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

geof:sfContains(a, b)

Geometry a contains geometry b.

geof:sfWithin(a, b)

Geometry a is inside geometry b.

geof:sfIntersects(a, b)

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

--geo-min-level

0

Coarsest S2 cell level in a covering.

--geo-max-level

24

Finest S2 cell level (points always sit at the level-30 leaf).

--geo-max-cells

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))'