There are a couple methods to do this. Let’s start by adapting the query I provided earlier to return 7,534 gates connected to the perimeters of dog parks:
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX osmway: <https://www.openstreetmap.org/way/>
PREFIX osmkey: <https://www.openstreetmap.org/wiki/Key:>
PREFIX osmrel: <https://www.openstreetmap.org/relation/>
SELECT * WHERE {
?gate osmkey:barrier "gate" .
?gate ^(osmway:member/osmway:member_id)/^(osmrel:member/osmrel:member_id)? ?dog_park .
?dog_park osmkey:leisure "dog_park" .
?gate geo:hasGeometry/geo:asWKT ?geometry .
}
The property path is starting to look like code golf, so let’s break it down. Normally, to find all the members of a set of relations, you use:
?multipolygon osmrel:member ?member .
?member osmrel:member_id ?ring .
If you don’t need a separate variable to filter on the member’s role or list position, then you can use a property path as shorthand:
?multipolygon osmrel:member/osmrel:member_id ?ring .
Similarly, you can get the vertex of a way using a property path:
?fence osmway:member/osmway:member_id ?gate .
Property paths are sort of like regular expressions. You can use operators like ? to make part of the path optional or + to repeat it one or more times. You can also use ^ to flip the path around. So instead of getting the fences first and then restricting the results to the fences that have child gates, you can get the gates first and restrict the set to the gates that have parent fences.
There’s a more performant method to getting more or less the same results. This very different query yields 7,646 gates along the perimeters of dog parks:
PREFIX ogc: <http://www.opengis.net/rdf#>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX osmkey: <https://www.openstreetmap.org/wiki/Key:>
SELECT * WHERE {
?gate osmkey:barrier "gate" .
?gate ogc:sfTouches ?dog_park .
?dog_park osmkey:leisure "dog_park" .
?gate geo:hasGeometry/geo:asWKT ?geometry .
}
QLever has precomputed the DE-9IM spatial relationships between all the elements in OSM. sfTouches says that the gate’s geometry touches the perimeter of the dog park’s geometry. It doesn’t matter if the dog park is an area or a multipolygon, as long as the geometry is valid and closed. This is much simpler and faster, but it’s purely a spatial relationship, not a topological one. Apparently 112 gates are disconnected from the dog parks that they guard. Or maybe some of them are on a different level of a building and happen to coincide.
QLever is optimized for getting select information about the results in columnar format. It is possible to get all the tags of all the results, but this isn’t as natural for the triplestore architecture.
Figuring out which gates and dog parks to associate with each other is expensive enough that you only want QLever to do that work once per gate, not once for each tag of each dog park. To improve performance, a subquery isolates the first step from the step of getting all the tags (or geometry). Here are 28,775 tags on dog parks that have gates along their perimeters:
PREFIX ogc: <http://www.opengis.net/rdf#>
PREFIX osmkey: <https://www.openstreetmap.org/wiki/Key:>
SELECT * WHERE {
{
SELECT ?gate ?dog_park WHERE {
?gate osmkey:barrier "gate" .
?gate ogc:sfTouches ?dog_park .
?dog_park osmkey:leisure "dog_park" .
}
}
?dog_park ?key ?value .
FILTER(STRSTARTS(STR(?key), STR(osmkey:)))
}
The results are in two columns, key and value. If you want each value in a dedicated column per key, the query needs to explicitly store each key’s value in a separate variable, one variable for each key you care about. You can wrap each statement in an OPTIONAL block. It isn’t possible to return an unlimited number of columns for any key that it comes across.