Geospatial Data
Waratah knows geometry natively: geometry values are literals in your data, spatial questions are SPARQL functions, and an S2 cell index makes those questions fast. The support follows GeoSPARQL conventions, so existing geo RDF tends to work unchanged.
The plugin is always active — no setup, no flag.
Geometry literals
A geometry is a typed literal. The everyday form is WKT text with the
geo:wktLiteral datatype:
"POINT(153.02 -27.46)"^^<http://www.opengis.net/ont/geosparql#wktLiteral>
Coordinates are normalized to CRS84 — longitude first, latitude second —
and the engine also reads the binary geo:wkbLiteral form. Attach the
literal to your resources like any other value (Turtle syntax, part of an
example dataset used throughout this page):
@prefix ex: <http://ex/> .
@prefix geo: <http://www.opengis.net/ont/geosparql#> .
ex:brisbane ex:name "Brisbane" ;
geo:hasGeometry "POINT(153.02 -27.46)"^^geo:wktLiteral .
ex:ipswich ex:name "Ipswich" ;
geo:hasGeometry "POINT(152.90 -27.60)"^^geo:wktLiteral .
ex:sydney ex:name "Sydney" ;
geo:hasGeometry "POINT(151.20 -33.87)"^^geo:wktLiteral .
Load it as you would any RDF file — for example into a running cluster:
waratah import places.ttl --connect
The three spatial functions
| Function | True when |
|---|---|
|
Geometry |
|
Geometry |
|
The two geometries share at least one point. |
Each takes two geometry literals and returns a boolean; evaluation is an exact geometric computation (DE-9IM), not a bounding-box approximation.
Asking spatial questions
Which of our places are inside a rectangle over greater Brisbane?
PREFIX ex: <http://ex/>
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
SELECT ?name WHERE {
?place ex:name ?name ;
geo:hasGeometry ?loc .
FILTER(geof:sfWithin(?loc, "POLYGON((152.8 -27.7, 153.2 -27.7, 153.2 -27.2, 152.8 -27.2, 152.8 -27.7))"^^geo:wktLiteral))
}
Brisbane and Ipswich; Sydney is well outside. The same window as
sfContains with the arguments swapped answers equivalently:
FILTER(geof:sfContains("POLYGON((...))"^^geo:wktLiteral, ?loc))
The window does not have to be spelled inline — a VALUES row works the
same way:
VALUES ?window { "POLYGON((...))"^^geo:wktLiteral }
FILTER(geof:sfWithin(?loc, ?window))
How it answers fast
At load time, every geometry literal is covered by a small set of S2
cells (a hierarchical grid over the sphere) and those cell postings are
written to the storage index. When a query compares stored geometries
against a constant window — inline literal or VALUES-bound, as above —
the planner uses the window’s cell covering to read only candidate rows
from the index, then runs the exact geometric check on those candidates.
Queries where both geometries are variables cannot use the index; they
still answer correctly, evaluating the geometry pair by pair.
Tuning the covering
Three waratah start flags control how finely geometries are covered:
| Flag | Default |
|---|---|
Meaning |
|
0 |
Coarsest S2 cell level in a covering. |
|
24 |
Finest S2 cell level (points always sit at the level-30 leaf). |
|
40 |
Maximum cells per covering — higher is finer-grained but larger indexes. |
These values must be identical on every process in the cluster, and changing them changes how the index is built — after a change, rebuild:
waratah geo rebuild-index --data-dir ./waratah-data
That wipes and recomputes every spatial posting under the current configuration.
Diagnosing the index
waratah geo census is a read-only inspection of the spatial index:
posting counts per S2 level, envelope coverage, and the statistics
histogram’s incidence mass. With --window it takes one query window (a
WKT literal, like a geof: conjunct would carry) and decomposes the
estimate the planner would make against what a full scan actually
returns:
waratah geo census --data-dir ./waratah-data \
--window 'POLYGON((152.8 -27.7, 153.2 -27.7, 153.2 -27.2, 152.8 -27.2, 152.8 -27.7))'