Skip to main content

oxirs_geosparql/
lib.rs

1//! # OxiRS GeoSPARQL
2//!
3//! [![Version](https://img.shields.io/badge/version-0.2.4-blue)](https://github.com/cool-japan/oxirs/releases)
4//! [![docs.rs](https://docs.rs/oxirs-geosparql/badge.svg)](https://docs.rs/oxirs-geosparql)
5//!
6//! **Status**: Production Release (v0.2.4)
7//! **Stability**: Public APIs are stable. Production-ready with comprehensive testing.
8//!
9//! GeoSPARQL implementation for spatial data and queries in RDF/SPARQL.
10//!
11//! This crate provides:
12//! - GeoSPARQL vocabulary and datatypes
13//! - WKT (Well-Known Text) geometry parsing and serialization
14//! - Simple Features topological relations (sfEquals, sfContains, sfIntersects, etc.)
15//! - Geometric operations (buffer, convex hull, intersection, etc.)
16//! - Geometric properties (dimension, SRID, isEmpty, etc.)
17//! - Spatial indexing with R-tree for efficient queries
18//! - **SIMD-accelerated distance calculations** (2-4x speedup)
19//! - **Parallel batch processing** for large datasets
20//!
21//! ## Example
22//!
23//! ```rust
24//! use oxirs_geosparql::geometry::Geometry;
25//! use oxirs_geosparql::functions::simple_features;
26//!
27//! // Parse WKT geometries
28//! let point = Geometry::from_wkt("POINT(1.0 2.0)").expect("should succeed");
29//! let polygon = Geometry::from_wkt("POLYGON((0 0, 4 0, 4 4, 0 4, 0 0))").expect("should succeed");
30//!
31//! // Test spatial relations
32//! let contains = simple_features::sf_contains(&polygon, &point).expect("should succeed");
33//! assert!(contains);
34//! ```
35//!
36//! ## GeoSPARQL Compliance
37//!
38//! This implementation follows the OGC GeoSPARQL 1.0/1.1 specification:
39//! - Core: Geometry classes and properties
40//! - Topology Vocabulary: Simple Features relations
41//! - Geometry Extension: WKT and GML support
42//! - Query Rewrite Extension: Spatial indexing
43//!
44//! ## Features
45//!
46//! - `wkt-support` (default): WKT parsing and serialization
47//! - `geojson-support`: GeoJSON support
48//! - `geos-backend`: Use GEOS library for geometric operations
49//! - `proj-support`: Coordinate transformation support
50//! - `parallel`: Parallel processing for large datasets
51
52#![warn(missing_docs)]
53#![warn(clippy::all)]
54
55pub mod aggregate;
56pub mod analysis;
57/// Spatial clustering algorithms (DBSCAN with Haversine distance).
58pub mod clustering;
59pub mod crs;
60pub mod crs_transform;
61pub mod error;
62pub mod functions;
63pub mod geometry;
64pub mod index;
65pub mod performance;
66pub mod reasoning;
67pub mod sparql_integration;
68pub mod validation;
69pub mod vocabulary;
70
71// Geometry serialization to WKT, GeoJSON, and GML (v1.1.0 round 6)
72pub mod geo_serializer;
73
74/// CRS coordinate transformations: WGS84 ↔ UTM ↔ WebMercator.
75pub mod coordinate_transformer;
76
77// Raster value sampler with NN/bilinear/bicubic interpolation (v1.1.0 round 8)
78pub mod raster_sampler;
79
80// Spatial topology checking (DE-9IM simplified) (v1.1.0 round 9)
81pub mod topology_checker;
82
83// WKT geometry parser and serializer (v1.1.0 round 10)
84pub mod wkt_parser;
85
86/// Spatial grid index for fast bounding-box queries.
87pub mod spatial_index;
88
89// WKT geometry serializer (v1.1.0 round 12)
90pub mod wkt_writer;
91
92// Bounding box (Envelope) operations for spatial queries (v1.1.0 round 13)
93pub mod bounding_box;
94
95// Area calculations for geographic polygons (v1.1.0 round 12)
96pub mod area_calculator;
97
98// Geodesic/Euclidean distance calculations (v1.1.0 round 11)
99pub mod distance_calculator;
100
101/// Geometric intersection detection: point-in-polygon, line–line, polygon overlap,
102/// containment, touches, crosses, segment distance (v1.1.0 round 13)
103pub mod intersection_detector;
104
105/// 2D convex hull computation using the Graham scan algorithm (v1.1.0 round 14).
106pub mod convex_hull;
107
108/// Geometry simplification using Douglas-Peucker and radial distance algorithms (v1.1.0 round 15).
109pub mod simplifier;
110
111/// Coordinate system conversions: WGS84 ↔ Web Mercator, plus Haversine distance (v1.1.0 round 16).
112pub mod coordinate_converter;
113
114// Re-export commonly used types
115pub use aggregate::{
116    aggregate_bounding_box, AggregateBoundingBox, BoundingBoxResult, GEO_AGG_BOUNDING_BOX,
117};
118pub use crs::crs_literal::{
119    encode_crs_wkt_literal, parse_crs_wkt_literal, CrsGeometryTransformer, CrsLiteral, CRS84_URI,
120    GEO_CRS, GEO_WKT_LITERAL,
121};
122pub use crs::osgb36::{coordinate_to_osgb_grid_ref, osgb_grid_ref_to_coordinate, OsgbCoordinate};
123pub use crs::utm::{utm_to_wgs84_batch, wgs84_to_utm_batch, UtmCoordinate, WgsCoordinate};
124pub use crs::{CrsKind, CrsTransformer, GeometryWithCrs};
125pub use error::{GeoSparqlError, Result};
126pub use functions::ogc11::{
127    area_with_unit, area_with_unit_uri, concave_hull, distance_with_unit, distance_with_unit_uri,
128    length_with_unit, length_with_unit_uri, UnitOfMeasure, UOM_PREFIX,
129};
130pub use geometry::geometry3d::{
131    BoundingBox3D, Geometry3DEnum, LineString3D, LinearRing3D, Point3D, Polygon3D,
132};
133pub use geometry::{Crs, Geometry};
134pub use index::SpatialIndex;
135pub use index::{PureRTree, RTreeBBox};
136pub use index::{RtreeEntry, SpatialRtreeIndex};
137pub use performance::BatchProcessor;
138
139/// GeoSPARQL function registry
140///
141/// This registry contains all available GeoSPARQL filter functions (predicates)
142/// and property functions (geometric operations).
143pub struct GeoSparqlRegistry;
144
145impl GeoSparqlRegistry {
146    /// Get all available Simple Features topological relation functions
147    ///
148    /// Returns a list of (function_uri, function_name) tuples
149    pub fn simple_features_functions() -> Vec<(&'static str, &'static str)> {
150        vec![
151            (vocabulary::GEO_SF_EQUALS, "sfEquals"),
152            (vocabulary::GEO_SF_DISJOINT, "sfDisjoint"),
153            (vocabulary::GEO_SF_INTERSECTS, "sfIntersects"),
154            (vocabulary::GEO_SF_TOUCHES, "sfTouches"),
155            (vocabulary::GEO_SF_CROSSES, "sfCrosses"),
156            (vocabulary::GEO_SF_WITHIN, "sfWithin"),
157            (vocabulary::GEO_SF_CONTAINS, "sfContains"),
158            (vocabulary::GEO_SF_OVERLAPS, "sfOverlaps"),
159        ]
160    }
161
162    /// Get all available Egenhofer topological relation functions
163    ///
164    /// Returns a list of (function_uri, function_name) tuples
165    #[cfg(feature = "geos-backend")]
166    pub fn egenhofer_functions() -> Vec<(&'static str, &'static str)> {
167        vec![
168            (vocabulary::GEO_EH_EQUALS, "ehEquals"),
169            (vocabulary::GEO_EH_DISJOINT, "ehDisjoint"),
170            (vocabulary::GEO_EH_MEET, "ehMeet"),
171            (vocabulary::GEO_EH_OVERLAP, "ehOverlap"),
172            (vocabulary::GEO_EH_COVERS, "ehCovers"),
173            (vocabulary::GEO_EH_COVERED_BY, "ehCoveredBy"),
174            (vocabulary::GEO_EH_INSIDE, "ehInside"),
175            (vocabulary::GEO_EH_CONTAINS, "ehContains"),
176        ]
177    }
178
179    /// Get all available RCC8 topological relation functions
180    ///
181    /// Returns a list of (function_uri, function_name) tuples
182    #[cfg(feature = "geos-backend")]
183    pub fn rcc8_functions() -> Vec<(&'static str, &'static str)> {
184        vec![
185            (vocabulary::GEO_RCC8_EQ, "rcc8eq"),
186            (vocabulary::GEO_RCC8_DC, "rcc8dc"),
187            (vocabulary::GEO_RCC8_EC, "rcc8ec"),
188            (vocabulary::GEO_RCC8_PO, "rcc8po"),
189            (vocabulary::GEO_RCC8_TPPI, "rcc8tppi"),
190            (vocabulary::GEO_RCC8_TPP, "rcc8tpp"),
191            (vocabulary::GEO_RCC8_NTPP, "rcc8ntpp"),
192            (vocabulary::GEO_RCC8_NTPPI, "rcc8ntppi"),
193        ]
194    }
195
196    /// Get all available geometric property functions
197    ///
198    /// Returns a list of (function_uri, function_name) tuples
199    pub fn property_functions() -> Vec<(&'static str, &'static str)> {
200        vec![
201            (vocabulary::GEO_DIMENSION, "dimension"),
202            (vocabulary::GEO_COORDINATE_DIMENSION, "coordinateDimension"),
203            (vocabulary::GEO_SPATIAL_DIMENSION, "spatialDimension"),
204            (vocabulary::GEO_IS_EMPTY, "isEmpty"),
205            (vocabulary::GEO_IS_SIMPLE, "isSimple"),
206            (vocabulary::GEO_AS_WKT, "asWKT"),
207        ]
208    }
209
210    /// Get all available geometric operation functions
211    ///
212    /// Returns a list of (function_uri, function_name) tuples
213    #[cfg(feature = "geos-backend")]
214    pub fn operation_functions() -> Vec<(&'static str, &'static str)> {
215        vec![
216            (vocabulary::GEO_BUFFER, "buffer"),
217            (vocabulary::GEO_CONVEX_HULL, "convexHull"),
218            (vocabulary::GEO_INTERSECTION, "intersection"),
219            (vocabulary::GEO_UNION, "union"),
220            (vocabulary::GEO_DIFFERENCE, "difference"),
221            (vocabulary::GEO_SYM_DIFFERENCE, "symDifference"),
222            (vocabulary::GEO_ENVELOPE, "envelope"),
223            (vocabulary::GEO_BOUNDARY, "boundary"),
224        ]
225    }
226
227    /// Get all available distance functions
228    ///
229    /// Returns a list of (function_uri, function_name) tuples
230    pub fn distance_functions() -> Vec<(&'static str, &'static str)> {
231        vec![(vocabulary::GEO_DISTANCE, "distance")]
232    }
233
234    /// Get all GeoSPARQL extension functions (filter functions)
235    ///
236    /// Returns all boolean predicates that can be used in SPARQL FILTER clauses
237    pub fn all_filter_functions() -> Vec<(&'static str, &'static str)> {
238        #[cfg(not(feature = "geos-backend"))]
239        {
240            Self::simple_features_functions()
241        }
242
243        #[cfg(feature = "geos-backend")]
244        {
245            let mut functions = Self::simple_features_functions();
246            functions.extend(Self::egenhofer_functions());
247            functions.extend(Self::rcc8_functions());
248            functions
249        }
250    }
251
252    /// Get all GeoSPARQL property functions
253    ///
254    /// Returns all functions that compute geometric properties or perform operations
255    pub fn all_property_functions() -> Vec<(&'static str, &'static str)> {
256        let mut functions = Self::property_functions();
257        functions.extend(Self::distance_functions());
258
259        #[cfg(feature = "geos-backend")]
260        {
261            functions.extend(Self::operation_functions());
262        }
263
264        functions
265    }
266}
267
268/// Type alias for GeoSPARQL function registry result
269type GeoSparqlFunctions = (
270    Vec<(&'static str, &'static str)>,
271    Vec<(&'static str, &'static str)>,
272);
273
274/// Initialize GeoSPARQL support in a SPARQL engine
275///
276/// This function should be called to register GeoSPARQL functions
277/// with the SPARQL query engine.
278///
279/// # Returns
280///
281/// Returns a tuple of (filter_functions, property_functions) where:
282/// - filter_functions: Boolean predicates for FILTER clauses
283/// - property_functions: Functions that compute properties or perform operations
284pub fn init() -> GeoSparqlFunctions {
285    tracing::info!("Initializing GeoSPARQL support");
286
287    let filter_functions = GeoSparqlRegistry::all_filter_functions();
288    let property_functions = GeoSparqlRegistry::all_property_functions();
289
290    tracing::info!(
291        "Registered {} GeoSPARQL filter functions",
292        filter_functions.len()
293    );
294    tracing::info!(
295        "Registered {} GeoSPARQL property functions",
296        property_functions.len()
297    );
298
299    (filter_functions, property_functions)
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305
306    #[test]
307    fn test_init() {
308        let (filter_functions, property_functions) = init();
309
310        // Check that we have the expected number of functions
311        #[cfg(feature = "geos-backend")]
312        {
313            assert_eq!(filter_functions.len(), 24); // 8 SF + 8 Egenhofer + 8 RCC8
314            assert_eq!(property_functions.len(), 15); // 6 props + 1 distance + 8 ops
315        }
316        #[cfg(not(feature = "geos-backend"))]
317        {
318            assert_eq!(filter_functions.len(), 8); // 8 Simple Features functions
319            assert_eq!(property_functions.len(), 7); // 6 properties + 1 distance
320        }
321    }
322
323    #[test]
324    fn test_simple_features_registry() {
325        let functions = GeoSparqlRegistry::simple_features_functions();
326
327        assert_eq!(functions.len(), 8);
328
329        // Verify some key functions are present
330        assert!(functions.contains(&(vocabulary::GEO_SF_EQUALS, "sfEquals")));
331        assert!(functions.contains(&(vocabulary::GEO_SF_CONTAINS, "sfContains")));
332        assert!(functions.contains(&(vocabulary::GEO_SF_INTERSECTS, "sfIntersects")));
333    }
334
335    #[test]
336    #[cfg(feature = "geos-backend")]
337    fn test_egenhofer_registry() {
338        let functions = GeoSparqlRegistry::egenhofer_functions();
339
340        assert_eq!(functions.len(), 8);
341
342        // Verify some key functions are present
343        assert!(functions.contains(&(vocabulary::GEO_EH_EQUALS, "ehEquals")));
344        assert!(functions.contains(&(vocabulary::GEO_EH_MEET, "ehMeet")));
345        assert!(functions.contains(&(vocabulary::GEO_EH_CONTAINS, "ehContains")));
346    }
347
348    #[test]
349    #[cfg(feature = "geos-backend")]
350    fn test_rcc8_registry() {
351        let functions = GeoSparqlRegistry::rcc8_functions();
352
353        assert_eq!(functions.len(), 8);
354
355        // Verify some key functions are present
356        assert!(functions.contains(&(vocabulary::GEO_RCC8_EQ, "rcc8eq")));
357        assert!(functions.contains(&(vocabulary::GEO_RCC8_DC, "rcc8dc")));
358        assert!(functions.contains(&(vocabulary::GEO_RCC8_PO, "rcc8po")));
359    }
360
361    #[test]
362    fn test_property_functions_registry() {
363        let functions = GeoSparqlRegistry::property_functions();
364
365        assert_eq!(functions.len(), 6);
366
367        // Verify some key properties are present
368        assert!(functions.contains(&(vocabulary::GEO_DIMENSION, "dimension")));
369        assert!(functions.contains(&(vocabulary::GEO_IS_EMPTY, "isEmpty")));
370        assert!(functions.contains(&(vocabulary::GEO_IS_SIMPLE, "isSimple")));
371    }
372
373    #[test]
374    #[cfg(feature = "geos-backend")]
375    fn test_operation_functions_registry() {
376        let functions = GeoSparqlRegistry::operation_functions();
377
378        assert_eq!(functions.len(), 8);
379
380        // Verify some key operations are present
381        assert!(functions.contains(&(vocabulary::GEO_BUFFER, "buffer")));
382        assert!(functions.contains(&(vocabulary::GEO_INTERSECTION, "intersection")));
383        assert!(functions.contains(&(vocabulary::GEO_UNION, "union")));
384    }
385
386    #[test]
387    fn test_distance_functions_registry() {
388        let functions = GeoSparqlRegistry::distance_functions();
389
390        assert_eq!(functions.len(), 1);
391        assert!(functions.contains(&(vocabulary::GEO_DISTANCE, "distance")));
392    }
393
394    #[test]
395    fn test_all_filter_functions() {
396        let functions = GeoSparqlRegistry::all_filter_functions();
397
398        // Should have at least the Simple Features functions
399        assert!(functions.len() >= 8);
400
401        // Verify all Simple Features functions are included
402        assert!(functions.contains(&(vocabulary::GEO_SF_EQUALS, "sfEquals")));
403        assert!(functions.contains(&(vocabulary::GEO_SF_DISJOINT, "sfDisjoint")));
404        assert!(functions.contains(&(vocabulary::GEO_SF_INTERSECTS, "sfIntersects")));
405        assert!(functions.contains(&(vocabulary::GEO_SF_TOUCHES, "sfTouches")));
406        assert!(functions.contains(&(vocabulary::GEO_SF_CROSSES, "sfCrosses")));
407        assert!(functions.contains(&(vocabulary::GEO_SF_WITHIN, "sfWithin")));
408        assert!(functions.contains(&(vocabulary::GEO_SF_CONTAINS, "sfContains")));
409        assert!(functions.contains(&(vocabulary::GEO_SF_OVERLAPS, "sfOverlaps")));
410    }
411
412    #[test]
413    fn test_all_property_functions() {
414        let functions = GeoSparqlRegistry::all_property_functions();
415
416        // Should have at least the basic property functions + distance
417        assert!(functions.len() >= 7);
418
419        // Verify key property functions are included
420        assert!(functions.contains(&(vocabulary::GEO_DIMENSION, "dimension")));
421        assert!(functions.contains(&(vocabulary::GEO_DISTANCE, "distance")));
422    }
423}