GeoSPARQL Support in QLever
This page describes which features from the OGC GeoSPARQL standard are supported in QLever, and how to use them. It also describes some custom extensions, like nearest neighbor search.
Geometry Preprocessing
QLever can preprocess geometries to accelerate various queries. This can be requested via the option VOCABULARY_TYPE = on-disk-compressed-geo-split in the [index] section of your Qleverfile for use with qlever index or the --vocabulary-type on-disk-compressed-geo-split argument of IndexBuilderMain.
If this option is used, QLever will currently precompute centroid, bounding box, geometry type, number of child geometries, length and area for all WKT literals in the input dataset. These can be used for the respective GeoSPARQL functions, but also for further optimizations (for example, automatic prefiltering of geometries for more efficient geometric relation filters). More optimizations will be added over time.
Note: If you use this option, please expect that you have to rebuild your index multiple times in the coming weeks and months while QLever is being updated to support more GeoSPARQL features efficiently. The server will report an error during startup if an index rebuild is necessary.
GeoSPARQL Functions
geof:distance(?geom_1, ?geom_2, ?unit):
Returns the geodesic distance between two geometries on the WGS84 ellipsoid.
The first two arguments must be literals
with datatype geo:wktLiteral, representing geometries in WKT format. The
(optional) third argument specifies the unit, and must be one of unit:M
(meters), unit:KiloM (kilometers), unit:MI (land miles), where unit: is
the prefix for http://qudt.org/vocab/unit/. Alternatively, the unit IRI can
be given as an xsd:anyURI literal. If no unit is given, the distance is
returned in kilometers. The distance is returned as a literal with datatype
xsd:decimal. RESTRICTION: Currently, only POINT geometries are supported;
this will be extended to other geometry types in the near future. NOTE: For a
fast distance-based search, please also see GeoSPARQL Maximum Distance
Search below.
geof:metricDistance(?geom_1, ?geom_2):
Like geof:distance, but always returns the distance in meters as xsd:decimal.
Example query for geof:distance and geof:metricDistance
The correct distance between the two points is approximately 446.363 meters.
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
PREFIX unit: <http://qudt.org/vocab/unit/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
SELECT * WHERE {
# Freiburg Central Railway Station and Freiburg University Library
BIND ("POINT(7.8412948 47.9977308)"^^geo:wktLiteral AS ?a)
BIND ("POINT(7.8450491 47.9946000)"^^geo:wktLiteral AS ?b)
# Using unit: for unit IRIs
BIND (geof:distance(?a, ?b, unit:M) AS ?d_meters_1)
BIND (geof:distance(?a, ?b, unit:KiloM) AS ?d_kilometers_1)
BIND (geof:distance(?a, ?b, unit:MI) AS ?d_miles_1)
# Using xsd:anyURI for unit IRIs
BIND (geof:distance(?a, ?b, "http://qudt.org/vocab/unit/M"^^xsd:anyURI) AS ?d_meters_2)
BIND (geof:distance(?a, ?b, "http://qudt.org/vocab/unit/KiloM"^^xsd:anyURI) AS ?d_kilometers_2)
BIND (geof:distance(?a, ?b, "http://qudt.org/vocab/unit/MI"^^xsd:anyURI) AS ?d_miles_2)
# Without unit argument, defaults to kilometers
BIND (geof:distance(?a, ?b) AS ?d_kilometers_3)
# For backwards compatibility
BIND (geof:metricDistance(?a, ?b) AS ?d_meters_3)
}
geof:latitude(?geom), geof:longitude(?geom):
Returns latitude and longitude coordinates from a POINT geometry, given as a
literal with datatype geo:wktLiteral. The return value is a literal with
datatype xsd:decimal.
Example query for geof:latitude and geof:longitude
Coordinates of Freiburg Central Railway Station:
geof:centroid(?geom): Returns the centroid of the
given geometry, which must be a literal with datatype geo:wktLiteral. The
return value is a literal with datatype geo:wktLiteral, representing a POINT.
This function benefits from geometry preprocessing.
Example query for geof:centroid
The centroid should be 3,3.
geof:envelope(?geom): Returns the bounding box of a geometry. The geometry
must be given as a literal with datatype geo:wktLiteral. The return value is
a literal with datatype geo:wktLiteral, representing a POLYGON with exactly
five points (the first and last point are identical). This function benefits
from geometry preprocessing.
Example query for geof:envelope
The envelope of the given
LINESTRING should be the rectangle with corners (2,4),
(8,4), (8,6), (2,6), (2,4).
geof:geometryType(?geom): Returns the geometry type of the given geometry.
The geometry must be given as literal with datatype geo:wktLiteral. The
return value is a literal with datatype xsd:anyURI, where the value is one of
the geometry type IRIs defined by the OGC Simple Features
Specification:
http://www.opengis.net/ont/sf#Point,
http://www.opengis.net/ont/sf#LineString,
http://www.opengis.net/ont/sf#Polygon,
http://www.opengis.net/ont/sf#MultiPoint,
http://www.opengis.net/ont/sf#MultiLineString,
http://www.opengis.net/ont/sf#MultiPolygon,
http://www.opengis.net/ont/sf#GeometryCollection. This function benefits
from geometry preprocessing.
Example query for geof:geometryType
The result should be
"http://www.opengis.net/ont/sf#LineString"^^xsd:anyURI.
geof:minX(?geom), geof:minY(?geom), geof:maxX(?geom), geof:maxY(?geom):
Return the minimum and maximum X (longitude) and Y (latitude) coordinates of the
given geometry. The geometry must be given as a literal with datatype
geo:wktLiteral. This function benefits from
geometry preprocessing. The return values are
literals with datatype xsd:decimal.
Example query for geof:minX, geof:minY, geof:maxX and geof:maxY
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
SELECT * {
BIND("LINESTRING(2 4, 3 3, 6 8)"^^geo:wktLiteral AS ?geometry)
BIND (geof:minX(?geometry) AS ?minX) # Result: 2
BIND (geof:minY(?geometry) AS ?minY) # Result: 3
BIND (geof:maxX(?geometry) AS ?maxX) # Result: 6
BIND (geof:maxY(?geometry) AS ?maxY) # Result: 8
}
geof:length(?geom, ?unit): This function computes the length of a geometry given as geo:wktLiteral. Note that not only linestrings have a length according to the OGC standard, but also for example polygons. The length function supports the same units as the geof:distance function. This function benefits from geometry preprocessing.
geof:metricLength(?geom): The same as `geof:length`` but always returns the length in meters.
Example query for geof:length and geof:metricLength
Length of the Rhine Valley Railway Mannheim - Basel:
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
PREFIX unit: <http://qudt.org/vocab/unit/>
PREFIX osmrel: <https://www.openstreetmap.org/relation/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
SELECT * {
osmrel:1781296 geo:hasGeometry/geo:asWKT ?geom .
BIND (geof:metricLength(?geom) AS ?meters) # Result: 267084
BIND (geof:length(?geom, unit:M) AS ?meters2) # Result: 267084
BIND (geof:length(?geom, unit:KiloM) AS ?kilometers2) # Result: 267.084
BIND (geof:length(?geom, unit:MI) AS ?miles2) # Result: 165.958
BIND (geof:length(?geom, "http://qudt.org/vocab/unit/M"^^xsd:anyURI) AS ?meters3) # Result: 267084
BIND (geof:length(?geom, "http://qudt.org/vocab/unit/KiloM"^^xsd:anyURI) AS ?kilometers3) # Result: 267.084
BIND (geof:length(?geom, "http://qudt.org/vocab/unit/MI"^^xsd:anyURI) AS ?miles3) # Result: 165.958
}
geof:area(?geom, ?unit): This function computes the area of a geometry given as geo:wktLiteral. The supported units are currently square meters unit:M2, square kilometers unit:KiloM2 and square land miles unit:MI2. The units may be passed as an IRI with the prefix http://qudt.org/vocab/unit/ or as xsd:anyURI literal. The function returns an xsd:decimal. This function benefits from geometry preprocessing. NOTE: The OGC standard says that geof:area and geof:metricArea "must return zero for all geometry types other than Polygon". We interpret this such that an area may still be returned for polygons contained in MULTIPOLYGON or GEOMETRYCOLLECTION literals.
geof:metricArea(?geom): The same as geof:area but always returns the area in square meters.
Example query for geof:area and geof:metricArea
Area of the water reservoir "Llac d'Engolasters" in Andorra:
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX osmway: <https://www.openstreetmap.org/way/>
PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
PREFIX unit: <http://qudt.org/vocab/unit/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
SELECT * WHERE {
osmway:6593464 geo:hasGeometry/geo:asWKT ?geometry .
BIND (geof:metricArea(?geometry) AS ?metric_area)
BIND (geof:area(?geometry, unit:M2) AS ?area_sqm)
BIND (geof:area(?geometry, unit:KiloM2) AS ?area_sqkm)
BIND (geof:area(?geometry, unit:MI2) AS ?area_sqmi)
BIND (geof:area(?geometry, "http://qudt.org/vocab/unit/M2"^^xsd:anyURI) AS ?area_sqm_2)
BIND (geof:area(?geometry, "http://qudt.org/vocab/unit/KiloM2"^^xsd:anyURI) AS ?area_sqkm_2)
BIND (geof:area(?geometry, "http://qudt.org/vocab/unit/MI2"^^xsd:anyURI) AS ?area_sqmi_2)
}
geof:numGeometries(?geom): This function returns the number of child geometries contained in a geometry of a collection type (MULTIPOINT, MULTILINESTRING, MULTIPOLYGON or GEOMETRYCOLLECTION) given as geo:wktLiteral. It returns an xsd:integer. This function benefits from geometry preprocessing.
Example query for geof:numGeometries
The top 100 geometries with the most members:
GeoSPARQL Maximum Distance Search
QLever supports each of the following query patterns for a fast maximum distance search:
FILTER(geof:distance(?geom1, ?geom2) <= constant)
FILTER(geof:metricDistance(?geom1, ?geom2) <= constant)
FILTER(geof:distance(?geom1, ?geom2, <some-supported-unit-iri>) <= constant)
FILTER(geof:distance(?geom1, ?geom2, "some-supported-unit-iri"^^xsd:anyURI) <= constant)
In the Analysis view of the QLever UI you can see Spatial Join instead of Cartesian Product Join, when the optimization is in effect. The GeoSPARQL maximum distance search is a standard syntax method for using the QLever Spatial Search. The custom feature provides more options, for example nearest neighbor search.
The implementation currently has to parse WKT geometries for all geometry types except points. This is being worked on, so you may expect a performance improvement in the future.
Current quirk: The maximum distance search (each of the FILTER patterns above) supports the WKT geometry types POINT, LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, MULTIPOLYGON and GEOMETRYCOLLECTION, while the non-optimized geof:distance and geof:metricDistance implementation only supports POINT so far.
Example query
All restaurants within 50 meters of public transport stops:
SELECT ?restaurant ?stop ?restaurant_geometry WHERE {
?restaurant osmkey:amenity "restaurant" ;
geo:hasGeometry/geo:asWKT ?restaurant_geometry .
?stop osmkey:public_transport "stop_position" ;
geo:hasGeometry/geo:asWKT ?stop_geometry .
FILTER (geof:metricDistance(?restaurant_geometry,?stop_geometry) <= 50)
}
GeoSPARQL Geometric Relations
QLever supports each of the following query patterns for GeoSPARQL geometric relation functions:
FILTER geof:sfIntersects(?geom1, ?geom2)
FILTER geof:sfContains(?geom1, ?geom2)
FILTER geof:sfCovers(?geom1, ?geom2)
FILTER geof:sfCrosses(?geom1, ?geom2)
FILTER geof:sfTouches(?geom1, ?geom2)
FILTER geof:sfEquals(?geom1, ?geom2)
FILTER geof:sfOverlaps(?geom1, ?geom2)
FILTER geof:sfWithin(?geom1, ?geom2)
These GeoSPARQL-compliant filters are a standard syntax method for using the
QLever Spatial Search with qlss:algorithm set to
qlss:libspatialjoin and qlss:joinType set appropriately.
NOTE: Currently, the functions stated above are only supported in FILTERs
between two different variables. Also there may not be multiple filters on the
same pair of variables. Otherwise the query processing will return an error.
This will be fixed in the near future. Also, the implementation currently has
to parse WKT geometries for all geometry types except points. This is being
worked on, so you may expect a performance improvement in the future.
Precomputing GeoSPARQL geometric relations using osm2rdf
For OpenStreetMap data, geometric relations can be precomputed as part of the dataset (e.g. ogc:sfContains, ogc:sfIntersects, ... triples) using osm2rdf. Geometries from osm2rdf are represented as geo:wktLiterals, which can be addressed by geo:hasGeometry/geo:asWKT. osm2rdf also provides centroids of objects via geo:hasCentroid/geo:asWKT and more, if requested. Please note that the geometric relations are given as triples between the OpenStreetMap entities, not the geometries.
Example query with osm2rdf
All Buildings in the City of Freiburg:
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX osmkey: <https://www.openstreetmap.org/wiki/Key:>
PREFIX ogc: <http://www.opengis.net/rdf#>
PREFIX osmrel: <https://www.openstreetmap.org/relation/>
SELECT ?osm_id ?hasgeometry WHERE {
osmrel:62768 ogc:sfContains ?osm_id .
?osm_id geo:hasGeometry/geo:asWKT ?hasgeometry .
?osm_id osmkey:building [] .
}
Custom spatial search
QLever supports a custom fast spatial search operation for geometries from
literals with geo:wktLiteral datatype. It can be invoked using a SERVICE
operation to the IRI <https://qlever.cs.uni-freiburg.de/spatialSearch/>. Note
that this address is not contacted but only used to activate the feature
locally. A spatial query has the following form:
PREFIX qlss: <https://qlever.cs.uni-freiburg.de/spatialSearch/>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
SELECT * WHERE {
# Arbitrary operations that select ?left_geometry
?some_entity geo:hasCentroid/geo:asWKT ?left_geometry .
SERVICE qlss: {
_:config qlss:algorithm qlss:s2 ;
qlss:left ?left_geometry ;
qlss:right ?right_geometry ;
qlss:numNearestNeighbors 2 ;
qlss:maxDistance 500 ;
qlss:bindDistance ?dist_left_right ;
qlss:payload ?payloadA , ?payloadB .
{
# Any subquery, that selects ?right_geometry, ?payloadA and ?payloadB
?some_other_entity geo:hasCentroid/geo:asWKT ?right_geometry .
# ...
}
}
}
The SERVICE must include the configuration triples and exactly one group graph pattern that selects the right geometry. If numNearestNeighbors is not used, the right geometry may also be provided outside of the SERVICE definition.
Configuration parameters
The following configuration parameters are provided in the SERVICE as triples with arbitrary subject. The predicate must be an IRI of the form <parameter> or qlss:parameter. The parameters left and right are mandatory. Additionally you must provide search instructions, either numNearestNeighbors or maxDistance or joinType. The remaining parameters are optional.
| Parameter | Domain | Description |
|---|---|---|
algorithm |
<baseline>, <s2>, <boundingBox>, <libspatialjoin> |
The algorithm to use. |
left |
variable | The left join table: "for every [left] geometry ...". Must refer to a column with literals of geo:wktLiteral datatype. |
right |
variable | The right join table: "... find the closest/all intersecting/... [right] geometries". Must refer to a column with literals of geo:wktLiteral datatype. |
numNearestNeighbors |
integer | The maximum number of nearest neighbor points from right for every point from left. Only supported by the baseline and s2 algorithms. |
maxDistance |
integer | The maximum distance in meters between points from left and right to be included in the result. |
bindDistance |
variable | An otherwise unbound variable name which will be used to give the distance in kilometers between the result point pairs. |
payload |
variable or IRI <all> |
Variable from the group graph pattern inside the SERVICE to be included in the result. right is automatically included. This parameter may be repeated to include multiple variables. For all variables use <all>. If right is given outside of the SERVICE do not use this parameter. |
joinType |
<intersects>, <covers>, <contains>, <touches>, <crosses>, <overlaps>, <equals>, <within-dist> |
The geometric relation to compute between the left and right geometries. If within-dist is chosen, the maxDistance parameter is required. Mandatory when using the libspatialjoin algorithm and illegal for all other algorithms. |
NOTE: The individual algorithms support different subsets of all valid literals of geo:wktLiteral datatype. The libspatialjoin algorithm supports POINT, LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, MULTIPOLYGON and GEOMETRYCOLLECTION. The baseline and boundingBox algorithms support the same literals except GEOMETRYCOLLECTION. The s2 algorithm currently only works with POINT literals.
NOTE: Geometries except for points currently need to be parsed for every query leading to longer running times. We are working on it.
Example queries
For each railway station, the three closest supermarkets:
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX osmkey: <https://www.openstreetmap.org/wiki/Key:>
PREFIX qlss: <https://qlever.cs.uni-freiburg.de/spatialSearch/>
SELECT * WHERE {
?station osmkey:railway "station" ;
osmkey:name ?name ;
geo:hasCentroid/geo:asWKT ?station_geometry .
SERVICE qlss: {
_:config qlss:left ?station_geometry ;
qlss:right ?supermarket_geometry ;
qlss:numNearestNeighbors 3 .
{
?supermarket osmkey:shop "supermarket" ;
geo:hasCentroid/geo:asWKT ?supermarket_geometry .
}
}
}
PREFIX osmkey: <https://www.openstreetmap.org/wiki/Key:>
PREFIX osmrel: <https://www.openstreetmap.org/relation/>
PREFIX ogc: <http://www.opengis.net/rdf#>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX qlss: <https://qlever.cs.uni-freiburg.de/spatialSearch/>
SELECT DISTINCT ?rail ?rail_geometry WHERE {
osmrel:51477 ogc:sfIntersects ?river .
?river osmkey:water "river" .
?river geo:hasGeometry/geo:asWKT ?river_geometry .
SERVICE qlss: {
_:config qlss:algorithm qlss:libspatialjoin ;
qlss:left ?river_geometry ;
qlss:right ?rail_geometry ;
qlss:payload ?rail ;
qlss:joinType qlss:intersects .
{
osmrel:51477 ogc:sfContains ?rail .
?rail osmkey:railway "rail" .
?rail geo:hasGeometry/geo:asWKT ?rail_geometry .
}
}
}
Special predicate <max-distance-in-meters:m>: As a shortcut, a special
predicate <max-distance-in-meters:m> is also supported. The parameter m
refers to the maximum search radius in meters. It may be used as a triple with
the left join variable as subject and the right join variable as object.
Example query:
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
SELECT * WHERE {
?a geo:hasCentroid/geo:asWKT ?left_geometry .
?left_geometry <max-distance-in-meters:300> ?right_geometry .
?b geo:hasCentroid/geo:asWKT ?right_geometry .
}
Deprecated special predicate <nearest-neighbors:k> or
<nearest-neighbors:k:m>: This feature is deprecated and will produce a
warning, due to confusing semantics. Please use the SERVICE syntax instead.
A spatial search for nearest neighbors can be realized using ?left
<nearest-neighbors:k:m> ?right. Please replace k and m with integers as
follows: For each point ?left QLever will output the k nearest points from
?right. Of course, the sets ?left and ?right can each be limited using
further statements. Using the optional integer value m a maximum distance in
meters can be given that restricts the search radius. Example query:
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
SELECT * WHERE {
?a geo:hasCentroid/geo:asWKT ?left_geometry .
?left_geometry <nearest-neighbors:2:1000> ?right_geometry .
?b geo:hasCentroid/geo:asWKT ?right_geometry .
}
Runtime parameters
spatial-join-max-num-threads: the number of threads for a spatial search
operation (also GeoSPARQL FILTER on maximum distance or geometric relations)
can be limited. Setting the option to 0 amounts to taking the number of CPU
threads. The default value is 8, since further threads seem to provide little
additional performance gain.
spatial-join-prefilter-max-size: If the special VOCABULARY_TYPE for
geometries is used (see Geometry Preprocessing), the
inputs from the larger side of a spatial search are automatically prefiltered
based on the aggregated bounding box of the inputs from the smaller side. If
the aggregated bounding box is very large, the cost for prefiltering can
outweigh the savings. Thus prefiltering is disabled at a certain bounding box
size. This can be configured using spatial-join-prefilter-max-size. By
default the limit is 2500 square coordinates. To deactivate prefiltering
completely, set this to 0.