@unrdf/geosparql
v26.4.8
Published
OGC GeoSPARQL standard compliance for spatial RDF queries
Maintainers
Readme
@unrdf/geosparql
OGC GeoSPARQL standard compliance for spatial RDF queries. Enterprise-grade geospatial capabilities for UNRDF knowledge graphs.
Features
- OGC Compliance: Full GeoSPARQL 1.0 standard implementation
- Geometry Types: Point, LineString, Polygon with WKT support
- Spatial Relations: within, intersects, contains, touches, disjoint, overlaps
- Distance Calculations: Haversine formula with meter-level accuracy
- Spatial Indexing: R-tree for high-performance queries (10K+ geometries)
- CRS Support: WGS84, CRS84, EPSG:4326
- GeoSPARQL Functions: 13 standard functions (distance, buffer, area, etc.)
- OTEL Instrumentation: Production-ready observability
Installation
pnpm add @unrdf/geosparqlQuick Start
import {
Point,
Polygon,
within,
distance,
createGeoSPARQLEngine
} from '@unrdf/geosparql';
// Create geometries
const sanFrancisco = Point([-122.4194, 37.7749]);
const losAngeles = Point([-118.2437, 34.0522]);
// Calculate distance
const dist = distance(sanFrancisco, losAngeles);
console.log(`Distance: ${(dist / 1000).toFixed(2)} km`); // ~559 km
// Spatial queries
const bayArea = Polygon([[
[-122.5, 37.7],
[-122.3, 37.7],
[-122.3, 37.9],
[-122.5, 37.9],
[-122.5, 37.7]
]]);
console.log(within(sanFrancisco, bayArea)); // trueGeometry Types
Point
import { Point, toWKT } from '@unrdf/geosparql';
const sf = Point([-122.4194, 37.7749]); // [longitude, latitude]
console.log(toWKT(sf)); // "POINT(-122.4194 37.7749)"LineString
import { LineString } from '@unrdf/geosparql';
const route = LineString([
[-122.4194, 37.7749], // San Francisco
[-122.4089, 37.7849], // North Beach
[-122.4169, 37.7949] // Fisherman's Wharf
]);Polygon
import { Polygon } from '@unrdf/geosparql';
// Rings must be closed (first point = last point)
const zone = Polygon([[
[-122.5, 37.7],
[-122.3, 37.7],
[-122.3, 37.9],
[-122.5, 37.9],
[-122.5, 37.7] // Closed
]]);Spatial Relations
import {
Point,
Polygon,
within,
contains,
intersects,
disjoint,
filter
} from '@unrdf/geosparql';
const point = Point([-122.4, 37.8]);
const polygon = Polygon([[
[-122.5, 37.7],
[-122.3, 37.7],
[-122.3, 37.9],
[-122.5, 37.9],
[-122.5, 37.7]
]]);
// Test relations
within(point, polygon); // true
contains(polygon, point); // true
intersects(point, polygon); // true
disjoint(point, polygon); // false
// Filter geometries
const points = [
Point([-122.4, 37.8]),
Point([-122.35, 37.85]),
Point([-118.2, 34.0])
];
const nearby = filter(points, polygon, 'within');
console.log(nearby.length); // 2Distance Calculations
import {
Point,
distance,
withinDistance,
bearing,
convertDistance
} from '@unrdf/geosparql';
const sf = Point([-122.4194, 37.7749]);
const la = Point([-118.2437, 34.0522]);
// Distance in meters
const dist = distance(sf, la);
console.log(`${(dist / 1000).toFixed(2)} km`); // ~559 km
// Check proximity
const nearby = Point([-122.4189, 37.7750]);
console.log(withinDistance(nearby, sf, 100, 'meters')); // true
// Bearing (degrees)
const bear = bearing(sf.coordinates, la.coordinates);
console.log(`${bear.toFixed(2)}°`); // ~130° (southeast)
// Convert units
const km = convertDistance(dist, 'meters', 'kilometers');
const miles = convertDistance(dist, 'meters', 'miles');Spatial Indexing (R-tree)
import { createSpatialIndex, Point } from '@unrdf/geosparql';
const index = createSpatialIndex();
// Add geometries
index.insert(Point([-122.4, 37.8]), { name: 'Point A' });
index.insert(Point([-122.35, 37.85]), { name: 'Point B' });
index.insert(Point([-118.2, 34.0]), { name: 'Point C' });
// Search by bounding box
const bbox = [-122.5, 37.7, -122.3, 37.9]; // [minLon, minLat, maxLon, maxLat]
const results = index.search(bbox);
console.log(results.length); // 2
// Bulk load (efficient for large datasets)
const entries = Array.from({ length: 10000 }, (_, i) => ({
geometry: Point([-122.4 + i * 0.01, 37.7 + i * 0.01]),
data: { id: i }
}));
index.bulkLoad(entries); // <100ms for 10K geometries
// K-nearest neighbors
import { kNearest } from '@unrdf/geosparql';
const target = Point([-122.38, 37.82]);
const nearest = kNearest(index, target, 5);GeoSPARQL Functions
import {
Point,
Polygon,
geof_distance,
geof_within,
geof_buffer,
geof_area,
geof_length
} from '@unrdf/geosparql';
const point = Point([-122.4194, 37.7749]);
const polygon = Polygon([[
[-122.5, 37.7],
[-122.3, 37.7],
[-122.3, 37.9],
[-122.5, 37.9],
[-122.5, 37.7]
]]);
// geof:distance
geof_distance(point, Point([-118.2437, 34.0522])); // ~559120 meters
// geof:within
geof_within(point, polygon); // false
// geof:buffer (1km radius)
const buffered = geof_buffer(point, 1000);
console.log(buffered.type); // "Polygon"
// geof:area (square meters)
const area = geof_area(polygon);
console.log(`${(area / 1e6).toFixed(2)} km²`);
// geof:length (meters)
const line = LineString([[-122.4, 37.8], [-122.3, 37.9]]);
const length = geof_length(line);GeoSPARQL Engine
import { createGeoSPARQLEngine, Point, Polygon } from '@unrdf/geosparql';
const engine = createGeoSPARQLEngine({
enableIndex: true,
indexMaxEntries: 9
});
// Add geometries with metadata
engine.addGeometry(
Point([-122.4194, 37.7749]),
{ city: 'San Francisco', population: 883305 }
);
engine.addGeometry(
Point([-118.2437, 34.0522]),
{ city: 'Los Angeles', population: 3979576 }
);
// Query by bounding box
const results = engine.query({
bbox: [-123, 37, -122, 38]
});
// Query by spatial relation
const bayArea = Polygon([[
[-122.5, 37.7],
[-122.3, 37.7],
[-122.3, 37.9],
[-122.5, 37.9],
[-122.5, 37.7]
]]);
const withinBayArea = engine.query({
relation: { geometry: bayArea, type: 'within' }
});
// Query by distance
const nearby = engine.query({
distance: {
from: Point([-122.4, 37.8]),
max: 10000, // 10km
unit: 'meters'
}
});
// Execute GeoSPARQL functions
const dist = engine.executeFunction(
'http://www.opengis.net/def/function/geosparql/distance',
Point([-122.4194, 37.7749]),
Point([-118.2437, 34.0522])
);
// Add from WKT
engine.addWKT('POINT(-122.4194 37.7749)', { name: 'SF' });
// Statistics
const stats = engine.getStats();
console.log(stats); // { geometryCount: 3, indexEnabled: true, indexSize: 3 }WKT Support
import { toWKT, fromWKT, Point, LineString } from '@unrdf/geosparql';
// Geometry to WKT
const point = Point([-122.4194, 37.7749]);
console.log(toWKT(point)); // "POINT(-122.4194 37.7749)"
const line = LineString([[-122.4, 37.8], [-122.3, 37.9]]);
console.log(toWKT(line)); // "LINESTRING(-122.4 37.8, -122.3 37.9)"
// WKT to Geometry
const parsed = fromWKT('POINT(-122.4194 37.7749)');
console.log(parsed.type); // "Point"
console.log(parsed.coordinates); // [-122.4194, 37.7749]Coordinate Reference Systems
import { Point, validateCRS, getCRSUri } from '@unrdf/geosparql';
// Supported CRS
const point = Point([-122.4194, 37.7749], 'WGS84');
console.log(point.crs); // "WGS84"
// Get OGC URI
const uri = getCRSUri('CRS84');
console.log(uri); // "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
// Validate CRS
validateCRS('EPSG:4326'); // Valid
validateCRS('INVALID'); // Throws errorPerformance
Targets for production workloads:
| Operation | P95 Target | Typical | |-----------|------------|---------| | Point creation | <0.1ms | ~0.05ms | | Distance calculation | <0.5ms | ~0.2ms | | Spatial relation check | <1ms | ~0.3ms | | R-tree query (10K geometries) | <10ms | ~2ms | | Index build (10K geometries) | <100ms | ~50ms |
// Benchmark distance calculation
const point1 = Point([-122.4194, 37.7749]);
const point2 = Point([-118.2437, 34.0522]);
const start = performance.now();
distance(point1, point2);
const elapsed = performance.now() - start;
console.log(`Elapsed: ${elapsed.toFixed(3)}ms`); // ~0.2msUse Cases
Location-Based Services
// Find all restaurants within 1km
const restaurants = engine.query({
distance: {
from: userLocation,
max: 1000,
unit: 'meters'
}
});Geofencing
// Check if vehicle is within delivery zone
const deliveryZone = Polygon([[...]]);
const vehicleLocation = Point([-122.4, 37.8]);
if (within(vehicleLocation, deliveryZone)) {
console.log('Vehicle in zone');
}Route Planning
// Calculate route length
const route = LineString([
[-122.4194, 37.7749],
[-122.4089, 37.7849],
[-122.4169, 37.7949]
]);
const routeLength = geof_length(route);
console.log(`Route: ${(routeLength / 1000).toFixed(2)} km`);Spatial Analytics
// Find points of interest in region
const region = Polygon([[...]]);
const pois = allPOIs.filter(poi => within(poi.location, region));
// Calculate coverage area
const coverageArea = geof_area(region);
console.log(`Coverage: ${(coverageArea / 1e6).toFixed(2)} km²`);OTEL Observability
import { trace } from '@opentelemetry/api';
// Engine operations emit spans automatically
const engine = createGeoSPARQLEngine();
engine.addGeometry(point, data);
// Emits: geosparql.addGeometry span
const results = engine.query({ bbox: [...] });
// Emits: geosparql.query span with attributes:
// - query.type: "bbox"
// - results.count: 42
const dist = engine.executeFunction(uri, g1, g2);
// Emits: geosparql.executeFunction spanOGC GeoSPARQL Compliance
Implements OGC GeoSPARQL 1.0 standard:
- Geometry Types: Point, LineString, Polygon (GeoJSON/WKT)
- Spatial Relations: within, contains, intersects, touches, disjoint, overlaps
- Functions: distance, buffer, convexHull, envelope, intersection, union, difference, area, length
- CRS: WGS84, CRS84, EPSG:4326
- Topology: DE-9IM model (via Turf.js)
Dependencies
@turf/turf- Geospatial calculations (FOSS)rbush- R-tree spatial indexing (FOSS)@unrdf/oxigraph- SPARQL engine@opentelemetry/api- Observabilityzod- Runtime validation
API Reference
Geometry
Point(coords, crs?)- Create Point geometryLineString(coords, crs?)- Create LineString geometryPolygon(coords, crs?)- Create Polygon geometrygetBBox(geometry)- Get bounding boxtoWKT(geometry)- Convert to Well-Known TextfromWKT(wkt)- Parse Well-Known Text
Distance
distance(g1, g2, unit?)- Calculate distancehaversineDistance(c1, c2, unit?)- Haversine formulawithinDistance(point, center, radius, unit?)- Proximity checkbearing(c1, c2)- Calculate bearingconvertDistance(value, from, to)- Unit conversion
Spatial Relations
within(g1, g2)- g1 within g2contains(g1, g2)- g1 contains g2intersects(g1, g2)- Geometries intersecttouches(g1, g2)- Geometries touchdisjoint(g1, g2)- Geometries don't intersectoverlaps(g1, g2)- Geometries overlapevaluate(g1, g2, relation)- Evaluate named relationfilter(geometries, target, relation)- Filter by relation
Spatial Index
createSpatialIndex(maxEntries?)- Create R-tree indexindex.insert(geometry, data?)- Insert geometryindex.search(bbox)- Search by bounding boxindex.searchGeometry(geometry)- Search by geometryindex.bulkLoad(entries)- Bulk load geometrieskNearest(index, target, k)- K-nearest neighbors
GeoSPARQL Functions
geof_distance(g1, g2, unit?)- Distancegeof_within(g1, g2)- Within testgeof_contains(g1, g2)- Contains testgeof_intersects(g1, g2)- Intersects testgeof_touches(g1, g2)- Touches testgeof_buffer(geometry, distance)- Buffergeof_convexHull(geometry)- Convex hullgeof_envelope(geometry)- Envelopegeof_intersection(g1, g2)- Intersectiongeof_union(g1, g2)- Uniongeof_difference(g1, g2)- Differencegeof_area(geometry)- Areageof_length(geometry)- Length
Engine
createGeoSPARQLEngine(options?)- Create engineengine.addGeometry(geometry, data?)- Add geometryengine.removeGeometry(id)- Remove geometryengine.query(criteria)- Query geometriesengine.executeFunction(uri, ...args)- Execute functionengine.addWKT(wkt, data?)- Add from WKTengine.getGeometry(id)- Get geometry by IDengine.getAllGeometries()- Get all geometriesengine.clear()- Clear allengine.getStats()- Get statistics
License
MIT
Contributing
See CONTRIBUTING.md
Support
- Issues: https://github.com/seanchatmangpt/unrdf/issues
- Docs: https://unrdf.dev/geosparql
