| name | geosparql |
| description | Use when adding GeoSPARQL spatial support to the sparq RDF/SPARQL engine — parsing geo:wktLiteral and geo:gmlLiteral (GML Simple-Features) geometries, calling geof: functions (distance, metricArea/metricLength/metricPerimeter/centroid, bounding coordinates, isEmpty, simplify, sf*/eh*/rcc8* DE-9IM relations, geometry/set operations, getSRID) inside SPARQL FILTER/BIND/SELECT via the sparq-engine extension-function registry, or building an R-tree GeoIndex over a Graph for within_distance / nearest / intersects queries. Crate: sparq-geo. |
sparq-geosparql
sparq-geo is the OPT-IN GeoSPARQL 1.0/1.1 core for the sparq engine: it parses geo:wktLiteral and geo:gmlLiteral (the GML Simple-Features geometry profile) lexical forms, evaluates the geof: spatial functions, packages those functions as a sparq_engine::FunctionRegistry so they run inside real SPARQL FILTER/BIND/SELECT, and builds an R-tree GeoIndex over a sparq_core::Graph for distance/nearest/intersection queries. It is a separate crate so the core engine and the wasm build carry zero geometry code — you engage spatial support only by depending on sparq-geo.
Quickstart
Cargo.toml:
[dependencies]
sparq-geo = { path = "../sparq-geo" }
sparq-core = { path = "../sparq-core" }
sparq-engine = { path = "../sparq-engine" }
geo-types = "0.7"
Run geof: functions inside a SPARQL query:
use sparq_core::Graph;
use sparq_engine::query_with_functions;
use sparq_geo::geof_registry;
let g = Graph::load_str(r#"
@prefix geo: <http://www.opengis.net/ont/geosparql#> .
<http://ex/london> <http://ex/loc> "POINT(-0.1278 51.5074)"^^geo:wktLiteral .
<http://ex/paris> <http://ex/loc> "POINT(2.3522 48.8566)"^^geo:wktLiteral .
"#, "turtle").unwrap();
let reg = geof_registry();
let r = query_with_functions(&g,
"PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
PREFIX uom: <http://www.opengis.net/def/uom/OGC/1.0/>
SELECT ?a ?b WHERE {
?a <http://ex/loc> ?ga . ?b <http://ex/loc> ?gb .
FILTER(STR(?a) < STR(?b) && geof:distance(?ga, ?gb, uom:kilometre) < 400)
}", ®).unwrap();
assert_eq!(r.len(), 1);
POINT / WKT coordinates are longitude latitude (CRS84, the GeoSPARQL default). A leading <CRS-IRI> before the WKT selects a CRS; EPSG:4326 literals are LAT/LONG and are axis-swapped to internal long/lat on parse and back on output.
Key APIs
pub fn parse_wkt_literal(lex: &str) -> Result<GeoGeometry, GeoError>;
pub fn parse_gml_literal(lex: &str) -> Result<GeoGeometry, GeoError>;
pub fn parse_geometry_literal(value: &str, datatype: &str) -> Result<GeoGeometry, GeoError>;
pub fn is_geometry_datatype(datatype: &str) -> bool;
pub struct GeoGeometry { pub crs: Crs, pub geometry: geo_types::Geometry<f64> }
impl GeoGeometry { pub fn new(g: Geometry<f64>) -> Self;
pub fn to_wkt_literal(&self) -> String;
pub fn to_gml_literal(&self) -> String;
pub fn metadata(&self) -> GeometryMetadata; }
pub enum Crs { Crs84, Epsg4326, Other(String) }
pub struct GeometryMetadata { pub dimension: Option<u8>,
pub coordinate_dimension: Option<u8>, pub spatial_dimension: Option<u8>,
pub is_empty: bool, pub is_simple: bool, pub has_serialization: String }
pub fn geof_registry() -> sparq_engine::FunctionRegistry;
pub fn geosparql_rewrite(sparql: &str) -> Result<sparq_engine::PreparedQuery, String>;
pub fn rewrite_query(q: spargebra::Query) -> spargebra::Query;
pub fn is_topology_property(iri: &str) -> bool;
pub fn distance(a: &GeoGeometry, b: &GeoGeometry, unit: Unit) -> Result<f64, GeoError>;
pub fn metric_area(g: &GeoGeometry) -> Result<f64, GeoError>;
pub fn metric_length(g: &GeoGeometry) -> Result<f64, GeoError>;
pub fn metric_perimeter(g: &GeoGeometry) -> Result<f64, GeoError>;
pub fn centroid(g: &GeoGeometry) -> Result<GeoGeometry, GeoError>;
pub fn max_x|min_x|max_y|min_y(g: &GeoGeometry) -> Result<f64, GeoError>;
pub fn is_empty(g: &GeoGeometry) -> Result<bool, GeoError>;
pub fn simplify(g: &GeoGeometry, tolerance: f64) -> Result<GeoGeometry, GeoError>;
pub fn sf_within(a, b) -> Result<bool, GeoError>;
pub fn relate(a, b, pattern: &str) -> Result<bool, GeoError>;
pub fn envelope|boundary|convex_hull(g: &GeoGeometry) -> Result<GeoGeometry, GeoError>;
pub fn buffer(g: &GeoGeometry, radius: f64, unit: Unit) -> Result<GeoGeometry, GeoError>;
pub fn intersection|union|difference|sym_difference(a, b) -> Result<GeoGeometry, GeoError>;
pub enum Unit { Metre, Kilometre, Mile, Degree, Radian }
pub fn GeoIndex::build(graph: &sparq_core::Graph) -> GeoIndex;
pub fn within_distance(&self, center: Point<f64>, meters: f64, limit: Option<usize>) -> Vec<(&Term, f64)>;
pub fn nearest(&self, center: Point<f64>, k: usize) -> Vec<(&Term, f64)>;
pub fn intersects(&self, g: &GeoGeometry) -> Vec<&Term>;
pub fn within_region_literals(&self, region: &GeoGeometry) -> Vec<Term>;
pub fn contains_region_literals(&self, region: &GeoGeometry) -> Vec<Term>;
pub fn apply_delta(&mut self, graph: &Graph, inserts: &[[Term;3]], deletes: &[[Term;3]]);
pub fn entries(&self) -> impl Iterator<Item=&Entry>;
Function IRIs are all under http://www.opengis.net/def/function/geosparql/. Result types in SPARQL: geof:distance / metricArea / metricLength / metricPerimeter / maxX / minX / maxY / minY → xsd:double; isEmpty and the relation families → xsd:boolean; envelope/boundary/centroid/convexHull/simplify/buffer/the four set ops → geo:wktLiteral; getSRID → xsd:anyURI. [GPT-5.6] sq-lc2io
The OPT-IN geof_accessors feature (OFF by default; the default geof_registry() byte-set is unchanged) adds the GeoSPARQL 1.1 §8.5 non-topological accessor functions — geof:dimension / geof:coordinateDimension / geof:spatialDimension → xsd:integer, geof:isSimple → xsd:boolean, geof:geometryType → the OGC Simple-Features geometry-class IRI (sf:Point etc.) — each delegating to the pure sparq_geo::metadata accessors; undefined cases (an empty geometry's dimensions, an unmapped collection's sf: class) return a GeoError::Unsupported-mapped per-row expression error rather than a fabricated value. [FABLE-5] sq-lsp7k
Common recipes
Spatial join — which point is within which polygon (SPARQL):
let r = query_with_functions(&g,
"PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
SELECT ?city ?region WHERE {
?city <http://ex/loc> ?pt . ?region <http://ex/area> ?poly .
FILTER(geof:sfWithin(?pt, ?poly)) }", &geof_registry()).unwrap();
Topology PROPERTY form via the query-rewrite extension (opt-in geosparql_rewrite feature) — write the relation as a triple, not a FILTER; the rewrite resolves each feature's default geometry and applies the matching geof::
let prepared = sparq_geo::geosparql_rewrite(
"PREFIX geo: <http://www.opengis.net/ont/geosparql#>
SELECT ?city WHERE { ?city geo:sfWithin <http://ex/region> }").unwrap();
let reg = geof_registry();
let r = sparq_engine::with_functions(®, || sparq_engine::query_prepared(&g, &prepared)).unwrap();
Chain geometry-producing + relation functions in one expression: geof:envelope / geof:buffer return a geo:wktLiteral that feeds straight back into another geof: call:
FILTER(geof:sfWithin(?there, geof:buffer(?here, 400, uom:kilometre))) # 400 km buffer
FILTER(geof:sfContains(geof:envelope(?poly), ?pt))
Build an R-tree index and run k-NN / radius / intersection queries:
use geo_types::Point;
use sparq_geo::{GeoIndex, parse_wkt_literal};
let index = GeoIndex::build(&graph);
let near = index.nearest(Point::new(2.35, 48.86), 5);
let ball = index.within_distance(Point::new(2.35, 48.86), 50_000.0, Some(20));
let hits = index.intersects(&parse_wkt_literal(
"POLYGON((-1 48, 3 48, 3 49, -1 49, -1 48))").unwrap());
Keep the index in sync with graph updates (no rebuild): apply the SAME batch to the graph, then to the index:
graph.apply_delta(&inserts, &deletes).unwrap();
index.apply_delta(&graph, &inserts, &deletes);
Call geof: directly as Rust (no SPARQL engine, works with --no-default-features):
use sparq_geo::{geof, geof::Unit, parse_wkt_literal};
let a = parse_wkt_literal("POINT(-0.1278 51.5074)").unwrap();
let b = parse_wkt_literal("POINT(2.3522 48.8566)").unwrap();
let km = geof::distance(&a, &b, Unit::Kilometre).unwrap();
let inside = geof::sf_within(&b, &parse_wkt_literal(
"POLYGON((-1 42.5,7 42.5,7 51,-1 51,-1 42.5))").unwrap()).unwrap();
let eastern_edge = geof::max_x(&b).unwrap();
let empty = geof::is_empty(&b).unwrap();
Parse or serialise a GML literal (or dispatch parsing by datatype): GML rides the same GeoGeometry as WKT, so it works everywhere a parsed geometry does. to_gml_literal emits the GML 3 Simple Features form and preserves CRS axis order:
use sparq_geo::{geof, geof::Unit, parse_gml_literal, parse_geometry_literal, vocab};
let london = parse_gml_literal(
"<gml:Point><gml:pos>-0.1278 51.5074</gml:pos></gml:Point>").unwrap();
let paris = parse_geometry_literal(
"<gml:Point><gml:pos>2.3522 48.8566</gml:pos></gml:Point>", vocab::GML_LITERAL).unwrap();
let km = geof::distance(&london, &paris, Unit::Kilometre).unwrap();
let london_gml = london.to_gml_literal();
assert_eq!(parse_gml_literal(&london_gml).unwrap(), london);
In SPARQL, a "…"^^geo:gmlLiteral object is accepted by every geof: function exactly like a geo:wktLiteral, and attaching geometry via geo:asGML makes it indexable by GeoIndex::build alongside geo:asWKT.
Reproject a projected literal into CRS84 (opt-in reproject feature):
use sparq_geo::reproject::{to_crs84, to_crs84_lex};
let crs84 = to_crs84_lex(
"<http://www.opengis.net/def/crs/EPSG/0/27700> POINT(530000 180000)").unwrap();
Gotchas / feature flags / prerequisites
engine feature (default ON) pulls in sparq-engine and exposes geof_registry(). Disable default features (default-features = false) for the pure geometry library (WKT literals, geof::* as plain Rust, GeoIndex) with no engine in the dependency graph — geof_registry / the registry then do not exist. The crate-level README front page (whose quickstart is a runnable doctest driving geof_registry) is attached only when engine is on (sq-wo5jw); the engine-less build gets a short pure-geometry crate doc, and the gating sparq-geo pure-geometry (--no-default-features) feature-matrix lane keeps that state (build/tests/doctests/clippy) green.
reproject feature (OFF by default) adds the reproject module (pure-Rust proj4rs). It ships only a small curated EPSG table: projected 27700 (British National Grid), 3857 (Web Mercator), 2154 (Lambert-93), 25832/25833 (ETRS89 UTM 32/33N), and WGS84 UTM zones 326xx/327xx; plus geographic 4258 (ETRS89), 4269 (NAD83), 4283 (GDA94), 4171 (RGF93), 4490 (CGCS2000) — degree-valued, written LAT/LONG per the EPSG registry (geographic_axis_order), axis-normalised + datum-shifted into CRS84 by to_crs84 (datums treated as WGS84-coincident, metre-level; sq-ove). NAD27 (4267) is deliberately unsupported (needs NADCON grids). Any other code is GeoError::Unsupported.
topology_index feature (OFF by default) adds GeoIndex::within_region_literals and contains_region_literals: the index window-scans a geographic constant region's AABB, prepares that constant once, then DE-9IM-refines every candidate. These methods return the exact, deduplicated literal set for sfWithin(literal, region) / sfContains(region, literal); empty and non-geographic constants return an empty set. The feature ALSO lights the engine's exact-candidate seam (a weak sparq-engine?/spatial-exact-pushdown feature edge): GeoIndexProvider::candidates_exact certifies this exact set to the engine, which then SKIPS the residual DE-9IM FILTER for certified rows — see the pushdown bullet below. [FABLE-5] sq-lk3aw.4
- In SPARQL, every
geof: failure is a per-row EXPRESSION error, not a hard query error: a wrong datatype (must be a geo:wktLiteral or geo:gmlLiteral), unparseable WKT/GML, CRS mismatch, unknown unit IRI, or an unsupported operand combo drops the row in a FILTER / leaves the variable unbound in a BIND — the query still succeeds. But an unregistered geof: IRI (e.g. geof:gmlToWkt, which is not implemented) is the engine's usual hard "unsupported SPARQL function" error.
- Relations are PLANAR DE-9IM (via
geo's Relate) in coordinate/degree space, not geodesic. The hand-curated tests/ogc_compliance_ratchet.rs ratchet pins the exact DE-9IM truth value of every sf*/eh*/rcc8* relation over point/line/polygon/MULTI* operands in both orders (floor rises only with genuinely-passing assertions). geof:distance with metric units (metre/kilometre/mile) requires a geographic CRS (CRS84/EPSG:4326) and is exact haversine for point↔point and point↔extended geometry (spherical closest point via HaversineClosestPoint). Extended↔extended geometry uses vertex-HaversineClosestPoint iteration (sq-lk3aw.3): for each vertex of each operand the haversine distance to the nearest point on the other geometry is computed; the minimum is returned. This resolves the prior equirectangular projection distortion and is exact when the closest pair involves at least one vertex (the common case). Remaining approximation: interior-of-segment↔interior-of-segment closest pairs, bounded by vertex arc spacing — uncommon for typical GeoSPARQL geometries; a continent-spanning differential test (tests/distance_differential.rs) pins this to ≤ 1 % vs a Geodesic (WGS84 Karney) oracle. geof:buffer in metric units still uses a local equirectangular frame about the geometry's mean latitude. Unit::Degree/Radian measure euclidean coordinate-space distance.
- Metric measurements are LOCAL-PLANAR.
geof:metricArea, metricLength, and metricPerimeter require CRS84/EPSG:4326 and use the same bounding-box-centred equirectangular metre frame as metric buffer; distortion grows with geographic extent and towards the poles. metricArea accepts polygonal inputs (holes subtracted; degenerate rings return zero), while metricLength / metricPerimeter accept curves and polygonal boundaries; dimensionally undefined inputs return GeoError::Unsupported. This strict unsupported dispatch intentionally follows the crate's honest-error policy, including metricArea(POINT), rather than manufacturing a numeric result. geof:centroid returns a point in the input CRS; geographic inputs use that same frame and other CRSs use their coordinate space. [GPT-5.6] sq-lsp7k.18
- Simplification uses coordinate-space Douglas–Peucker.
geof:simplify(g, tolerance) preserves the input CRS and retains only input vertices. Non-positive tolerances, points, multipoints, and two-vertex lines are unchanged. Line strings and polygon rings use geo's Ramer–Douglas–Peucker implementation; it is not topology-preserving, so a simplified polygon can be invalid. Rectangles, triangles, geometry collections, and non-finite positive tolerances return GeoError::Unsupported. [GPT-5.6] sq-lsp7k.23
- Set ops cover the point/line/polygon matrix. Polygon×polygon
intersection/union/difference/symDifference are exact (geo BooleanOps); the point and line cases are handled where well-defined — including 1-D set subtraction (line−line, line−polygon and their symDifference), done via i_overlay's string-line clip plus a linear-referencing collinear-overlap subtraction (point-set semantics: crossing points are measure-zero, so line−line drops only collinear overlaps; line−polygon keeps the line strictly outside the polygon closure; polygon−line/point is unchanged). The only remaining GeoError::Unsupported set-op cases are operands the dimension dispatch can't classify (e.g. a heterogeneous GEOMETRYCOLLECTION). Full operand matrix in the "Set-operation operand matrix" section below.
GeoIndex only indexes geographic-CRS, non-empty geometries reached via geo:asWKT or geo:asGML (default graph + named graphs), with the indexed entity resolved through geo:hasGeometry / geo:hasDefaultGeometry when present (else the serialization subject itself). Other CRSs, empties, non-geometry-typed objects, and unparseable WKT/GML are counted in index.skipped() and excluded — check it to detect mis-typed data. intersects also requires a geographic-CRS argument.
- Spatial FILTER pushdown (
SpatialProvider seam) is a candidate SUPERSET, never a filter. Wrap a GeoIndex as GeoIndexProvider::new(index) and install it with sparq_engine::with_spatial_index(Arc<dyn SpatialProvider>, || …) (alongside with_functions(&geof_registry(), …)): the engine recognises a pushable FILTER — geof:sfWithin/sfIntersects(?g, box) and geof:distance(?g, pt, unit) OP r with OP ∈ {<,<=} (metric units only; >/>= is an unbounded exterior and stays post-hoc) — asks the provider for the candidate literals over the geometry variable, restricts rows to them, and still runs the EXACT geof: FILTER. Results are byte-identical to running without the index; only fewer geometries are exact-checked. Answer-safety: a binding the index has NO opinion on (bound via a non-geo:asWKT predicate, a non-geographic CRS, a different graph, …) is NEVER dropped — it is kept for the exact FILTER. The keep decision is made at the ID level: SpatialProvider::indexed_ids(dict_ptr) (backed by GeoIndex::indexed_ids_for) returns the indexed-literal id-set as an Arc<FxHashSet<Id>> ONLY when dict_ptr (std::ptr::from_ref(&graph.dict) as usize) matches the dict the index was built over — a pure integer-lookup fast path that avoids per-row Term materialisation; on any dict mismatch it returns None and the engine falls back to the per-row is_indexed(&Term) check (always correct). Rebuild or apply_delta the inner index on data change and wrap afresh. [OPUS-4.8] sq-7jt80
- EXACT-candidate pushdown (
spatial-exact-pushdown engine feature, OFF by default; driven by sparq-geo's topology_index) may SKIP the residual geof: FILTER — but ONLY for provider-CERTIFIED rows. For the within-region orientations — geof:sfWithin(?g, REGION) and (newly recognised) geof:sfContains(REGION, ?g), Simple Features converses — the engine first asks SpatialProvider::candidates_exact(SpatialExactQuery::WithinRegion): a Some(v) CERTIFIES v is EXACTLY the indexed bindings satisfying the predicate (no false positives/negatives over the indexed universe — a strictly stronger contract than candidates' superset). Rows are restricted to certified ∪ not-indexed; the residual FILTER is skipped only when EVERY surviving row is certified (then it is provably an identity). Soundness invariant: a NOT-indexed binding is never certified — if any survives, the residual FILTER still runs (identity on certified rows, sole judge of the rest). Declining (None, the default) falls back to the superset path. Differentials: sparq-engine/tests/spatial_exact_pushdown.rs (mock, mutation-checked) + sparq-geo/tests/exact_pushdown.rs (end-to-end, zero residual DE-9IM checks on an all-indexed corpus). [FABLE-5] sq-lk3aw.4
geo:gmlLiteral rides the SAME pipeline as WKT. GML literals use the OGC GML Simple-Features geometry profile (parse_gml_literal): gml:Point/LineString/Polygon (exterior + interior rings)/MultiPoint/MultiCurve/MultiSurface, both GML 3 (gml:pos/posList/exterior) and GML 2 (gml:coordinates/outerBoundaryIs) spellings, namespace-prefix-agnostic, with srsName→CRS (URN/EPSG: forms; EPSG:4326 axis swap) matching the WKT path. GeoGeometry::to_gml_literal emits the GML 3 forms for the same six geometry classes, including holes and aggregates, with the inverse EPSG:4326 swap. [GPT-5.6] sq-i3wb5. An empty lexical form (or a member-less aggregate such as <gml:MultiPoint/> / <gml:MultiGeometry/>) is the empty geometry (OGC R16), the GML twin of the empty wktLiteral. A geof: call may mix a GML and a WKT argument. Beyond GML-SF (parsed additively into the same 2-D model): gml:Envelope (gml:lowerCorner/upperCorner → the 5-point bbox Polygon); arc-segment gml:Curve/gml:Surface — gml:LineStringSegment (linear) plus gml:Arc/gml:ArcString/gml:CircularArcByCenterPoint, densified to a polyline at a fixed 5°-per-chord step (gml::ARC_STEP_DEG); this is an APPROXIMATION (no circular-arc type in geo_types), so densified topology/length/area are accurate only to that resolution; and srsDimension="3" coordinates, whose Z ordinate is parsed then projected out (the model is 2-D). Still deferred (clean GeoError::Unsupported): tessellated gml:Triangle/gml:TIN patches and non-circular (elliptic/clothoid) arc interpolations.
- No RIF/
geor: query rewriting, no .hdt write-out. This is the GeoSPARQL core; see the crate's open beads (bd list -l area:sparq-geo) for the boundary.
- Server wiring:
sparq-server exposes exactly this behind its opt-in geo cargo feature (cargo build -p sparq-server --features geo), which installs geof_registry() on every SPARQL endpoint via with_functions. With the feature off, the server and the wasm build carry no geometry code.
- Reuse the registry:
geof_registry() is clone-cheap (Arc-shared fns) and Send + Sync — build it once (e.g. a OnceLock) and share across queries and threads.
- Constant-geometry parse caching (sq-lkrgi): the registry parses geometry arguments through a small bounded PER-THREAD cache keyed by (datatype, exact lexical form), so a constant geometry in a
FILTER/BIND — e.g. Geographica's ~100 KB polygon constants — is parsed once per thread, not once per row. Sound because a lexical form parses to the same geometry deterministically (no graph state feeds the parse; no invalidation needed); WKT and GML are keyed separately; parse FAILURES are never cached (each erroneous row re-errors, preserving per-row expression-error semantics). Bounded by entry count and total key bytes; the MRU entry is never evicted, so even a single over-budget constant stays resident. [FABLE-5]
- Prepared-relate caching (sq-hq8t5): the DE-9IM relation families (
sf*/eh*/rcc8*/relate) additionally cache a prepared form of each REUSED operand (geo::PreparedGeometry's indexed topology graph — edge R*-tree + self-nodes) in the same per-thread cache entry, the structure Jena's prepared-GeometryWrapper cache reuses. Built lazily on the entry's first warm relate lookup — a cache hit is the proof of reuse; single-use per-row geometries never pay preparation — and evicted with its entry under the same bounds. Results are byte-identical to unprepared relates (a prepared operand only substitutes a precomputed, equal GeometryGraph); differential tests pin registry-vs-plain equality across all 24 relations, geof:relate patterns, every prepared/unprepared dispatch arm, and the CRS-mismatch/malformed-pattern error strings. Honest boundary: this removes the constant side's per-row graph rebuild only — the STREAMING side's graph is still built per row (inherent: each row geometry is new), and no Geographica re-measurement has been recorded yet (the sq-lkrgi probe + Geographica harvest are the follow-up). [FABLE-5]
Set-operation operand matrix
The four point-set operations dispatch on operand dimension:
| operands | intersection | union | difference (a − b) | symDifference |
|---|
| polygon × polygon | ✅ overlay | ✅ overlay | ✅ overlay | ✅ overlay |
| point × point | ✅ shared points | ✅ MULTIPOINT | ✅ set subtraction | ✅ set sym-diff |
| point × line/polygon | ✅ points on the other | ✅ GEOMETRYCOLLECTION | ✅ points not on the other | ✅ (point−other) ∪ other |
| line × line | ✅ crossings + collinear overlaps | ✅ MULTILINESTRING | ✅ collinear-overlap subtraction | ✅ (a−b) ∪ (b−a) |
| line × polygon | ✅ line clipped to the polygon | ✅ GEOMETRYCOLLECTION | ✅ line outside the polygon | ✅ (line outside) ∪ polygon |
| polygon × line/point | (symmetric) | (symmetric) | ✅ polygon unchanged (measure-zero) | ✅ (symmetric) |
Results are the lowest-dimension geometry capturing the answer and serialise back to geo:wktLiteral; line/line and line/polygon unions are plain set-unions (not noded/dissolved). Roll-your-own + upstream: geo 0.33's BooleanOps overlays only polygons, so the line cases are done locally (i_overlay's FloatClip::clip_by + an in-crate linear-referencing subtraction); a geo-side LineString difference is queued upstream for georust/geo (bead sq-fxv3). GML's parser is likewise a roll-your-own profile (no maintained pure-Rust GML geometry crate exists) queued for georust (bead sq-zy0).
Benchmarking the geo surface
- Per-commit HARD gate:
bench/geo/run.sh (fixed 100k-point CRS84 corpus; result-set-size +
compliance counts asserted vs bench/geo/expected.tsv — COUNTS-NOT-COORDINATES).
- Same-box competitor comparison:
scripts/bench/geo-same-box.sh (vs jena-fuseki-geosparql;
opt-in GEO_GEOGRAPHICA=1 adds the Geographica real-world LGD/GeoNames family — pinned recipe
bench/geo/geographica.sh, pinned queries bench/geo/queries-geographica/). No timing is
trusted without result-set-size agreement per query; see bench/geo/README.md.
- Ad-hoc query timing:
bench_geo query <corpus.nt> <query.rq> [iters] (the crate example,
default engine feature) — in-process query_with_functions + geof_registry(), emitting
name\tcount\tus.
See also
serve / sparql-query sibling skills for running the engine and writing SPARQL.
hdt-format and fused-decompress-parse for getting RDF into the Graph that GeoIndex::build and geof_registry() operate on.