From 0829aa5c4276cd592cd4888266a140aa8044a759 Mon Sep 17 00:00:00 2001 From: Lorenz Buehmann Date: Tue, 29 Nov 2022 08:48:39 +0100 Subject: [PATCH] GH-3026: spatial index per graph and kryo serialization --- .../java/org/apache/jena/system/TxnCtl.java | 92 ++++++++ .../fuseki/geosparql/DatasetOperations.java | 4 +- jena-geosparql/deptree.txt | 86 +++++++ jena-geosparql/pom.xml | 114 +++++++-- .../apache/jena/geosparql/InitGeoSPARQL.java | 2 +- .../geosparql/assembler/GeoAssembler.java | 40 +++- .../geosparql/assembler/VocabGeoSPARQL.java | 4 + .../configuration/GeoSPARQLConfig.java | 35 ++- .../configuration/GeoSPARQLOperations.java | 10 +- .../topological/GenericPropertyFunction.java | 16 +- .../geosparql/spatial/SearchEnvelope.java | 30 ++- .../spatial/SpatialIndexFindUtils.java | 179 ++++++++++++++ .../geosparql/spatial/SpatialIndexItem.java | 23 +- .../spatial/SpatialIndexStorage.java | 14 +- .../spatial/index/compat/SpatialIndexIo.java | 65 ++++++ .../v1/SpatialIndexV1.java} | 86 ++++--- .../spatial/index/v2/STRtreePerGraph.java | 140 +++++++++++ .../spatial/index/v2/STRtreeUtils.java | 108 +++++++++ .../spatial/index/v2/SpatialIndex.java | 60 +++++ .../index/v2/SpatialIndexAdapterV1.java | 82 +++++++ .../spatial/index/v2/SpatialIndexIoKryo.java | 193 +++++++++++++++ .../index/v2/SpatialIndexPerGraph.java | 165 +++++++++++++ .../spatial/index/v2/SpatialIndexUtils.java | 212 +++++++++++++++++ .../GenericSpatialPropertyFunction.java | 36 ++- .../spatial/serde/CustomGeometrySerde.java | 221 ++++++++++++++++++ .../serde/CustomSpatialIndexSerde.java | 70 ++++++ .../spatial/serde/GeometrySerdeAdapter.java | 30 +++ .../serde/GeometrySerdeAdapterJtsWkb.java | 54 +++++ .../serde/GeometrySerdeAdapterShapeSerde.java | 55 +++++ .../spatial/serde/JtsKryoRegistrator.java | 89 +++++++ .../geo/topological/CancelQueryTest.java | 7 +- .../GenericPropertyFunctionTest.java | 28 ++- .../AbstractSpatialIndexGraphLookpTest.java | 200 ++++++++++++++++ .../geosparql/spatial/SearchEnvelopeTest.java | 19 +- .../geosparql/spatial/SpatialIndexTest.java | 102 ++++++++ .../spatial/SpatialIndexTestData.java | 45 +++- .../TestSpatialIndexGraphLookupV1.java | 36 +++ .../TestSpatialIndexGraphLookupV2.java | 31 +++ .../GenericSpatialPropertyFunctionTest.java | 8 +- .../cardinal/EastGeomPFTest.java | 21 +- .../cardinal/EastPFTest.java | 17 +- .../cardinal/WestGeomPFTest.java | 18 +- .../cardinal/WestPFTest.java | 17 +- 43 files changed, 2710 insertions(+), 154 deletions(-) create mode 100644 jena-arq/src/main/java/org/apache/jena/system/TxnCtl.java create mode 100644 jena-geosparql/deptree.txt create mode 100644 jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SpatialIndexFindUtils.java create mode 100644 jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/compat/SpatialIndexIo.java rename jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/{SpatialIndex.java => index/v1/SpatialIndexV1.java} (85%) create mode 100644 jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/STRtreePerGraph.java create mode 100644 jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/STRtreeUtils.java create mode 100644 jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndex.java create mode 100644 jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexAdapterV1.java create mode 100644 jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexIoKryo.java create mode 100644 jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexPerGraph.java create mode 100644 jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexUtils.java create mode 100644 jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/CustomGeometrySerde.java create mode 100644 jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/CustomSpatialIndexSerde.java create mode 100644 jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/GeometrySerdeAdapter.java create mode 100644 jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/GeometrySerdeAdapterJtsWkb.java create mode 100644 jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/GeometrySerdeAdapterShapeSerde.java create mode 100644 jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/JtsKryoRegistrator.java create mode 100644 jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/AbstractSpatialIndexGraphLookpTest.java create mode 100644 jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/SpatialIndexTest.java create mode 100644 jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/TestSpatialIndexGraphLookupV1.java create mode 100644 jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/TestSpatialIndexGraphLookupV2.java diff --git a/jena-arq/src/main/java/org/apache/jena/system/TxnCtl.java b/jena-arq/src/main/java/org/apache/jena/system/TxnCtl.java new file mode 100644 index 00000000000..caff6f11822 --- /dev/null +++ b/jena-arq/src/main/java/org/apache/jena/system/TxnCtl.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.system; + +import java.util.Objects; + +import org.apache.jena.query.ReadWrite; +import org.apache.jena.query.TxnType; +import org.apache.jena.sparql.core.Transactional; + +/** + * Txn variant for use with try-with-resources. Allows raising + * checked exceptions in an idiomatic way. Closing the TxnCtl + * instance will by default abort the transaction unless it + * has been manually committed. + *

+ * + * Usage example: + *

+ * public void myMethod() throws IOException {
+ *   try (TxnCtl txn = TxnCtl.begin(dataset, TxnType.WRITE)) {
+ *     // Do work
+ *     // Must manually call commit on success.
+ *     txn.commit();
+ *   }
+ * }
+ * 
+ *
+ * @param txn The transactional.
+ * @param txnType The transaction type.
+ * @return An instance of AutoCloseable that commits the transaction on success.
+ */
+public class TxnCtl
+    implements AutoCloseable
+{
+    private Transactional txn;
+    private boolean b;
+
+    private TxnCtl(Transactional txn, boolean b) {
+        super();
+        this.txn = txn;
+        this.b = b;
+    }
+
+    public static TxnCtl begin(Transactional txn, ReadWrite readWrite) {
+        return begin(txn, TxnType.convert(readWrite));
+    }
+
+    public static TxnCtl begin(Transactional txn, TxnType txnType) {
+        Objects.requireNonNull(txn);
+        Objects.requireNonNull(txnType);
+        boolean b = txn.isInTransaction();
+        if ( b )
+            TxnOp.compatibleWithPromote(txnType, txn);
+        else
+            txn.begin(txnType);
+        return new TxnCtl(txn, b);
+    }
+
+    public void commit() {
+        if ( txn.isInTransaction() ) {
+
+            // May have been explicit commit or abort.
+            txn.commit();
+        }
+    }
+
+    @Override
+    public void close() {
+        if ( !b ) {
+            if ( txn.isInTransaction() )
+                // May have been explicit commit or abort.
+                txn.abort();
+            txn.end();
+        }
+    }
+}
diff --git a/jena-fuseki2/jena-fuseki-geosparql/src/main/java/org/apache/jena/fuseki/geosparql/DatasetOperations.java b/jena-fuseki2/jena-fuseki-geosparql/src/main/java/org/apache/jena/fuseki/geosparql/DatasetOperations.java
index 765f621c5f9..decf66e1e90 100644
--- a/jena-fuseki2/jena-fuseki-geosparql/src/main/java/org/apache/jena/fuseki/geosparql/DatasetOperations.java
+++ b/jena-fuseki2/jena-fuseki-geosparql/src/main/java/org/apache/jena/fuseki/geosparql/DatasetOperations.java
@@ -228,10 +228,10 @@ private static void prepareSpatialExtension(Dataset dataset, ArgsConfig argsConf
         if (!isEmpty) {
             if (argsConfig.getSpatialIndexFile() != null) {
                 File spatialIndexFile = argsConfig.getSpatialIndexFile();
-                GeoSPARQLConfig.setupSpatialIndex(dataset, spatialIndexFile);
+                GeoSPARQLConfig.setupSpatialIndex(dataset, spatialIndexFile.toPath());
             } else if (argsConfig.isTDBFileSetup()) {
                 File spatialIndexFile = new File(argsConfig.getTdbFile(), SPATIAL_INDEX_FILE);
-                GeoSPARQLConfig.setupSpatialIndex(dataset, spatialIndexFile);
+                GeoSPARQLConfig.setupSpatialIndex(dataset, spatialIndexFile.toPath());
             } else {
                 GeoSPARQLConfig.setupSpatialIndex(dataset);
             }
diff --git a/jena-geosparql/deptree.txt b/jena-geosparql/deptree.txt
new file mode 100644
index 00000000000..081422429a9
--- /dev/null
+++ b/jena-geosparql/deptree.txt
@@ -0,0 +1,86 @@
+[INFO] Scanning for projects...
+[INFO] 
+[INFO] -------------------< org.apache.jena:jena-geosparql >-------------------
+[INFO] Building Apache Jena - GeoSPARQL Engine 5.4.0-SNAPSHOT
+[INFO] --------------------------------[ jar ]---------------------------------
+[INFO] 
+[INFO] --- maven-dependency-plugin:3.7.1:tree (default-cli) @ jena-geosparql ---
+[INFO] org.apache.jena:jena-geosparql:jar:5.4.0-SNAPSHOT
+[INFO] +- io.github.galbiston:expiring-map:jar:1.0.2:compile
+[INFO] |  \- org.slf4j:slf4j-api:jar:2.0.16:compile
+[INFO] +- javax.xml.bind:jaxb-api:jar:2.3.1:compile
+[INFO] |  \- javax.activation:javax.activation-api:jar:1.2.0:compile
+[INFO] +- org.apache.jena:jena-arq:jar:5.4.0-SNAPSHOT:compile
+[INFO] |  +- org.apache.jena:jena-core:jar:5.4.0-SNAPSHOT:compile
+[INFO] |  |  +- org.apache.jena:jena-base:jar:5.4.0-SNAPSHOT:compile
+[INFO] |  |  |  +- org.apache.commons:commons-csv:jar:1.13.0:compile
+[INFO] |  |  |  +- commons-io:commons-io:jar:2.18.0:compile
+[INFO] |  |  |  +- commons-codec:commons-codec:jar:1.18.0:compile
+[INFO] |  |  |  +- org.apache.commons:commons-compress:jar:1.27.1:compile
+[INFO] |  |  |  \- com.github.andrewoma.dexx:collection:jar:0.7:compile
+[INFO] |  |  +- org.apache.jena:jena-iri3986:jar:5.4.0-SNAPSHOT:compile
+[INFO] |  |  +- org.apache.jena:jena-iri:jar:5.4.0-SNAPSHOT:compile
+[INFO] |  |  \- org.roaringbitmap:RoaringBitmap:jar:1.3.0:compile
+[INFO] |  +- com.google.code.gson:gson:jar:2.12.1:compile
+[INFO] |  |  \- com.google.errorprone:error_prone_annotations:jar:2.36.0:compile
+[INFO] |  +- org.slf4j:jcl-over-slf4j:jar:2.0.16:compile
+[INFO] |  +- com.apicatalog:titanium-json-ld:jar:1.5.0:compile
+[INFO] |  +- org.glassfish:jakarta.json:jar:2.0.1:compile
+[INFO] |  +- com.google.protobuf:protobuf-java:jar:4.29.3:compile
+[INFO] |  +- org.apache.thrift:libthrift:jar:0.21.0:compile
+[INFO] |  \- org.apache.commons:commons-lang3:jar:3.17.0:compile
+[INFO] +- org.apache.sis.core:sis-referencing:jar:1.4:compile
+[INFO] |  +- jakarta.xml.bind:jakarta.xml.bind-api:jar:4.0.2:compile
+[INFO] |  |  \- jakarta.activation:jakarta.activation-api:jar:2.1.3:compile
+[INFO] |  \- org.apache.sis.core:sis-metadata:jar:1.4:compile
+[INFO] |     \- org.apache.sis.core:sis-utility:jar:1.4:compile
+[INFO] |        +- org.opengis:geoapi:jar:3.0.2:compile
+[INFO] |        \- javax.measure:unit-api:jar:2.1.3:compile
+[INFO] +- org.slf4j:jul-to-slf4j:jar:2.0.16:compile
+[INFO] +- org.locationtech.jts:jts-core:jar:1.20.0:compile
+[INFO] +- org.locationtech.jts.io:jts-io-common:jar:1.20.0:compile
+[INFO] |  \- com.googlecode.json-simple:json-simple:jar:1.1.1:compile
+[INFO] +- org.jdom:jdom2:jar:2.0.6.1:compile
+[INFO] +- org.apache.commons:commons-collections4:jar:4.4:compile
+[INFO] +- org.apache.sedona:sedona-common:jar:1.6.0:compile
+[INFO] |  +- org.apache.commons:commons-math3:jar:3.6.1:compile
+[INFO] |  +- org.wololo:jts2geojson:jar:0.16.1:compile
+[INFO] |  |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.12.2:compile
+[INFO] |  |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.12.2:compile
+[INFO] |  |  \- com.fasterxml.jackson.core:jackson-annotations:jar:2.12.2:compile
+[INFO] |  +- org.locationtech.spatial4j:spatial4j:jar:0.8:compile
+[INFO] |  +- com.google.geometry:s2-geometry:jar:2.0.0:compile
+[INFO] |  |  \- com.google.guava:guava:jar:33.4.0-jre:compile
+[INFO] |  |     +- com.google.guava:failureaccess:jar:1.0.2:compile
+[INFO] |  |     +- com.google.guava:listenablefuture:jar:9999.0-empty-to-avoid-conflict-with-guava:compile
+[INFO] |  |     +- com.google.code.findbugs:jsr305:jar:3.0.2:compile
+[INFO] |  |     \- com.google.j2objc:j2objc-annotations:jar:3.0.0:compile
+[INFO] |  +- com.uber:h3:jar:4.1.1:compile
+[INFO] |  +- net.sf.geographiclib:GeographicLib-Java:jar:1.52:compile
+[INFO] |  \- com.github.ben-manes.caffeine:caffeine:jar:3.2.0:compile
+[INFO] |     \- org.jspecify:jspecify:jar:1.0.0:compile
+[INFO] +- com.esotericsoftware:kryo:jar:4.0.2:compile
+[INFO] |  +- com.esotericsoftware:reflectasm:jar:1.11.3:compile
+[INFO] |  +- com.esotericsoftware:minlog:jar:1.3.0:compile
+[INFO] |  \- org.objenesis:objenesis:jar:2.5.1:compile
+[INFO] +- org.ow2.asm:asm:jar:8.0.1:compile
+[INFO] +- org.apache.sis.non-free:sis-embedded-data:jar:1.4:test
+[INFO] |  +- org.apache.derby:derby:jar:10.15.2.0:test
+[INFO] |  |  \- org.apache.derby:derbyshared:jar:10.15.2.0:test
+[INFO] |  \- org.apache.derby:derbytools:jar:10.15.2.0:test
+[INFO] +- org.junit.vintage:junit-vintage-engine:jar:5.11.4:test
+[INFO] |  +- org.junit.platform:junit-platform-engine:jar:1.11.4:test
+[INFO] |  |  +- org.opentest4j:opentest4j:jar:1.3.0:test
+[INFO] |  |  \- org.junit.platform:junit-platform-commons:jar:1.11.4:test
+[INFO] |  +- junit:junit:jar:4.13.2:test
+[INFO] |  |  \- org.hamcrest:hamcrest-core:jar:1.3:test
+[INFO] |  \- org.apiguardian:apiguardian-api:jar:1.1.2:test
+[INFO] \- org.apache.logging.log4j:log4j-slf4j2-impl:jar:2.24.3:test
+[INFO]    +- org.apache.logging.log4j:log4j-api:jar:2.24.3:test (optional)
+[INFO]    \- org.apache.logging.log4j:log4j-core:jar:2.24.3:test (optional)
+[INFO] ------------------------------------------------------------------------
+[INFO] BUILD SUCCESS
+[INFO] ------------------------------------------------------------------------
+[INFO] Total time:  0.634 s
+[INFO] Finished at: 2025-02-26T13:11:11+01:00
+[INFO] ------------------------------------------------------------------------
diff --git a/jena-geosparql/pom.xml b/jena-geosparql/pom.xml
index b97bd0ad61b..b2ffe2c2ed3 100644
--- a/jena-geosparql/pom.xml
+++ b/jena-geosparql/pom.xml
@@ -34,6 +34,16 @@
 
   
 
+    
+      io.github.galbiston
+      expiring-map
+    
+
+    
+      javax.xml.bind
+      jaxb-api
+    
+
     
       org.apache.jena
       jena-arq
@@ -44,18 +54,6 @@
       org.apache.sis.core
       sis-referencing
     
-    
-    
-    
-	
-      javax.xml.bind
-      jaxb-api
-    
 
     
       org.slf4j
@@ -82,6 +80,90 @@
       commons-collections4
     
 
+    
+
+
+
+   
+      org.apache.sedona
+      sedona-common
+      1.6.0
+      
+        
+          org.apache.spark
+          spark-core_2.12
+        
+        
+          org.apache.hadoop
+          hadoop-minicluster
+        
+        
+          org.apache.hadoop
+          hadoop-client
+        
+        
+          org.geotools
+          gt-geotiff
+        
+        
+          org.geotools
+          gt-coverage
+        
+      
+    
+
+    
+    
+
+    
+      com.esotericsoftware
+      kryo
+      4.0.2
+    
+
+    
+
+
     
     
       org.apache.sis.non-free
@@ -102,6 +184,8 @@
       test
     
 
+
+
   
 
   
@@ -133,11 +217,11 @@
       
         org.apache.maven.plugins
         maven-source-plugin
-         
+        
           
-            attach-sources-test 
+            attach-sources-test
             
-              test-jar-no-fork 
+              test-jar-no-fork
             
           
         
diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/InitGeoSPARQL.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/InitGeoSPARQL.java
index a0ca84639c6..e32482a1a6e 100644
--- a/jena-geosparql/src/main/java/org/apache/jena/geosparql/InitGeoSPARQL.java
+++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/InitGeoSPARQL.java
@@ -42,7 +42,7 @@ public static void init() {
         if ( initialized )
             return ;
         synchronized (initLock) {
-            if ( initialized ) {
+            if ( initialized || System.getProperty("jena.geosparql.skip", "false").equalsIgnoreCase("true") ) {
                 JenaSystem.logLifecycle("InitGeoSPARQL - skip") ;
                 return ;
             }
diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/assembler/GeoAssembler.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/assembler/GeoAssembler.java
index 5e54e6ad95c..49bfb6a5c09 100644
--- a/jena-geosparql/src/main/java/org/apache/jena/geosparql/assembler/GeoAssembler.java
+++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/assembler/GeoAssembler.java
@@ -18,7 +18,16 @@
 
 package org.apache.jena.geosparql.assembler;
 
-import static org.apache.jena.geosparql.assembler.VocabGeoSPARQL.*;
+import static org.apache.jena.geosparql.assembler.VocabGeoSPARQL.pApplyDefaultGeometry;
+import static org.apache.jena.geosparql.assembler.VocabGeoSPARQL.pDataset;
+import static org.apache.jena.geosparql.assembler.VocabGeoSPARQL.pIndexEnabled;
+import static org.apache.jena.geosparql.assembler.VocabGeoSPARQL.pIndexExpiries;
+import static org.apache.jena.geosparql.assembler.VocabGeoSPARQL.pIndexSizes;
+import static org.apache.jena.geosparql.assembler.VocabGeoSPARQL.pInference;
+import static org.apache.jena.geosparql.assembler.VocabGeoSPARQL.pQueryRewrite;
+import static org.apache.jena.geosparql.assembler.VocabGeoSPARQL.pSpatialIndexFile;
+import static org.apache.jena.geosparql.assembler.VocabGeoSPARQL.pSpatialIndexPerGraph;
+import static org.apache.jena.geosparql.assembler.VocabGeoSPARQL.pSrsUri;
 import static org.apache.jena.sparql.util.graph.GraphUtils.getBooleanValue;
 
 import java.io.IOException;
@@ -34,6 +43,7 @@
 import org.apache.jena.geosparql.configuration.GeoSPARQLOperations;
 import org.apache.jena.geosparql.configuration.SrsException;
 import org.apache.jena.geosparql.spatial.SpatialIndexException;
+import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexUtils;
 import org.apache.jena.graph.Graph;
 import org.apache.jena.graph.Node;
 import org.apache.jena.query.Dataset;
@@ -117,9 +127,21 @@ public DatasetGraph createDataset(Assembler a, Resource root) {
         if (root.hasProperty(pSpatialIndexFile) )
             spatialIndexFilename = GraphUtils.getStringValue(root, pSpatialIndexFile);
 
-        // ---- Build
+        // spatial index per named graph option
+        boolean spatialIndexPerGraph = false;
+        if (root.hasProperty(pSpatialIndexPerGraph) )
+            spatialIndexPerGraph = getBooleanValue(root, pSpatialIndexPerGraph);
+
+        // SRS URI
+        String srsURI = null;
+        if (root.hasProperty(pSrsUri) )
+            srsURI = GraphUtils.getStringValue(root, pSrsUri);
 
+
+        // ---- Build
         Dataset dataset = DatasetFactory.wrap(base);
+        dataset.getContext().set(SpatialIndexUtils.symSpatialIndexPerGraph, spatialIndexPerGraph);
+        dataset.getContext().set(SpatialIndexUtils.symSrsUri, srsURI);
 
         // Conversion of data. Startup-only.
         // needed for w3c:geo/wgs84_pos#lat/log.
@@ -142,13 +164,13 @@ public DatasetGraph createDataset(Assembler a, Resource root) {
         //Setup GeoSPARQL
         if (indexEnabled) {
             GeoSPARQLConfig.setupMemoryIndex(indexSizes.get(0), indexSizes.get(1), indexSizes.get(2),
-                                             (long)indexExpiries.get(0), (long)indexExpiries.get(1), (long)indexExpiries.get(2),
-                                             queryRewrite);
+                    (long)indexExpiries.get(0), (long)indexExpiries.get(1), (long)indexExpiries.get(2),
+                    queryRewrite);
         } else {
             GeoSPARQLConfig.setupNoIndex(queryRewrite);
         }
 
-        prepareSpatialExtension(dataset, spatialIndexFilename);
+        prepareSpatialExtension(dataset, spatialIndexFilename, spatialIndexPerGraph);
         return base;
     }
 
@@ -165,8 +187,8 @@ private static List getListInteger(Resource r, Property p, int len) {
         return integerList;
     }
 
-    private static void prepareSpatialExtension(Dataset dataset, String spatialIndex){
-        boolean isEmpty = dataset.calculateRead(()->dataset.isEmpty());
+    private static void prepareSpatialExtension(Dataset dataset, String spatialIndex, boolean spatialIndexPerGraph){
+        boolean isEmpty = dataset.calculateRead(dataset::isEmpty);
         if ( isEmpty && spatialIndex != null ) {
             LOG.warn("Dataset empty. Spatial Index not constructed. Server will require restarting after adding data and any updates to build Spatial Index.");
             return;
@@ -185,12 +207,12 @@ private static void prepareSpatialExtension(Dataset dataset, String spatialIndex
             // file given but empty -> compute and serialize index
             Path spatialIndexPath = Path.of(spatialIndex);
             if ( ! Files.exists(spatialIndexPath) || Files.size(spatialIndexPath) == 0 ) {
-                GeoSPARQLConfig.setupSpatialIndex(dataset, spatialIndexPath.toFile());
+                GeoSPARQLConfig.setupSpatialIndex(dataset, spatialIndexPath, spatialIndexPerGraph);
                 return;
             }
 
             // load and setup the precomputed index
-            GeoSPARQLConfig.setupPrecomputedSpatialIndex(dataset, spatialIndexPath.toFile());
+            GeoSPARQLConfig.setupPrecomputedSpatialIndex(dataset, spatialIndexPath);
         }
         catch (SrsException ex) {
             // Data but no spatial data.
diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/assembler/VocabGeoSPARQL.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/assembler/VocabGeoSPARQL.java
index 49dafdd635b..5156ea2c28d 100644
--- a/jena-geosparql/src/main/java/org/apache/jena/geosparql/assembler/VocabGeoSPARQL.java
+++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/assembler/VocabGeoSPARQL.java
@@ -73,6 +73,10 @@ private static Property property(String shortName) {
     // "File to load or store the spatial index. Default to " + SPATIAL_INDEX_FILE + " in TDB folder if using TDB and not set. Otherwise spatial index is not stored.
     public static final Property pSpatialIndexFile = property("spatialIndexFile");
 
+    public static final Property pSpatialIndexPerGraph = property("spatialIndexPerGraph");
+
+    public static final Property pSrsUri = property("srsUri");
+
     // Dataset
     public static final Property pDataset = property("dataset");
 }
diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/configuration/GeoSPARQLConfig.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/configuration/GeoSPARQLConfig.java
index 759e5f11f24..dd42e483ad2 100644
--- a/jena-geosparql/src/main/java/org/apache/jena/geosparql/configuration/GeoSPARQLConfig.java
+++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/configuration/GeoSPARQLConfig.java
@@ -17,11 +17,17 @@
  */
 package org.apache.jena.geosparql.configuration;
 
-import java.io.File;
+import java.nio.file.Path;
 
 import org.apache.jena.geosparql.geof.topological.RelateFF;
 import org.apache.jena.geosparql.implementation.datatype.GeometryDatatype;
-import org.apache.jena.geosparql.implementation.function_registration.*;
+import org.apache.jena.geosparql.implementation.function_registration.Egenhofer;
+import org.apache.jena.geosparql.implementation.function_registration.GeometryProperty;
+import org.apache.jena.geosparql.implementation.function_registration.NonTopological;
+import org.apache.jena.geosparql.implementation.function_registration.RCC8;
+import org.apache.jena.geosparql.implementation.function_registration.Relate;
+import org.apache.jena.geosparql.implementation.function_registration.SimpleFeatures;
+import org.apache.jena.geosparql.implementation.function_registration.Spatial;
 import org.apache.jena.geosparql.implementation.index.GeometryLiteralIndex;
 import org.apache.jena.geosparql.implementation.index.GeometryTransformIndex;
 import org.apache.jena.geosparql.implementation.index.IndexConfiguration;
@@ -29,8 +35,11 @@
 import org.apache.jena.geosparql.implementation.index.QueryRewriteIndex;
 import org.apache.jena.geosparql.implementation.registry.SRSRegistry;
 import org.apache.jena.geosparql.implementation.vocabulary.Geo;
-import org.apache.jena.geosparql.spatial.SpatialIndex;
 import org.apache.jena.geosparql.spatial.SpatialIndexException;
+import org.apache.jena.geosparql.spatial.index.compat.SpatialIndexIo;
+import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex;
+import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexIoKryo;
+import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexUtils;
 import org.apache.jena.query.Dataset;
 import org.apache.jena.sparql.function.FunctionRegistry;
 import org.apache.jena.sparql.pfunction.PropertyFunctionRegistry;
@@ -249,7 +258,7 @@ public static final void setupQueryRewriteIndex(Dataset dataset, String queryRew
      * @throws SpatialIndexException
      */
     public static final void setupSpatialIndex(Dataset dataset) throws SpatialIndexException {
-        SpatialIndex.buildSpatialIndex(dataset);
+        SpatialIndexUtils.buildSpatialIndex(dataset.asDatasetGraph());
     }
 
     /**
@@ -260,9 +269,9 @@ public static final void setupSpatialIndex(Dataset dataset) throws SpatialIndexE
      * @param spatialIndexFile the file containing the serialized spatial index
      * @throws SpatialIndexException
      */
-    public static final void setupPrecomputedSpatialIndex(Dataset dataset, File spatialIndexFile) throws SpatialIndexException {
-        SpatialIndex si = SpatialIndex.load(spatialIndexFile);
-        SpatialIndex.setSpatialIndex(dataset, si);
+    public static final void setupPrecomputedSpatialIndex(Dataset dataset, Path spatialIndexFile) throws SpatialIndexException {
+        SpatialIndex si = SpatialIndexIo.load(spatialIndexFile);
+        SpatialIndexUtils.setSpatialIndex(dataset, si);
     }
 
     /**
@@ -274,8 +283,12 @@ public static final void setupPrecomputedSpatialIndex(Dataset dataset, File spat
      * @param spatialIndexFile
      * @throws SpatialIndexException
      */
-    public static final void setupSpatialIndex(Dataset dataset, File spatialIndexFile) throws SpatialIndexException {
-        SpatialIndex.buildSpatialIndex(dataset, spatialIndexFile);
+    public static final void setupSpatialIndex(Dataset dataset, Path spatialIndexFile) throws SpatialIndexException {
+        SpatialIndexIoKryo.buildSpatialIndex(dataset, spatialIndexFile);
+    }
+
+    public static final void setupSpatialIndex(Dataset dataset, Path spatialIndexFile, boolean spatialIndexPerGraph) throws SpatialIndexException {
+        SpatialIndexIoKryo.buildSpatialIndex(dataset, spatialIndexFile, spatialIndexPerGraph);
     }
 
     /**
@@ -287,8 +300,8 @@ public static final void setupSpatialIndex(Dataset dataset, File spatialIndexFil
      * @param spatialIndexFile
      * @throws SpatialIndexException
      */
-    public static final void setupSpatialIndex(Dataset dataset, String srsURI, File spatialIndexFile) throws SpatialIndexException {
-        SpatialIndex.buildSpatialIndex(dataset, srsURI, spatialIndexFile);
+    public static final void setupSpatialIndex(Dataset dataset, String srsURI, Path spatialIndexFile) throws SpatialIndexException {
+        SpatialIndexIoKryo.buildSpatialIndex(dataset, srsURI, spatialIndexFile);
     }
 
     /**
diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/configuration/GeoSPARQLOperations.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/configuration/GeoSPARQLOperations.java
index 5ecce51d424..8046f8ad568 100644
--- a/jena-geosparql/src/main/java/org/apache/jena/geosparql/configuration/GeoSPARQLOperations.java
+++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/configuration/GeoSPARQLOperations.java
@@ -17,6 +17,8 @@
  */
 package org.apache.jena.geosparql.configuration;
 
+import static org.apache.jena.geosparql.configuration.GeoSPARQLConfig.DECIMAL_PLACES_PRECISION;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -32,9 +34,9 @@
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.UUID;
+
 import org.apache.jena.datatypes.DatatypeFormatException;
 import org.apache.jena.datatypes.RDFDatatype;
-import static org.apache.jena.geosparql.configuration.GeoSPARQLConfig.DECIMAL_PLACES_PRECISION;
 import org.apache.jena.geosparql.implementation.GeometryWrapper;
 import org.apache.jena.geosparql.implementation.datatype.GMLDatatype;
 import org.apache.jena.geosparql.implementation.datatype.GeometryDatatype;
@@ -44,6 +46,7 @@
 import org.apache.jena.geosparql.implementation.vocabulary.GeoSPARQL_URI;
 import org.apache.jena.geosparql.implementation.vocabulary.SpatialExtension;
 import org.apache.jena.geosparql.spatial.ConvertLatLon;
+import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexUtils;
 import org.apache.jena.query.Dataset;
 import org.apache.jena.query.DatasetFactory;
 import org.apache.jena.query.ReadWrite;
@@ -501,6 +504,11 @@ public static final boolean validateGeometryLiteral(Model model) {
      * @return SRS URI
      */
     public static final String findModeSRS(Dataset dataset) throws SrsException {
+        // return SRS if set via assembler config
+        if (dataset.getContext().isDefined(SpatialIndexUtils.symSrsUri)) {
+            return dataset.getContext().getAsString(SpatialIndexUtils.symSrsUri);
+        }
+
         LOGGER.info("Find Mode SRS - Started");
         ModeSRS modeSRS = new ModeSRS();
         //Default Model
diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/geo/topological/GenericPropertyFunction.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/geo/topological/GenericPropertyFunction.java
index e389a681c60..0dfd48c8d17 100644
--- a/jena-geosparql/src/main/java/org/apache/jena/geosparql/geo/topological/GenericPropertyFunction.java
+++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/geo/topological/GenericPropertyFunction.java
@@ -18,7 +18,6 @@
 package org.apache.jena.geosparql.geo.topological;
 
 import java.util.Collection;
-import java.util.HashSet;
 import java.util.List;
 
 import org.apache.jena.atlas.iterator.Iter;
@@ -27,12 +26,12 @@
 import org.apache.jena.geosparql.implementation.index.QueryRewriteIndex;
 import org.apache.jena.geosparql.implementation.vocabulary.Geo;
 import org.apache.jena.geosparql.implementation.vocabulary.SpatialExtension;
-import org.apache.jena.geosparql.spatial.SpatialIndex;
 import org.apache.jena.geosparql.spatial.SpatialIndexException;
+import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex;
+import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexUtils;
 import org.apache.jena.graph.Graph;
 import org.apache.jena.graph.Node;
 import org.apache.jena.graph.Triple;
-import org.apache.jena.rdf.model.Resource;
 import org.apache.jena.sparql.core.Var;
 import org.apache.jena.sparql.engine.ExecutionContext;
 import org.apache.jena.sparql.engine.QueryIterator;
@@ -155,7 +154,7 @@ private QueryIterator oneBound(Binding binding, Node subject, Node predicate, No
             }
         }
 
-        boolean isSpatialIndex = SpatialIndex.isDefined(execCxt);
+        boolean isSpatialIndex = SpatialIndexUtils.isDefined(execCxt);
         QueryIterator result;
         if (!isSpatialIndex || filterFunction.isDisjoint() || filterFunction.isDisconnected()) {
             //Disjointed so retrieve all cases.
@@ -231,15 +230,16 @@ private QueryIterator findIndex(Graph graph, Node boundNode, Node unboundNode, B
             Node geometryLiteral = boundGeometryLiteral.getGeometryLiteral();
 
             //Perform the search of the Spatial Index of the Dataset.
-            SpatialIndex spatialIndex = SpatialIndex.retrieve(execCxt);
+            SpatialIndex spatialIndex = SpatialIndexUtils.retrieve(execCxt);
             GeometryWrapper geom = GeometryWrapper.extract(geometryLiteral);
             GeometryWrapper transformedGeom = geom.transform(spatialIndex.getSrsInfo());
             Envelope searchEnvelope = transformedGeom.getEnvelope();
-            HashSet features = spatialIndex.query(searchEnvelope);
+            Node graphName = SpatialIndexUtils.unwrapGraphName(graph);
+            Collection features = spatialIndex.query(searchEnvelope, graphName);
 
             // Check each of the Features that match the search.
             QueryIterator featuresIter = QueryIterPlainWrapper.create(
-                    Iter.map(features.iterator(), feature -> BindingFactory.binding(binding, unboundVar, feature.asNode())),
+                    Iter.map(features.iterator(), feature -> BindingFactory.binding(binding, unboundVar, feature)),
                     execCxt);
 
             QueryIterator queryIterator = QueryIter.flatMap(featuresIter,
@@ -257,6 +257,7 @@ private QueryIterator findIndex(Graph graph, Node boundNode, Node unboundNode, B
         }
     }
 
+    // FIXME Same feature with different geometries in separated graphs might break with old logic
     private QueryIterator findByFeature(Graph graph, Binding binding, Binding featureBinding,
             boolean isSubjectBound, Node boundNode, Node predicate, Var unboundVar,
             ExecutionContext execCxt, Collection assertedNodes) {
@@ -334,3 +335,4 @@ public Boolean testFilterFunction(Node subjectGeometryLiteral, Node objectGeomet
         return filterFunction.exec(subjectGeometryLiteral, objectGeometryLiteral);
     }
 }
+
diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SearchEnvelope.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SearchEnvelope.java
index 7ab0ea98796..1628c43be3c 100644
--- a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SearchEnvelope.java
+++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SearchEnvelope.java
@@ -18,14 +18,17 @@
 package org.apache.jena.geosparql.spatial;
 
 import java.lang.invoke.MethodHandles;
-import java.util.HashSet;
+import java.util.Collection;
 import java.util.Objects;
+
 import org.apache.jena.geosparql.implementation.GeometryWrapper;
 import org.apache.jena.geosparql.implementation.SRSInfo;
 import org.apache.jena.geosparql.implementation.UnitsOfMeasure;
 import org.apache.jena.geosparql.implementation.great_circle.GreatCirclePointDistance;
 import org.apache.jena.geosparql.implementation.great_circle.LatLonPoint;
-import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex;
+import org.apache.jena.graph.Node;
+import org.apache.jena.sparql.core.Quad;
 import org.apache.jena.sparql.expr.ExprEvalException;
 import org.locationtech.jts.geom.Envelope;
 import org.opengis.geometry.MismatchedDimensionException;
@@ -107,11 +110,28 @@ public SRSInfo getCrsInfo() {
         return srsInfo;
     }
 
-    public HashSet check(SpatialIndex spatialIndex) {
-        HashSet features = spatialIndex.query(mainEnvelope);
+
+//    public Collection check(SpatialIndex spatialIndex) {
+//        Collection features = spatialIndex.query(mainEnvelope);
+//
+//        if (wrapEnvelope != null) {
+//            Collection wrapFeatures = spatialIndex.query(wrapEnvelope);
+//            features.addAll(wrapFeatures);
+//        }
+//        return features;
+//    }
+
+    // Check default graph only
+    public Collection check(SpatialIndex spatialIndex) {
+        return check(spatialIndex, Quad.defaultGraphIRI);
+    }
+
+    // Check within a single graph; null for default graph.
+    public Collection check(SpatialIndex spatialIndex, Node graph) {
+        Collection features = spatialIndex.query(mainEnvelope, graph);
 
         if (wrapEnvelope != null) {
-            HashSet wrapFeatures = spatialIndex.query(wrapEnvelope);
+            Collection wrapFeatures = spatialIndex.query(wrapEnvelope, graph);
             features.addAll(wrapFeatures);
         }
         return features;
diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SpatialIndexFindUtils.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SpatialIndexFindUtils.java
new file mode 100644
index 00000000000..3246fd92bff
--- /dev/null
+++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SpatialIndexFindUtils.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jena.geosparql.spatial;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Iterator;
+
+import org.apache.jena.atlas.iterator.Iter;
+import org.apache.jena.atlas.iterator.IteratorCloseable;
+import org.apache.jena.geosparql.implementation.GeometryWrapper;
+import org.apache.jena.geosparql.implementation.vocabulary.Geo;
+import org.apache.jena.geosparql.implementation.vocabulary.SpatialExtension;
+import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.system.G;
+import org.locationtech.jts.geom.Envelope;
+import org.opengis.geometry.MismatchedDimensionException;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.util.FactoryException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SpatialIndexFindUtils {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+    /**
+     * Find Spatial Index Items from all graphs in Dataset.
+ * + * @param datasetGraph + * @param srsURI + * @return SpatialIndexItems found. + * @throws SpatialIndexException + */ + public static IteratorCloseable findSpatialIndexItems(DatasetGraph datasetGraph, String srsURI) { + Graph defaultGraph = datasetGraph.getDefaultGraph(); + IteratorCloseable itemsIter = findSpatialIndexItems(defaultGraph, srsURI); + try { + //Named Models + Iterator graphNodeIt = datasetGraph.listGraphNodes(); + Iterator namedGraphItemsIt = Iter.iter(graphNodeIt).flatMap(graphNode -> { + Graph namedGraph = datasetGraph.getGraph(graphNode); + IteratorCloseable graphItems = findSpatialIndexItems(namedGraph, srsURI); + return graphItems; + }); + itemsIter = Iter.iter(itemsIter).append(namedGraphItemsIt); + } catch(Throwable t) { + t.addSuppressed(new RuntimeException("Failure during findSpatialIndexItems.", t)); + Iter.close(itemsIter); + throw t; + } + return itemsIter; + } + + /** + * Find items from the Model transformed to the SRS URI. + * + * @param graph + * @param srsURI + * @return Items found in the Model in the SRS URI. + * @throws SpatialIndexException + */ + public static final IteratorCloseable findSpatialIndexItems(Graph graph, String srsURI) { + IteratorCloseable result; + // Only add one set of statements as a converted dataset will duplicate the same info. + if (graph.contains(null, Geo.HAS_GEOMETRY_NODE, null)) { + LOGGER.info("Feature-hasGeometry-Geometry statements found."); + if (graph.contains(null, SpatialExtension.GEO_LAT_NODE, null)) { + LOGGER.warn("Lat/Lon Geo predicates also found but will not be added to index."); + } + result = findGeometryIndexItems(graph, srsURI); + } else if (graph.contains(null, SpatialExtension.GEO_LAT_NODE, null)) { + LOGGER.info("Geo predicate statements found."); + result = findGeoPredicateIndexItems(graph, srsURI); + } else { + result = Iter.empty(); + } + return result; + } + + /** + * + * @param graph + * @param srsURI + * @return SpatialIndexItem items prepared for adding to SpatialIndex. + * @throws SpatialIndexException + */ + public static IteratorCloseable findGeometryIndexItems(Graph graph, String srsURI) { + Iterator stmtIter = graph.find(null, Geo.HAS_GEOMETRY_NODE, null); + IteratorCloseable result = Iter.iter(stmtIter).flatMap(stmt -> { + Node feature = stmt.getSubject(); + Node geometry = stmt.getObject(); + + Iterator nodeIter = G.iterSP(graph, geometry, Geo.HAS_SERIALIZATION_NODE); + try { + if (!nodeIter.hasNext()) { + Iter.close(nodeIter); + + Iterator wktNodeIter = G.iterSP(graph, geometry, Geo.AS_WKT_NODE); + nodeIter = wktNodeIter; + + Iterator gmlNodeIter = G.iterSP(graph, geometry, Geo.AS_GML_NODE); + nodeIter = Iter.append(wktNodeIter, gmlNodeIter); + } + } catch (Throwable t) { + t.addSuppressed(new RuntimeException("Error encountered.", t)); + Iter.close(nodeIter); + throw t; + } + + Iterator itemIter = Iter.map(nodeIter, geometryNode -> { + GeometryWrapper geometryWrapper = GeometryWrapper.extract(geometryNode); + SpatialIndexItem item = makeSpatialIndexItem(feature, geometryWrapper, srsURI); + return item; + }); + return itemIter; + }); + return result; + } + + /** + * + * @param graph + * @param srsURI + * @return Geo predicate objects prepared for adding to SpatialIndex. + */ + public static IteratorCloseable findGeoPredicateIndexItems(Graph graph, String srsURI) { + Iterator resIt = G.iterSubjectsOfPredicate(graph, SpatialExtension.GEO_LAT_NODE); + IteratorCloseable result = Iter.iter(resIt).flatMap(feature -> { + Node lat = G.getOneSP(graph, feature, SpatialExtension.GEO_LAT_NODE); + Node lon = G.getSP(graph, feature, SpatialExtension.GEO_LON_NODE); + Iterator r; + if (lon == null) { + LOGGER.warn("Geo predicates: latitude found but not longitude. " + feature); + r = Iter.empty(); + } else { + GeometryWrapper geometryWrapper = ConvertLatLon.toGeometryWrapper(lat, lon); + SpatialIndexItem item = makeSpatialIndexItem(feature, geometryWrapper, srsURI); + r = Iter.of(item); + } + return r; + }); + return result; + } + + public static SpatialIndexItem makeSpatialIndexItem(Node feature, GeometryWrapper geometryWrapper, String srsURI) { + //Ensure all entries in the target SRS URI. + GeometryWrapper transformedGeometryWrapper = unsafeConvert(geometryWrapper, srsURI); + Envelope envelope = transformedGeometryWrapper.getEnvelope(); + SpatialIndexItem item = new SpatialIndexItem(envelope, feature); + return item; + } + + public static GeometryWrapper unsafeConvert(GeometryWrapper geometryWrapper, String srsURI) { + GeometryWrapper result; + try { + result = geometryWrapper.convertSRS(srsURI); + } catch (MismatchedDimensionException | FactoryException | TransformException e) { + throw new RuntimeException(e); + } + return result; + } +} diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SpatialIndexItem.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SpatialIndexItem.java index 8e6e7a03866..9f7c896fe02 100644 --- a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SpatialIndexItem.java +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SpatialIndexItem.java @@ -17,7 +17,8 @@ */ package org.apache.jena.geosparql.spatial; -import org.apache.jena.rdf.model.Resource; +import org.apache.jena.graph.Node; +import org.apache.jena.rdf.model.RDFNode; import org.locationtech.jts.geom.Envelope; /** @@ -27,9 +28,23 @@ public class SpatialIndexItem { private final Envelope envelope; - private final Resource item; + private final Node item; - public SpatialIndexItem(Envelope envelope, Resource item) { + @Deprecated(forRemoval = true) + public static SpatialIndexItem of(Envelope envelope, RDFNode item) { + return new SpatialIndexItem(envelope, item); + } + + public static SpatialIndexItem of(Envelope envelope, Node node) { + return new SpatialIndexItem(envelope, node); + } + + @Deprecated(forRemoval = true) + public SpatialIndexItem(Envelope envelope, RDFNode item) { + this(envelope, item.asNode()); + } + + public SpatialIndexItem(Envelope envelope, Node item) { this.envelope = envelope; this.item = item; } @@ -38,7 +53,7 @@ public Envelope getEnvelope() { return envelope; } - public Resource getItem() { + public Node getItem() { return item; } diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SpatialIndexStorage.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SpatialIndexStorage.java index 410e8b4b34b..dacaba1915f 100644 --- a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SpatialIndexStorage.java +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SpatialIndexStorage.java @@ -21,6 +21,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; + +import org.apache.jena.geosparql.spatial.index.v1.SpatialIndexV1; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.ResourceFactory; import org.locationtech.jts.geom.Envelope; @@ -29,6 +31,7 @@ * Spatial Index Items in a Serializable form for file reading or writing. * */ +@Deprecated /** Serializable java class of spatial index v1. Moving this class would break Java serialization. */ public class SpatialIndexStorage implements Serializable { private final String srsURI; @@ -41,7 +44,7 @@ public SpatialIndexStorage(Collection spatialIndexItems, Strin this.storageItems = new ArrayList<>(spatialIndexItems.size()); for (SpatialIndexItem spatialIndexItem : spatialIndexItems) { - StorageItem storageItem = new StorageItem(spatialIndexItem.getEnvelope(), spatialIndexItem.getItem()); + StorageItem storageItem = new StorageItem(spatialIndexItem.getEnvelope(), spatialIndexItem.getItem().getURI()); storageItems.add(storageItem); } } @@ -62,8 +65,8 @@ public Collection getIndexItems() { return indexItems; } - public SpatialIndex getSpatialIndex() throws SpatialIndexException { - return new SpatialIndex(getIndexItems(), srsURI); + public SpatialIndexV1 getSpatialIndex() throws SpatialIndexException { + return new SpatialIndexV1(getIndexItems(), srsURI); } private class StorageItem implements Serializable { @@ -76,6 +79,11 @@ public StorageItem(Envelope envelope, Resource item) { this.uri = item.getURI(); } + public StorageItem(Envelope envelope, String uri) { + this.envelope = envelope; + this.uri = uri; + } + public Envelope getEnvelope() { return envelope; } diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/compat/SpatialIndexIo.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/compat/SpatialIndexIo.java new file mode 100644 index 00000000000..10beb4fac1f --- /dev/null +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/compat/SpatialIndexIo.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial.index.compat; + +import java.lang.invoke.MethodHandles; +import java.nio.file.Path; + +import org.apache.jena.geosparql.spatial.SpatialIndexException; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexAdapterV1; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexIoKryo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SpatialIndexIo { + private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + /** Attempt to load a spatial index from file using all supported formats. */ + public static final SpatialIndex load(Path spatialIndexFile) throws SpatialIndexException { + return load(spatialIndexFile, false); + } + + /** + * Attempt to load a spatial index from file using all supported formats. + * This method should only be used for testing as it allows suppressing warnings when loading legacy index formats. + */ + public static final SpatialIndex load(Path spatialIndexFile, boolean suppressLegacyWarnings) throws SpatialIndexException { + SpatialIndex result; + try { + result = SpatialIndexIoKryo.load(spatialIndexFile); + } catch (Throwable t1) { + if (!suppressLegacyWarnings) { + LOGGER.warn("Failed to load spatial index with latest format. Trying legacy formats..."); + } + try { + org.apache.jena.geosparql.spatial.index.v1.SpatialIndexV1 v1 = org.apache.jena.geosparql.spatial.index.v1.SpatialIndexV1.load(spatialIndexFile.toFile()); + result = new SpatialIndexAdapterV1(v1); + + if (!suppressLegacyWarnings) { + LOGGER.warn("Successfully loaded spatial index with legacy format v1. Upgrade advised."); + } + } catch (Throwable t2) { + LOGGER.warn("Failed to load spatial index legacy format.", t2); + t1.addSuppressed(new RuntimeException("Failed to load spatial index with any format.", t2)); + throw t1; + } + } + return result; + } +} diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SpatialIndex.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v1/SpatialIndexV1.java similarity index 85% rename from jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SpatialIndex.java rename to jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v1/SpatialIndexV1.java index 63f48f244f7..3a868bd523a 100644 --- a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/SpatialIndex.java +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v1/SpatialIndexV1.java @@ -15,13 +15,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.jena.geosparql.spatial; +package org.apache.jena.geosparql.spatial.index.v1; -import java.io.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.lang.invoke.MethodHandles; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import org.apache.jena.atlas.RuntimeIOException; import org.apache.jena.atlas.io.IOX; @@ -32,12 +40,27 @@ import org.apache.jena.geosparql.implementation.vocabulary.Geo; import org.apache.jena.geosparql.implementation.vocabulary.SRS_URI; import org.apache.jena.geosparql.implementation.vocabulary.SpatialExtension; +import org.apache.jena.geosparql.spatial.ConvertLatLon; +import org.apache.jena.geosparql.spatial.SpatialIndexException; +import org.apache.jena.geosparql.spatial.SpatialIndexItem; +import org.apache.jena.geosparql.spatial.SpatialIndexStorage; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexAdapterV1; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexUtils; import org.apache.jena.query.Dataset; import org.apache.jena.query.DatasetFactory; import org.apache.jena.query.ReadWrite; -import org.apache.jena.rdf.model.*; +import org.apache.jena.rdf.model.Literal; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.rdf.model.NodeIterator; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.ResIterator; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.Statement; +import org.apache.jena.rdf.model.StmtIterator; import org.apache.jena.sparql.engine.ExecutionContext; import org.apache.jena.sparql.util.Context; +import org.apache.jena.sparql.util.ModelUtils; import org.apache.jena.sparql.util.Symbol; import org.apache.jena.util.iterator.ExtendedIterator; import org.locationtech.jts.geom.Envelope; @@ -55,9 +78,9 @@ * The SpatialIndex is added to the Dataset Context when it is built.
* QueryRewriteIndex is also stored in the SpatialIndex as its content is * Dataset specific. - * */ -public class SpatialIndex { +@Deprecated /** Superseded by {@link org.apache.jena.geosparql.spatial.index.v2.SpatialIndex}*/ +public class SpatialIndexV1 { private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); @@ -67,8 +90,9 @@ public class SpatialIndex { private boolean isBuilt; private final STRtree strTree; private static final int MINIMUM_CAPACITY = 2; + private File location; - private SpatialIndex() { + private SpatialIndexV1() { this.strTree = new STRtree(MINIMUM_CAPACITY); this.isBuilt = true; this.strTree.build(); @@ -81,7 +105,7 @@ private SpatialIndex() { * @param capacity * @param srsURI */ - public SpatialIndex(int capacity, String srsURI) { + public SpatialIndexV1(int capacity, String srsURI) { int indexCapacity = capacity < MINIMUM_CAPACITY ? MINIMUM_CAPACITY : capacity; this.strTree = new STRtree(indexCapacity); this.isBuilt = false; @@ -95,7 +119,7 @@ public SpatialIndex(int capacity, String srsURI) { * @param srsURI * @throws SpatialIndexException */ - public SpatialIndex(Collection spatialIndexItems, String srsURI) throws SpatialIndexException { + public SpatialIndexV1(Collection spatialIndexItems, String srsURI) throws SpatialIndexException { int indexCapacity = spatialIndexItems.size() < MINIMUM_CAPACITY ? MINIMUM_CAPACITY : spatialIndexItems.size(); this.strTree = new STRtree(indexCapacity); insertItems(spatialIndexItems); @@ -104,6 +128,14 @@ public SpatialIndex(Collection spatialIndexItems, String srsUR this.srsInfo = SRSRegistry.getSRSInfo(srsURI); } + public File getLocation() { + return location; + } + + public void setLocation(File location) { + this.location = location; + } + /** * * @return Information about the SRS used by the SpatialIndex. @@ -147,7 +179,7 @@ public void build() { public final void insertItems(Collection indexItems) throws SpatialIndexException { for (SpatialIndexItem indexItem : indexItems) { - insertItem(indexItem.getEnvelope(), indexItem.getItem()); + insertItem(indexItem.getEnvelope(), ModelUtils.convertGraphNodeToRDFNode(indexItem.getItem()).asResource()); } } @@ -187,10 +219,10 @@ public String toString() { * @return SpatialIndex contained in the Context. * @throws SpatialIndexException */ - public static final SpatialIndex retrieve(ExecutionContext execCxt) throws SpatialIndexException { + public static final SpatialIndexV1 retrieve(ExecutionContext execCxt) throws SpatialIndexException { Context context = execCxt.getContext(); - SpatialIndex spatialIndex = (SpatialIndex) context.get(SPATIAL_INDEX_SYMBOL, null); + SpatialIndexV1 spatialIndex = (SpatialIndexV1) context.get(SPATIAL_INDEX_SYMBOL, null); if (spatialIndex == null) { throw new SpatialIndexException("Dataset Context does not contain SpatialIndex."); @@ -216,9 +248,10 @@ public static final boolean isDefined(ExecutionContext execCxt) { * @param dataset * @param spatialIndex */ - public static final void setSpatialIndex(Dataset dataset, SpatialIndex spatialIndex) { + public static final void setSpatialIndex(Dataset dataset, SpatialIndexV1 spatialIndex) { Context context = dataset.getContext(); - context.set(SPATIAL_INDEX_SYMBOL, spatialIndex); + SpatialIndex wrapper = new SpatialIndexAdapterV1(spatialIndex); + SpatialIndexUtils.setSpatialIndex(context, wrapper); } /** @@ -232,14 +265,15 @@ public static final void setSpatialIndex(Dataset dataset, SpatialIndex spatialIn * @return SpatialIndex constructed. * @throws SpatialIndexException */ - public static SpatialIndex buildSpatialIndex(Dataset dataset, String srsURI, File spatialIndexFile) throws SpatialIndexException { + public static SpatialIndexV1 buildSpatialIndex(Dataset dataset, String srsURI, File spatialIndexFile) throws SpatialIndexException { - SpatialIndex spatialIndex = load(spatialIndexFile); + SpatialIndexV1 spatialIndex = load(spatialIndexFile); + spatialIndex.setLocation(spatialIndexFile); if (spatialIndex.isEmpty()) { Collection spatialIndexItems = findSpatialIndexItems(dataset, srsURI); save(spatialIndexFile, spatialIndexItems, srsURI); - spatialIndex = new SpatialIndex(spatialIndexItems, srsURI); + spatialIndex = new SpatialIndexV1(spatialIndexItems, srsURI); spatialIndex.build(); } @@ -258,9 +292,9 @@ public static SpatialIndex buildSpatialIndex(Dataset dataset, String srsURI, Fil * @return SpatialIndex constructed. * @throws SpatialIndexException */ - public static SpatialIndex buildSpatialIndex(Dataset dataset, File spatialIndexFile) throws SpatialIndexException { + public static SpatialIndexV1 buildSpatialIndex(Dataset dataset, File spatialIndexFile) throws SpatialIndexException { String srsURI = GeoSPARQLOperations.findModeSRS(dataset); - SpatialIndex spatialIndex = buildSpatialIndex(dataset, srsURI, spatialIndexFile); + SpatialIndexV1 spatialIndex = buildSpatialIndex(dataset, srsURI, spatialIndexFile); return spatialIndex; } @@ -273,11 +307,11 @@ public static SpatialIndex buildSpatialIndex(Dataset dataset, File spatialIndexF * @return SpatialIndex constructed. * @throws SpatialIndexException */ - public static SpatialIndex buildSpatialIndex(Dataset dataset, String srsURI) throws SpatialIndexException { + public static SpatialIndexV1 buildSpatialIndex(Dataset dataset, String srsURI) throws SpatialIndexException { LOGGER.info("Building Spatial Index - Started"); Collection items = findSpatialIndexItems(dataset, srsURI); - SpatialIndex spatialIndex = new SpatialIndex(items, srsURI); + SpatialIndexV1 spatialIndex = new SpatialIndexV1(items, srsURI); spatialIndex.build(); setSpatialIndex(dataset, spatialIndex); LOGGER.info("Building Spatial Index - Completed"); @@ -321,9 +355,9 @@ public static Collection findSpatialIndexItems(Dataset dataset * @return SpatialIndex constructed. * @throws SpatialIndexException */ - public static SpatialIndex buildSpatialIndex(Dataset dataset) throws SpatialIndexException { + public static SpatialIndexV1 buildSpatialIndex(Dataset dataset) throws SpatialIndexException { String srsURI = GeoSPARQLOperations.findModeSRS(dataset); - SpatialIndex spatialIndex = buildSpatialIndex(dataset, srsURI); + SpatialIndexV1 spatialIndex = buildSpatialIndex(dataset, srsURI); return spatialIndex; } @@ -477,7 +511,7 @@ private static Collection getGeoPredicateIndexItems(Model mode * @return Built Spatial Index. * @throws SpatialIndexException */ - public static final SpatialIndex load(File spatialIndexFile) throws SpatialIndexException { + public static final SpatialIndexV1 load(File spatialIndexFile) throws SpatialIndexException { if (spatialIndexFile != null && spatialIndexFile.exists()) { LOGGER.info("Loading Spatial Index - Started: {}", spatialIndexFile.getAbsolutePath()); @@ -485,14 +519,14 @@ public static final SpatialIndex load(File spatialIndexFile) throws SpatialIndex try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(spatialIndexFile))) { SpatialIndexStorage storage = (SpatialIndexStorage) in.readObject(); - SpatialIndex spatialIndex = storage.getSpatialIndex(); + SpatialIndexV1 spatialIndex = storage.getSpatialIndex(); LOGGER.info("Loading Spatial Index - Completed: {}", spatialIndexFile.getAbsolutePath()); return spatialIndex; } catch (ClassNotFoundException | IOException ex) { throw new SpatialIndexException("Loading Exception: " + ex.getMessage(), ex); } } else { - return new SpatialIndex(); + return new SpatialIndexV1(); } } diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/STRtreePerGraph.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/STRtreePerGraph.java new file mode 100644 index 00000000000..e57e56ba94d --- /dev/null +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/STRtreePerGraph.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial.index.v2; + +import java.lang.invoke.MethodHandles; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.apache.jena.graph.Node; +import org.apache.jena.sparql.core.Quad; +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.index.strtree.STRtree; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class STRtreePerGraph { + private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private STRtree defaultTree; + private Map namedTreeMap; + + // XXX Make srsInfo part of this class? + // this.srsInfo = SRSRegistry.getSRSInfo(SRS_URI.DEFAULT_WKT_CRS84); + + private boolean isBuilt = false; + + public STRtreePerGraph() { + this(new STRtree(STRtreeUtils.MINIMUM_CAPACITY), new HashMap<>()); + } + + public STRtreePerGraph(STRtree defaultTree) { + this(defaultTree, new HashMap<>()); + } + + public STRtreePerGraph(STRtree defaultTree, Map namedTreeMap) { + super(); + this.defaultTree = Objects.requireNonNull(defaultTree); + this.namedTreeMap = Objects.requireNonNull(namedTreeMap); + } + + public STRtree getDefaultTree() { + return defaultTree; + } + + public Map getNamedTreeMap() { + return namedTreeMap; + } + + /** Returns the prior default tree. */ + public STRtree setDefaultTree(STRtree tree) { + STRtree result = defaultTree; + this.defaultTree = tree; + return result; + } + + /** Returns the prior tree of graphNode. */ + public STRtree setNamedTree(Node graphNode, STRtree tree) { + return namedTreeMap.put(graphNode, tree); + } + + // @Override + @SuppressWarnings("unchecked") + public Collection query(Envelope searchEnvelope) { + // return new LinkedHashSet<>(defaultTree.query(searchEnvelope)); + // FIXME This method should probably query all graphs - not just the default graph + return queryOneGraph(searchEnvelope, null); + } + + // @Override + public Collection queryOneGraph(Envelope searchEnvelope, Node graph) { + LOGGER.debug("spatial index lookup on graph: " + graph); + + Collection result; + + // FIXME Without the new (Linked)HashMap there are failing tests - yet maybe the index response can still be used directly without extra copy. + + // handle union graph + if (graph == null || Quad.isDefaultGraph(graph)) { + result = new LinkedHashSet<>(defaultTree.query(searchEnvelope)); + } else if (Quad.isUnionGraph(graph)) { + LOGGER.warn("spatial index lookup on union graph"); + result = namedTreeMap.values().stream() + .map(tree -> tree.query(searchEnvelope)) + .collect(LinkedHashSet::new, + Set::addAll, + Set::addAll); + } else { + STRtree tree = namedTreeMap.get(graph); + if (tree == null) { + LOGGER.warn("graph not indexed: " + graph); + } + if (tree != null && !tree.isEmpty()) { + result = new LinkedHashSet<>(tree.query(searchEnvelope)); + } else { + result = new HashSet<>(); + } + } + return result; + } + + public boolean isEmpty() { + boolean result = defaultTree.isEmpty() + || namedTreeMap.values().stream().allMatch(STRtree::isEmpty); + return result; + } + + public void build() { + if (!isBuilt) { + defaultTree.build(); + namedTreeMap.values().forEach(STRtree::build); + isBuilt = true; + } + } + + public long size() { + long result = defaultTree.size() + + namedTreeMap.values().stream().mapToLong(STRtree::size).sum(); + return result; + } +} diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/STRtreeUtils.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/STRtreeUtils.java new file mode 100644 index 00000000000..6eec9e0c80b --- /dev/null +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/STRtreeUtils.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial.index.v2; + +import java.lang.invoke.MethodHandles; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.jena.atlas.iterator.Iter; +import org.apache.jena.atlas.iterator.IteratorCloseable; +import org.apache.jena.geosparql.spatial.SpatialIndexException; +import org.apache.jena.geosparql.spatial.SpatialIndexFindUtils; +import org.apache.jena.geosparql.spatial.SpatialIndexItem; +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.compose.Union; +import org.apache.jena.sparql.core.DatasetGraph; +import org.locationtech.jts.index.strtree.STRtree; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class STRtreeUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + static final int MINIMUM_CAPACITY = 2; + + public static STRtree buildSpatialIndexTree(Graph graph, String srsURI) throws SpatialIndexException { + try { + STRtree tree; + IteratorCloseable it = SpatialIndexFindUtils.findSpatialIndexItems(graph, srsURI); + try { + tree = buildSpatialIndexTree(it); + } finally { + it.close(); + } + return tree; + } catch (Throwable e) { + throw new SpatialIndexException("Spatial index construction failed.", e); + } + } + + public static STRtree buildSpatialIndexTreeUnion(DatasetGraph datasetGraph, String srsURI) throws SpatialIndexException { + // create the union view of default graph and all named graphs + Union unionAllGraph = new Union(datasetGraph.getDefaultGraph(), datasetGraph.getUnionGraph()); + STRtree tree = buildSpatialIndexTree(unionAllGraph, srsURI); + return tree; + } + + public static STRtreePerGraph buildSpatialIndexTree(DatasetGraph datasetGraph, String srsURI) throws SpatialIndexException { + STRtree defaultGraphTree; + Map namedGraphTreeMap = new LinkedHashMap<>(); + + LOGGER.info("building spatial index for default graph ..."); + Graph defaultGraph = datasetGraph.getDefaultGraph(); + defaultGraphTree = buildSpatialIndexTree(defaultGraph, srsURI); + + // Named graphs + Iterator graphIter = datasetGraph.listGraphNodes(); + try { + while (graphIter.hasNext()) { + Node graphNode = graphIter.next(); + LOGGER.info("building spatial index for graph {} ...", graphNode); + Graph namedGraph = datasetGraph.getGraph(graphNode); + namedGraphTreeMap.put(graphNode, buildSpatialIndexTree(namedGraph, srsURI)); + } + } finally { + Iter.close(graphIter); + } + + return new STRtreePerGraph(defaultGraphTree, namedGraphTreeMap); + } + + // Caller must close the iterator + public static STRtree buildSpatialIndexTree(Iterator it) throws SpatialIndexException { + List items = Iter.toList(it); + STRtree tree = buildSpatialIndexTree(items); + return tree; + } + + public static STRtree buildSpatialIndexTree(Collection items) throws SpatialIndexException { + STRtree tree = new STRtree(Math.max(MINIMUM_CAPACITY, items.size())); + addToTree(tree, items.iterator()); + tree.build(); + return tree; + } + + public static void addToTree(STRtree treeAcc, Iterator it) throws SpatialIndexException { + List items = Iter.toList(it); + items.forEach(item -> treeAcc.insert(item.getEnvelope(), item.getItem())); + } +} diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndex.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndex.java new file mode 100644 index 00000000000..659bd369fc8 --- /dev/null +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndex.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial.index.v2; + +import java.nio.file.Path; +import java.util.Collection; + +import org.apache.jena.geosparql.implementation.SRSInfo; +import org.apache.jena.graph.Node; +import org.apache.jena.sparql.core.Quad; +import org.locationtech.jts.geom.Envelope; + +public interface SpatialIndex { + + /** + * + * @return Information about the SRS used by the SpatialIndex. + */ + SRSInfo getSrsInfo(); + + /** + * + * @return True if the SpatialIndex is empty. + */ + boolean isEmpty(); + + /** + * Returns the number of items in the index. + */ + long getSize(); + + /** + * Query one graph. + * The default graph can be referenced with null or any value for which {@link Quad#isDefaultGraph()} returns true. + * The union graph can be referenced with {@link Quad#isUnionGraph()}. + * + * @param searchEnvelope + * @param graph + * @return + */ + Collection query(Envelope searchEnvelope, Node graph); + + Path getLocation(); + void setLocation(Path location); +} diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexAdapterV1.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexAdapterV1.java new file mode 100644 index 00000000000..2ceb169b127 --- /dev/null +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexAdapterV1.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial.index.v2; + +import java.io.File; +import java.nio.file.Path; +import java.util.Collection; +import java.util.HashSet; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.jena.geosparql.implementation.SRSInfo; +import org.apache.jena.graph.Node; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; +import org.locationtech.jts.geom.Envelope; + +/** Adapter class for spatial index v1. */ +public class SpatialIndexAdapterV1 + implements SpatialIndex +{ + protected org.apache.jena.geosparql.spatial.index.v1.SpatialIndexV1 v1; + + public SpatialIndexAdapterV1(org.apache.jena.geosparql.spatial.index.v1.SpatialIndexV1 v1) { + super(); + this.v1 = v1; + } + + @Override + public SRSInfo getSrsInfo() { + return v1.getSrsInfo(); + } + + @Override + public boolean isEmpty() { + return v1.isEmpty(); + } + + @Override + public long getSize() { + return -1; + } + + protected Collection adapt(Collection resources) { + Collection result = resources.stream() + .map(RDFNode::asNode) + .collect(Collectors.toCollection(HashSet::new)); + return result; + } + + @Override + public Collection query(Envelope searchEnvelope, Node graph) { + HashSet resources = v1.query(searchEnvelope); + return adapt(resources); + } + + @Override + public Path getLocation() { + Path result = Optional.ofNullable(v1.getLocation()).map(File::toPath).orElse(null); + return result; + } + + @Override + public void setLocation(Path location) { + v1.setLocation(location.toFile()); + } +} diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexIoKryo.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexIoKryo.java new file mode 100644 index 00000000000..560a57cb881 --- /dev/null +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexIoKryo.java @@ -0,0 +1,193 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial.index.v2; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.invoke.MethodHandles; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +import org.apache.jena.atlas.RuntimeIOException; +import org.apache.jena.atlas.io.IOX; +import org.apache.jena.geosparql.configuration.GeoSPARQLOperations; +import org.apache.jena.geosparql.implementation.SRSInfo; +import org.apache.jena.geosparql.implementation.registry.SRSRegistry; +import org.apache.jena.geosparql.implementation.vocabulary.SRS_URI; +import org.apache.jena.geosparql.spatial.SpatialIndexException; +import org.apache.jena.geosparql.spatial.serde.JtsKryoRegistrator; +import org.apache.jena.graph.Node; +import org.apache.jena.query.Dataset; +import org.locationtech.jts.index.strtree.STRtree; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +public class SpatialIndexIoKryo { + private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + public static SpatialIndex buildSpatialIndex(Dataset dataset, + String srsURI, + Path spatialIndexFile, + boolean spatialIndexPerGraph) throws SpatialIndexException { + + SpatialIndexPerGraph spatialIndex = load(spatialIndexFile); + + if (spatialIndex.isEmpty()) { + spatialIndex = SpatialIndexUtils.buildSpatialIndex(dataset.asDatasetGraph(), srsURI, spatialIndexPerGraph); + save(spatialIndexFile, spatialIndex); + } + spatialIndex.setLocation(spatialIndexFile); + + SpatialIndexUtils.setSpatialIndex(dataset, spatialIndex); + return spatialIndex; + } + + /** + * Build Spatial Index from all graphs in Dataset.
+ * Dataset contains SpatialIndex in Context.
+ * Spatial Index written to file. + * + * @param dataset + * @param srsURI + * @param spatialIndexFile + * @return SpatialIndex constructed. + * @throws SpatialIndexException + */ + public static SpatialIndex buildSpatialIndex(Dataset dataset, String srsURI, Path spatialIndexFile) throws SpatialIndexException { + + return buildSpatialIndex(dataset, srsURI, spatialIndexFile, false); + } + + /** + * Build Spatial Index from all graphs in Dataset.
+ * Dataset contains SpatialIndex in Context.
+ * SRS URI based on most frequent found in Dataset.
+ * Spatial Index written to file. + * + * @param dataset + * @param spatialIndexFile + * @return SpatialIndex constructed. + * @throws SpatialIndexException + */ + public static SpatialIndex buildSpatialIndex(Dataset dataset, Path spatialIndexFile) throws SpatialIndexException { + return buildSpatialIndex(dataset, spatialIndexFile, false); + } + + public static SpatialIndex buildSpatialIndex(Dataset dataset, Path spatialIndexFile, boolean spatialIndexPerGraph) throws SpatialIndexException { + String srsURI = GeoSPARQLOperations.findModeSRS(dataset); + SpatialIndex spatialIndex = buildSpatialIndex(dataset, srsURI, spatialIndexFile, spatialIndexPerGraph); + return spatialIndex; + } + + /** + * Save SpatialIndex to file. + * + * @param spatialIndexFile the file being saved to + * @param index the spatial index + * @throws SpatialIndexException + */ + public static final void save(Path spatialIndexFile, SpatialIndexPerGraph index) throws SpatialIndexException { + Path absPath = spatialIndexFile.toAbsolutePath(); + if (spatialIndexFile != null) { + LOGGER.info("Saving Spatial Index - Started: {}", absPath); + + String filename = absPath.toString(); + Path file = Path.of(filename); + Path tmpFile = IOX.uniqueDerivedPath(file, null); + try { + Files.deleteIfExists(file); + } catch (IOException ex) { + throw new SpatialIndexException("Failed to delete file: " + ex.getMessage()); + } + try { + IOX.safeWriteOrCopy(file, tmpFile, out -> writeToOutputStream(out, index)); + } catch (RuntimeIOException ex) { + throw new SpatialIndexException("Save Exception: " + ex.getMessage()); + } finally { + LOGGER.info("Saving Spatial Index - Completed: {}", absPath); + } + + } + } + + + /** + * Write spatial index as Kryo serialization to given OutputStream. + * @param os output stream + * @param index spatial index + */ + public static void writeToOutputStream(OutputStream os, SpatialIndexPerGraph index) { + Kryo kryo = new Kryo(); + JtsKryoRegistrator.registerClasses(kryo); + try (Output output = new Output(os)) { + writeIndex(kryo, output, index); + output.flush(); + } + } + + public static void writeIndex(Kryo kryo, Output output, SpatialIndexPerGraph index) { + kryo.writeObject(output, index.getSrsInfo().getSrsURI()); + kryo.writeObject(output, index.getIndex().getDefaultTree()); + kryo.writeClassAndObject(output, index.getIndex().getNamedTreeMap()); + } + + /** + * Load a SpatialIndex from file.
+ * Index will be built and empty if file does not exist or is null. + * + * @param spatialIndexFile + * @return Built Spatial Index. + * @throws SpatialIndexException + */ + public static final SpatialIndexPerGraph load(Path spatialIndexFile) throws SpatialIndexException { + Kryo kryo = new Kryo(); + JtsKryoRegistrator.registerClasses(kryo); + + String srsUri; + STRtreePerGraph index; + + if (spatialIndexFile != null && Files.exists(spatialIndexFile)) { + spatialIndexFile = spatialIndexFile.toAbsolutePath(); + LOGGER.info("Loading Spatial Index - Started: {}", spatialIndexFile); + + try (Input input = new Input(Files.newInputStream(spatialIndexFile))) { + srsUri = kryo.readObject(input, String.class); + STRtree defaultGraphTree = kryo.readObject(input, STRtree.class); + Map graphToTree = (Map) kryo.readClassAndObject(input); + index = new STRtreePerGraph(defaultGraphTree, graphToTree); + LOGGER.info("Loading Spatial Index - Completed: {}", spatialIndexFile); + } catch (IOException ex) { + throw new SpatialIndexException("Loading Exception: " + ex.getMessage(), ex); + } + } else { + LOGGER.info("File {} does not exist. Creating empty Spatial Index.", (spatialIndexFile != null ? spatialIndexFile.toAbsolutePath() : "null")); + srsUri = SRS_URI.DEFAULT_WKT_CRS84; + index = new STRtreePerGraph(); + } + + SRSInfo srsInfo = SRSRegistry.getSRSInfo(srsUri); + SpatialIndexPerGraph spatialIndex = new SpatialIndexPerGraph(srsInfo, index, spatialIndexFile); + spatialIndex.setLocation(spatialIndexFile); + return spatialIndex; + } +} diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexPerGraph.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexPerGraph.java new file mode 100644 index 00000000000..356cc701f9e --- /dev/null +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexPerGraph.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial.index.v2; + +import java.lang.invoke.MethodHandles; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.apache.jena.geosparql.implementation.SRSInfo; +import org.apache.jena.geosparql.implementation.registry.SRSRegistry; +import org.apache.jena.geosparql.implementation.vocabulary.SRS_URI; +import org.apache.jena.geosparql.spatial.SpatialIndexException; +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.NodeFactory; +import org.apache.jena.query.TxnType; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.system.TxnCtl; +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.index.strtree.STRtree; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * SpatialIndex for testing bounding box collisions between geometries within a + * Dataset.
+ * Queries must be performed using the same SRS URI as the SpatialIndex.
+ * The SpatialIndex is added to the Dataset Context when it is built.
+ * QueryRewriteIndex is also stored in the SpatialIndex as its content is + * Dataset specific. + * + */ +public class SpatialIndexPerGraph implements SpatialIndex { + + private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private transient final SRSInfo srsInfo; + private STRtreePerGraph index; + private Path location; + + public SpatialIndexPerGraph(STRtreePerGraph index) { + this(SRS_URI.DEFAULT_WKT_CRS84, index, null); + } + + public SpatialIndexPerGraph(String srsUri, STRtreePerGraph index, Path location) { + this( + SRSRegistry.getSRSInfo(srsUri), + index, + null); + } + + public SpatialIndexPerGraph(SRSInfo srsInfo, STRtreePerGraph index, Path location) { + super(); + this.srsInfo = Objects.requireNonNull(srsInfo); + // this.isBuilt = isBuilt; + this.index = Objects.requireNonNull(index); + this.location = location; + } + + public STRtreePerGraph getIndex() { + return index; + } + + /** + * + * @return Information about the SRS used by the SpatialIndex. + */ + @Override + public SRSInfo getSrsInfo() { + return srsInfo; + } + + /** + * + * @return True if the SpatialIndex is empty. + */ + @Override + public boolean isEmpty() { + return index.isEmpty(); + } + + /** + * Returns the number of items in the index. + */ + @Override + public long getSize() { + return index.size(); + } + + @Override + public Collection query(Envelope searchEnvelope, Node graph) { + return index.queryOneGraph(searchEnvelope, graph); + } + + @Override + public Path getLocation() { + return location; + } + + @Override + public void setLocation(Path location) { + this.location = location; + } + + @Override + public String toString() { + return "SpatialIndex{" + "srsInfo=" + srsInfo + ", index=" + index + ", file=" + location + '}'; + } + + // FIXME It appear this method cannot recompute the tree for the default graph? + /** + * Recompute and replace the spatial index trees for the given named graphs. + * + * @param index the spatial index to modify + * @param datasetGraph the dataset containing the named graphs + * @param graphNames the named graphs + * @return the modified spatial index object, i.e. no copy of the input index object + * @throws SpatialIndexException + */ + public static SpatialIndex recomputeIndexForGraphs(SpatialIndexPerGraph index, + DatasetGraph datasetGraph, + List graphNames) throws SpatialIndexException { + STRtreePerGraph trees = index.getIndex(); + STRtree defaultTree = trees.getDefaultTree(); + Map namedTreeMap = trees.getNamedTreeMap(); + + try (TxnCtl txn = TxnCtl.begin(datasetGraph, TxnType.READ)) { + for (String graphName : graphNames) { + Node g = NodeFactory.createURI(graphName); + if (namedTreeMap.containsKey(g)) { + LOGGER.info("recomputing spatial index for graph: {}", graphName); + } else { + LOGGER.info("computing spatial index for graph: {}", graphName); + } + Graph namedGraph = datasetGraph.getGraph(g); + STRtree indexTree = STRtreeUtils.buildSpatialIndexTree(namedGraph, index.getSrsInfo().getSrsURI()); + STRtree oldIndexTree = trees.setNamedTree(g, indexTree); + if (oldIndexTree != null) { + LOGGER.info("replaced spatial index for graph: {}", graphName); + } else { + LOGGER.info("added spatial index for graph: {}", graphName); + } + } + } + return index; + } +} diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexUtils.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexUtils.java new file mode 100644 index 00000000000..c81849c451a --- /dev/null +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexUtils.java @@ -0,0 +1,212 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial.index.v2; + +import java.lang.invoke.MethodHandles; + +import org.apache.jena.geosparql.configuration.GeoSPARQLOperations; +import org.apache.jena.geosparql.implementation.SRSInfo; +import org.apache.jena.geosparql.implementation.registry.SRSRegistry; +import org.apache.jena.geosparql.spatial.SpatialIndexException; +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.query.Dataset; +import org.apache.jena.query.DatasetFactory; +import org.apache.jena.query.TxnType; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.NamedGraph; +import org.apache.jena.sparql.engine.ExecutionContext; +import org.apache.jena.sparql.util.Context; +import org.apache.jena.sparql.util.Symbol; +import org.apache.jena.system.TxnCtl; +import org.locationtech.jts.index.strtree.STRtree; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** ARQ-level utils for Dataset and Context. */ +public class SpatialIndexUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + public static final Symbol SPATIAL_INDEX_SYMBOL = Symbol.create("http://jena.apache.org/spatial#index"); + + public static final Symbol symSpatialIndexPerGraph = Symbol.create("http://jena.apache.org/spatial#indexPerGraph"); + public static final Symbol symSrsUri = Symbol.create("http://jena.apache.org/spatial#srsURI"); + + /** + * Set the SpatialIndex into the Context of the Dataset for later retrieval + * and use in spatial functions. + * + * @param dataset + * @param spatialIndex + */ + public static final void setSpatialIndex(Dataset dataset, SpatialIndex spatialIndex) { + Context cxt = dataset.getContext(); + setSpatialIndex(cxt, spatialIndex); + } + + public static final void setSpatialIndex(DatasetGraph datasetGraph, SpatialIndex spatialIndex) { + Context cxt = datasetGraph.getContext(); + setSpatialIndex(cxt, spatialIndex); + } + + public static final void setSpatialIndex(Context context, SpatialIndex spatialIndex) { + context.set(SPATIAL_INDEX_SYMBOL, spatialIndex); + } + + public static final SpatialIndex getSpatialIndex(Context cxt) { + return cxt.get(SPATIAL_INDEX_SYMBOL); + } + + /** + * + * @param execCxt + * @return True if a SpatialIndex is defined in the ExecutionContext. + */ + public static final boolean isDefined(ExecutionContext execCxt) { + Context context = execCxt.getContext(); + return context.isDefined(SPATIAL_INDEX_SYMBOL); + } + + /** + * Retrieve the SpatialIndex from the Context. + * + * @param execCxt + * @return SpatialIndex contained in the Context. + * @throws SpatialIndexException + */ + public static final SpatialIndex retrieve(ExecutionContext execCxt) throws SpatialIndexException { + Context context = execCxt.getContext(); + SpatialIndex spatialIndex = (SpatialIndex) context.get(SPATIAL_INDEX_SYMBOL, null); + if (spatialIndex == null) { + throw new SpatialIndexException("Dataset Context does not contain SpatialIndex."); + } + return spatialIndex; + } + + /** + * Wrap Model in a Dataset and build SpatialIndex. + * + * @param model + * @param srsURI + * @return Dataset with default Model and SpatialIndex in Context. + * @throws SpatialIndexException + */ + public static final Dataset wrapModel(Model model, String srsURI) throws SpatialIndexException { + Dataset dataset = DatasetFactory.createTxnMem(); + dataset.setDefaultModel(model); + buildSpatialIndex(dataset.asDatasetGraph(), srsURI); + return dataset; + } + + /** + * Wrap Model in a Dataset and build SpatialIndex. + * + * @param model + * @return Dataset with default Model and SpatialIndex in Context. + * @throws SpatialIndexException + */ + public static final Dataset wrapModel(Model model) throws SpatialIndexException { + Dataset dataset = DatasetFactory.createTxnMem(); + dataset.setDefaultModel(model); + String srsURI = GeoSPARQLOperations.findModeSRS(dataset); + buildSpatialIndex(dataset.asDatasetGraph(), srsURI); + return dataset; + } + + /** + * Build Spatial Index from all graphs in Dataset.
+ * Dataset contains SpatialIndex in Context.
+ * SRS URI based on most frequent found in Dataset. + * + * @param datasetGraph + * @return SpatialIndex constructed. + * @throws SpatialIndexException + */ + public static SpatialIndex buildSpatialIndex(DatasetGraph datasetGraph) throws SpatialIndexException { + // XXX Dataset wrapping due to legacy code + Dataset dataset = DatasetFactory.wrap(datasetGraph); + String srsURI = GeoSPARQLOperations.findModeSRS(dataset); + SpatialIndex spatialIndex = buildSpatialIndex(datasetGraph, srsURI); + return spatialIndex; + } + + /** + * Build Spatial Index from all graphs in Dataset.
+ * Dataset contains SpatialIndex in Context. + * + * @param datasetGraph + * @param srsURI + * @return SpatialIndex constructed. + * @throws SpatialIndexException + */ + public static SpatialIndex buildSpatialIndex(DatasetGraph datasetGraph, String srsURI) throws SpatialIndexException { + return buildSpatialIndex(datasetGraph, srsURI, true); + } + + public static SpatialIndexPerGraph buildSpatialIndex(DatasetGraph datasetGraph, String srsURI, boolean indexTreePerGraph) throws SpatialIndexException { + SpatialIndexPerGraph result = indexTreePerGraph + ? buildSpatialIndexPerGraph(datasetGraph, srsURI) + : buildSpatialIndexUnion(datasetGraph, srsURI); + return result; + } + + public static SpatialIndexPerGraph buildSpatialIndexUnion(DatasetGraph datasetGraph, String srsURI) throws SpatialIndexException { + // we always compute an index tree DGT for the default graph + // if an index per named graph NG is enabled, we compute a separate index tree NGT for each NG, otherwise all + // items will be indexed in the default graph index tree DGT + + STRtree treePerGraph; + LOGGER.info("Building Spatial Index - Started"); + try (TxnCtl txn = TxnCtl.begin(datasetGraph, TxnType.READ)) { + treePerGraph = STRtreeUtils.buildSpatialIndexTreeUnion(datasetGraph, srsURI); + } + LOGGER.info("Building Spatial Index - Completed"); + + SRSInfo srsInfo = SRSRegistry.getSRSInfo(srsURI); + STRtreePerGraph trees = new STRtreePerGraph(treePerGraph); + SpatialIndexPerGraph index = new SpatialIndexPerGraph(srsInfo, trees, null); + setSpatialIndex(datasetGraph, index); + return index; + } + + public static SpatialIndexPerGraph buildSpatialIndexPerGraph(DatasetGraph datasetGraph, String srsURI) throws SpatialIndexException { + // we always compute an index tree DGT for the default graph + // if an index per named graph NG is enabled, we compute a separate index tree NGT for each NG, otherwise all + // items will be indexed in the default graph index tree DGT + + STRtreePerGraph treePerGraph; + LOGGER.info("Building Spatial Index - Started"); + try (TxnCtl txn = TxnCtl.begin(datasetGraph, TxnType.READ)) { + treePerGraph = STRtreeUtils.buildSpatialIndexTree(datasetGraph, srsURI); + } + LOGGER.info("Building Spatial Index - Completed"); + + SRSInfo srsInfo = SRSRegistry.getSRSInfo(srsURI); + SpatialIndexPerGraph index = new SpatialIndexPerGraph(srsInfo, treePerGraph, null); + setSpatialIndex(datasetGraph, index); + return index; + } + + public static Node unwrapGraphName(Graph graph) { + Node graphNode = graph instanceof NamedGraph namedGraph + ? namedGraph.getGraphName() + : null; + return graphNode; + } +} diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/property_functions/GenericSpatialPropertyFunction.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/property_functions/GenericSpatialPropertyFunction.java index aa1b2e3427e..855665d142f 100644 --- a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/property_functions/GenericSpatialPropertyFunction.java +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/property_functions/GenericSpatialPropertyFunction.java @@ -18,7 +18,7 @@ package org.apache.jena.geosparql.spatial.property_functions; import java.util.Arrays; -import java.util.HashSet; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.stream.Stream; @@ -31,12 +31,12 @@ import org.apache.jena.geosparql.implementation.vocabulary.SpatialExtension; import org.apache.jena.geosparql.spatial.ConvertLatLon; import org.apache.jena.geosparql.spatial.SearchEnvelope; -import org.apache.jena.geosparql.spatial.SpatialIndex; import org.apache.jena.geosparql.spatial.SpatialIndexException; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexUtils; import org.apache.jena.graph.Graph; import org.apache.jena.graph.Node; import org.apache.jena.graph.Triple; -import org.apache.jena.rdf.model.Resource; import org.apache.jena.sparql.core.Var; import org.apache.jena.sparql.engine.ExecutionContext; import org.apache.jena.sparql.engine.QueryIterator; @@ -66,7 +66,7 @@ public abstract class GenericSpatialPropertyFunction extends PFuncSimpleAndList @Override public final QueryIterator execEvaluated(Binding binding, Node subject, Node predicate, PropFuncArg object, ExecutionContext execCxt) { try { - spatialIndex = SpatialIndex.retrieve(execCxt); + spatialIndex = SpatialIndexUtils.retrieve(execCxt); spatialArguments = extractObjectArguments(predicate, object, spatialIndex.getSrsInfo()); return search(binding, execCxt, subject, spatialArguments.limit); } catch (SpatialIndexException ex) { @@ -184,15 +184,35 @@ private QueryIterator checkUnbound(Binding binding, ExecutionContext execCxt, No //Find all Features in the spatial index which are within the rough search envelope. SearchEnvelope searchEnvelope = spatialArguments.searchEnvelope; - HashSet features = searchEnvelope.check(spatialIndex); + Graph activeGraph = execCxt.getActiveGraph(); + + Node graphName = SpatialIndexUtils.unwrapGraphName(activeGraph); + Collection features = searchEnvelope.check(spatialIndex, graphName); +// Collection features; +// +//// FIXME: Confirm that we no longer need to consider symSpatialIndexPerGraph. We just pass the graph to the index and if the index +//// supports named graphs than it will restrict matches to that graph - otherwise we'll just get more results with more post-processing work. +// if (!execCxt.getDataset().getContext().get(SpatialIndexUtils.symSpatialIndexPerGraph, false)) { +// // no index per graph activated, thus, fallback to query the default graph spatial index tree only +// // which is the default behaviour +// features = searchEnvelope.check(spatialIndex, null); +// } else { +// // check if context is a named graph, if so use to query only the corresponding spatial index tree +// // otherwise, query only the default graph spatial index tree +// Node graphName = SpatialIndexUtils.unwrapGraphName(activeGraph); +// features = searchEnvelope.check(spatialIndex, graphName); +//// features = graphName != null +//// ? searchEnvelope.check(spatialIndex, graphName) +//// : searchEnvelope.check(spatialIndex); +// } Var subjectVar = Var.alloc(subject.getName()); - Stream stream = features.stream(); + Stream stream = features.stream(); if (requireSecondFilter()) { - stream = stream.filter(feature -> checkBound(execCxt, feature.asNode())); + stream = stream.filter(feature -> checkBound(execCxt, feature)); } - Iterator iterator = stream.map(feature -> BindingFactory.binding(binding, subjectVar, feature.asNode())) + Iterator iterator = stream.map(feature -> BindingFactory.binding(binding, subjectVar, feature)) .limit(limit) .iterator(); return QueryIterPlainWrapper.create(iterator, execCxt); diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/CustomGeometrySerde.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/CustomGeometrySerde.java new file mode 100644 index 00000000000..8091b186ff4 --- /dev/null +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/CustomGeometrySerde.java @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial.serde; + +import org.apache.jena.graph.Node; +import org.apache.sedona.common.geometryObjects.Circle; +import org.apache.sedona.common.geometrySerde.GeometrySerde; +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryCollection; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.MultiLineString; +import org.locationtech.jts.geom.MultiPoint; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.geom.Polygon; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Registration; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + + +/** + * Provides methods to efficiently serialize and deserialize geometry types. + *

+ * Supports Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, + * GeometryCollection, Circle and Envelope types. + *

+ * First byte contains {@link Type#id}. Then go type-specific bytes, followed + * by user-data attached to the geometry. + */ +public class CustomGeometrySerde + extends GeometrySerde +{ + private static final long serialVersionUID = 1L; + private static final GeometryFactory geometryFactory = new GeometryFactory(); + + private GeometrySerdeAdapter geometrySerdeAdapter; + + public CustomGeometrySerde() { + this(new GeometrySerdeAdapterJtsWkb(geometryFactory)); + } + + public CustomGeometrySerde(GeometrySerdeAdapter geometrySerdeAdapter) { + super(); + this.geometrySerdeAdapter = geometrySerdeAdapter; + } + + @Override + public void write(Kryo kryo, Output out, Object object) + { + if (object instanceof Circle) { + Circle circle = (Circle) object; + writeType(out, Type.CIRCLE); + out.writeDouble(circle.getRadius()); + writeGeometry(kryo, out, circle.getCenterGeometry()); + writeUserData(kryo, out, circle); + } + else if (object instanceof Point || object instanceof LineString + || object instanceof Polygon || object instanceof MultiPoint + || object instanceof MultiLineString || object instanceof MultiPolygon) { + writeType(out, Type.SHAPE); + writeGeometry(kryo, out, (Geometry) object); + } + else if (object instanceof GeometryCollection) { + GeometryCollection collection = (GeometryCollection) object; + writeType(out, Type.GEOMETRYCOLLECTION); + out.writeInt(collection.getNumGeometries()); + for (int i = 0; i < collection.getNumGeometries(); i++) { + writeGeometry(kryo, out, collection.getGeometryN(i)); + } + writeUserData(kryo, out, collection); + } + else if (object instanceof Envelope) { + Envelope envelope = (Envelope) object; + writeType(out, Type.ENVELOPE); + out.writeDouble(envelope.getMinX()); + out.writeDouble(envelope.getMaxX()); + out.writeDouble(envelope.getMinY()); + out.writeDouble(envelope.getMaxY()); + } else if (object instanceof Node) { + writeType(out, Type.URI); + kryo.writeObject(out, object); + } + else { + throw new UnsupportedOperationException("Cannot serialize object of type " + + object.getClass().getName()); + } + } + + private void writeType(Output out, Type type) + { + out.writeByte((byte) type.id); + } + + private void writeGeometry(Kryo kryo, Output out, Geometry geometry) + { + geometrySerdeAdapter.write(kryo, out, geometry); + writeUserData(kryo, out, geometry); + } + + private void writeUserData(Kryo kryo, Output out, Geometry geometry) + { + out.writeBoolean(geometry.getUserData() != null); + if (geometry.getUserData() != null) { + kryo.writeClass(out, geometry.getUserData().getClass()); + kryo.writeObject(out, geometry.getUserData()); + } + } + + @Override + public Object read(Kryo kryo, Input input, Class aClass) + { + byte typeId = input.readByte(); + Type geometryType = Type.fromId(typeId); + switch (geometryType) { + case SHAPE: + return readGeometry(kryo, input); + case CIRCLE: { + double radius = input.readDouble(); + Geometry centerGeometry = readGeometry(kryo, input); + Object userData = readUserData(kryo, input); + + Circle circle = new Circle(centerGeometry, radius); + circle.setUserData(userData); + return circle; + } + case GEOMETRYCOLLECTION: { + int numGeometries = input.readInt(); + Geometry[] geometries = new Geometry[numGeometries]; + for (int i = 0; i < numGeometries; i++) { + geometries[i] = readGeometry(kryo, input); + } + GeometryCollection collection = geometryFactory.createGeometryCollection(geometries); + collection.setUserData(readUserData(kryo, input)); + return collection; + } + case ENVELOPE: { + double xMin = input.readDouble(); + double xMax = input.readDouble(); + double yMin = input.readDouble(); + double yMax = input.readDouble(); + return new Envelope(xMin, xMax, yMin, yMax); + } + case URI: + return kryo.readObject(input, Node.class); + default: + throw new UnsupportedOperationException( + "Cannot deserialize object of type " + geometryType); + } + } + + private Object readUserData(Kryo kryo, Input input) + { + Object userData = null; + if (input.readBoolean()) { + Registration clazz = kryo.readClass(input); + userData = kryo.readObject(input, clazz.getType()); + } + return userData; + } + + private Geometry readGeometry(Kryo kryo, Input input) + { + Geometry geometry; + try { + geometry = geometrySerdeAdapter.read(kryo, input); + } catch (Exception e) { + throw new RuntimeException(e); + } + + geometry.setUserData(readUserData(kryo, input)); + return geometry; + } + + private enum Type + { + SHAPE(0), + CIRCLE(1), + GEOMETRYCOLLECTION(2), + ENVELOPE(3), + + URI(4) + ; + + private final int id; + + Type(int id) + { + this.id = id; + } + + public static Type fromId(int id) + { + for (Type type : values()) { + if (type.id == id) { + return type; + } + } + + return null; + } + } +} diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/CustomSpatialIndexSerde.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/CustomSpatialIndexSerde.java new file mode 100644 index 00000000000..cfbea391a22 --- /dev/null +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/CustomSpatialIndexSerde.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial.serde; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.sedona.common.geometrySerde.GeometrySerde; +import org.apache.sedona.common.geometrySerde.SpatialIndexSerde; +import org.locationtech.jts.index.strtree.STRtree; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +public class CustomSpatialIndexSerde extends SpatialIndexSerde { + public CustomSpatialIndexSerde(GeometrySerde geometrySerde) { + super(geometrySerde); + } + + @Override + public void write(Kryo kryo, Output output, Object o) { + if (o instanceof STRtree) { + //serialize rtree index + output.writeByte((byte) 1); + STRtree tree = (STRtree) o; + org.locationtech.jts.index.strtree.IndexSerde indexSerde + = new org.locationtech.jts.index.strtree.IndexSerde(); + try { + FieldUtils.writeField(indexSerde, "geometrySerde", new CustomGeometrySerde(), true); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + indexSerde.write(kryo, output, tree); + } else { + super.write(kryo, output, o); + } + } + + @Override + public Object read(Kryo kryo, Input input, Class aClass) { + byte typeID = input.readByte(); + if (typeID == 1) { + org.locationtech.jts.index.strtree.IndexSerde indexSerde = + new org.locationtech.jts.index.strtree.IndexSerde(); + try { + FieldUtils.writeField(indexSerde, "geometrySerde", new CustomGeometrySerde(), true); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + return indexSerde.read(kryo, input); + } else { + return super.read(kryo, input, aClass); + } + + } +} diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/GeometrySerdeAdapter.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/GeometrySerdeAdapter.java new file mode 100644 index 00000000000..ac6a6c8e2f1 --- /dev/null +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/GeometrySerdeAdapter.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial.serde; + +import org.locationtech.jts.geom.Geometry; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +/** Interface to abstract de-/serialization of geometries and byte arrays. */ +public interface GeometrySerdeAdapter { + void write(Kryo kryo, Output output, Geometry geometry); + Geometry read(Kryo kryo, Input input) throws Exception; +} diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/GeometrySerdeAdapterJtsWkb.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/GeometrySerdeAdapterJtsWkb.java new file mode 100644 index 00000000000..ed9c99248fa --- /dev/null +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/GeometrySerdeAdapterJtsWkb.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial.serde; + +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.io.WKBReader; +import org.locationtech.jts.io.WKBWriter; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +/** Geometry de-/serialization via the WKB facilities of JTS. */ +public class GeometrySerdeAdapterJtsWkb + implements GeometrySerdeAdapter +{ + private WKBReader geometryReader; + private WKBWriter geometryWriter; + + public GeometrySerdeAdapterJtsWkb(GeometryFactory geometryFactory) { + super(); + this.geometryReader = new WKBReader(geometryFactory); + this.geometryWriter = new WKBWriter(); + } + + @Override + public void write(Kryo kryo, Output output, Geometry geometry) { + byte[] data = geometryWriter.write(geometry); + output.write(data, 0, data.length); + } + + @Override + public Geometry read(Kryo kryo, Input input) throws Exception { + byte[] bytes = kryo.readObject(input, byte[].class); + Geometry geometry = geometryReader.read(bytes); + return geometry; + } +} diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/GeometrySerdeAdapterShapeSerde.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/GeometrySerdeAdapterShapeSerde.java new file mode 100644 index 00000000000..956d598d41c --- /dev/null +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/GeometrySerdeAdapterShapeSerde.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial.serde; + +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +/** + * Geometry de-/serialization via ShapeSerde of Apache sedona-spark. + * This is a stub, because ShapeSerde currently draws in many dependencies; some which cause + * issues with the maven enforcer plugin. + */ +public class GeometrySerdeAdapterShapeSerde + implements GeometrySerdeAdapter +{ + protected GeometryFactory geometryFactory; + + public GeometrySerdeAdapterShapeSerde(GeometryFactory geometryFactory) { + super(); + this.geometryFactory = geometryFactory; + } + + @Override + public void write(Kryo kryo, Output output, Geometry geometry) { + // byte[] data = ShapeSerde.serialize(geometry); + // output.write(data, 0, data.length); + throw new UnsupportedOperationException(); + } + + @Override + public Geometry read(Kryo kryo, Input input) throws Exception { + // Geometry geometry = ShapeSerde.deserialize(input, geometryFactory); + // return geometry + throw new UnsupportedOperationException(); + } +} diff --git a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/JtsKryoRegistrator.java b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/JtsKryoRegistrator.java new file mode 100644 index 00000000000..473df87639c --- /dev/null +++ b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/serde/JtsKryoRegistrator.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial.serde; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.jena.graph.Node; +import org.apache.jena.graph.NodeFactory; +import org.apache.jena.graph.Node_Blank; +import org.apache.jena.graph.Node_URI; +import org.apache.sedona.common.geometrySerde.GeometrySerde; +import org.apache.sedona.common.geometrySerde.SpatialIndexSerde; +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.GeometryCollection; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.MultiLineString; +import org.locationtech.jts.geom.MultiPoint; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.index.quadtree.Quadtree; +import org.locationtech.jts.index.strtree.STRtree; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.kryo.serializers.MapSerializer; + +public class JtsKryoRegistrator { + + final static Logger log = LoggerFactory.getLogger(JtsKryoRegistrator.class); + + public static void registerClasses(Kryo kryo) { + GeometrySerde serializer = new CustomGeometrySerde(); + SpatialIndexSerde indexSerializer = new CustomSpatialIndexSerde(serializer); + + log.debug("Registering custom serializers for geometry types"); + + kryo.register(Point.class, serializer); + kryo.register(LineString.class, serializer); + kryo.register(Polygon.class, serializer); + kryo.register(MultiPoint.class, serializer); + kryo.register(MultiLineString.class, serializer); + kryo.register(MultiPolygon.class, serializer); + kryo.register(GeometryCollection.class, serializer); + kryo.register(Envelope.class, serializer); + + kryo.register(Quadtree.class, indexSerializer); + kryo.register(STRtree.class, indexSerializer); + kryo.register(Node.class, new NodeSerializer()); + kryo.register(Node_URI.class, new NodeSerializer()); + kryo.register(Node_Blank.class, new NodeSerializer()); + + kryo.register(HashMap.class, new MapSerializer()); + kryo.register(Map.class, new MapSerializer()); + } + + static class NodeSerializer extends Serializer { + + @Override + public void write(Kryo kryo, Output output, Node node) { + output.writeString(node.getURI()); + } + + @Override + public Node read(Kryo kryo, Input input, Class aClass) { + return NodeFactory.createURI(input.readString()); + } + } +} diff --git a/jena-geosparql/src/test/java/org/apache/jena/geosparql/geo/topological/CancelQueryTest.java b/jena-geosparql/src/test/java/org/apache/jena/geosparql/geo/topological/CancelQueryTest.java index 7b54ebb9049..c3da3ee0919 100644 --- a/jena-geosparql/src/test/java/org/apache/jena/geosparql/geo/topological/CancelQueryTest.java +++ b/jena-geosparql/src/test/java/org/apache/jena/geosparql/geo/topological/CancelQueryTest.java @@ -31,8 +31,9 @@ import org.apache.jena.geosparql.implementation.datatype.WKTDatatype; import org.apache.jena.geosparql.implementation.index.IndexConfiguration; import org.apache.jena.geosparql.implementation.vocabulary.Geo; -import org.apache.jena.geosparql.spatial.SpatialIndex; import org.apache.jena.geosparql.spatial.SpatialIndexException; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexUtils; import org.apache.jena.graph.Graph; import org.apache.jena.graph.NodeFactory; import org.apache.jena.query.Dataset; @@ -106,8 +107,8 @@ public void test_cancel_spatial_property_function1() { // create spatial index if (useIndex){ try { - SpatialIndex index = SpatialIndex.buildSpatialIndex(ds); - SpatialIndex.setSpatialIndex(ds, index); + SpatialIndex index = SpatialIndexUtils.buildSpatialIndex(ds.asDatasetGraph()); + SpatialIndexUtils.setSpatialIndex(ds, index); } catch (SpatialIndexException e) { throw new RuntimeException(e); } diff --git a/jena-geosparql/src/test/java/org/apache/jena/geosparql/geo/topological/GenericPropertyFunctionTest.java b/jena-geosparql/src/test/java/org/apache/jena/geosparql/geo/topological/GenericPropertyFunctionTest.java index 5a99718258f..376a3dd714b 100644 --- a/jena-geosparql/src/test/java/org/apache/jena/geosparql/geo/topological/GenericPropertyFunctionTest.java +++ b/jena-geosparql/src/test/java/org/apache/jena/geosparql/geo/topological/GenericPropertyFunctionTest.java @@ -17,7 +17,17 @@ */ package org.apache.jena.geosparql.geo.topological; -import static org.apache.jena.geosparql.geo.topological.QueryRewriteTestData.*; +import static org.apache.jena.geosparql.geo.topological.QueryRewriteTestData.FEATURE_A; +import static org.apache.jena.geosparql.geo.topological.QueryRewriteTestData.FEATURE_B; +import static org.apache.jena.geosparql.geo.topological.QueryRewriteTestData.FEATURE_D; +import static org.apache.jena.geosparql.geo.topological.QueryRewriteTestData.GEOMETRY_A; +import static org.apache.jena.geosparql.geo.topological.QueryRewriteTestData.GEOMETRY_B; +import static org.apache.jena.geosparql.geo.topological.QueryRewriteTestData.GEOMETRY_C_BLANK; +import static org.apache.jena.geosparql.geo.topological.QueryRewriteTestData.GEOMETRY_D; +import static org.apache.jena.geosparql.geo.topological.QueryRewriteTestData.GEOMETRY_F; +import static org.apache.jena.geosparql.geo.topological.QueryRewriteTestData.GEO_FEATURE_Y; +import static org.apache.jena.geosparql.geo.topological.QueryRewriteTestData.GEO_FEATURE_Z; +import static org.apache.jena.geosparql.geo.topological.QueryRewriteTestData.TEST_SRS_URI; import static org.junit.Assert.assertEquals; import java.util.ArrayList; @@ -30,16 +40,24 @@ import org.apache.jena.geosparql.implementation.index.IndexConfiguration.IndexOption; import org.apache.jena.geosparql.implementation.index.QueryRewriteIndex; import org.apache.jena.geosparql.implementation.vocabulary.Geo; -import org.apache.jena.geosparql.spatial.SpatialIndex; import org.apache.jena.geosparql.spatial.SpatialIndexException; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexUtils; import org.apache.jena.graph.Graph; import org.apache.jena.graph.Node; import org.apache.jena.graph.NodeFactory; -import org.apache.jena.query.*; +import org.apache.jena.query.Dataset; +import org.apache.jena.query.QueryExecution; +import org.apache.jena.query.QueryExecutionFactory; +import org.apache.jena.query.QuerySolution; +import org.apache.jena.query.ResultSet; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.Resource; import org.apache.jena.vocabulary.RDF; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; /** * @@ -57,7 +75,7 @@ public GenericPropertyFunctionTest() { public static void setUpClass() throws SpatialIndexException { GeoSPARQLConfig.setup(IndexOption.MEMORY, Boolean.TRUE); model = QueryRewriteTestData.createTestData(); - dataset = SpatialIndex.wrapModel(model, TEST_SRS_URI); + dataset = SpatialIndexUtils.wrapModel(model, TEST_SRS_URI); } @AfterClass diff --git a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/AbstractSpatialIndexGraphLookpTest.java b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/AbstractSpatialIndexGraphLookpTest.java new file mode 100644 index 00000000000..b34658549b7 --- /dev/null +++ b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/AbstractSpatialIndexGraphLookpTest.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial; + +import org.apache.jena.geosparql.implementation.vocabulary.SRS_URI; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex; +import org.apache.jena.query.ResultSet; +import org.apache.jena.query.ResultSetFormatter; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFParserBuilder; +import org.apache.jena.sparql.algebra.Table; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.exec.QueryExec; +import org.junit.Assert; +import org.junit.Test; + +public abstract class AbstractSpatialIndexGraphLookpTest { + protected abstract SpatialIndex buildSpatialIndex(DatasetGraph dsg, String srsUri) throws SpatialIndexException; + + private static boolean enableDebugPrint = false; + + private static void debugPrint(Table table) { + if (enableDebugPrint) { + System.err.println(ResultSetFormatter.asText(ResultSet.adapt(table.toRowSet()))); + } + } + + // SpatialIndexUtils.buildSpatialIndex(dsg, SRS_URI.DEFAULT_WKT_CRS84); + @Test + public void mustNotMatchDefaultGraph1() throws SpatialIndexException { + DatasetGraph dsg = RDFParserBuilder.create().fromString( """ + PREFIX eg: + PREFIX geo: + + eg:graph1 { + eg:feature1 geo:hasGeometry eg:geometry1 . + eg:geometry1 geo:asWKT "POINT (0.3 0.3)"^^geo:wktLiteral . + } + + eg:graph2 { + eg:feature1 geo:hasGeometry eg:geometry1 . + eg:geometry1 geo:asWKT "POINT (0.7 0.7)"^^geo:wktLiteral . + } + """).lang(Lang.TRIG).toDatasetGraph(); + + String queryStr = """ + PREFIX eg: + PREFIX spatial: + PREFIX geo: + + SELECT * { + VALUES ?search { + "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"^^geo:wktLiteral + } + LATERAL { + # GRAPH eg:graph1 { ?feature spatial:intersectBoxGeom(?search) . } + ?feature spatial:intersectBoxGeom(?search) . + } + } + """; + + buildSpatialIndex(dsg, SRS_URI.DEFAULT_WKT_CRS84); + Table table = QueryExec.dataset(dsg).query(queryStr).table(); + debugPrint(table); + Assert.assertTrue(table.isEmpty()); + } + + @Test + public void mustNotMatchDefaultGraph2() throws SpatialIndexException { + DatasetGraph dsg = RDFParserBuilder.create().fromString( """ + PREFIX eg: + PREFIX geo: + + # Feature in default graph is outside of query polygon + eg:feature1 geo:hasGeometry eg:geometry1 . + eg:geometry1 geo:asWKT "POINT (-10 -10)"^^geo:wktLiteral . + + eg:graph1 { + eg:feature1 geo:hasGeometry eg:geometry1 . + eg:geometry1 geo:asWKT "POINT (0.3 0.3)"^^geo:wktLiteral . + } + + eg:graph2 { + eg:feature1 geo:hasGeometry eg:geometry1 . + eg:geometry1 geo:asWKT "POINT (0.7 0.7)"^^geo:wktLiteral . + } + """).lang(Lang.TRIG).toDatasetGraph(); + + String queryStr = """ + PREFIX eg: + PREFIX spatial: + PREFIX geo: + + SELECT * { + VALUES ?search { + "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"^^geo:wktLiteral + } + LATERAL { + ?feature spatial:intersectBoxGeom(?search) . + } + } + """; + + buildSpatialIndex(dsg, SRS_URI.DEFAULT_WKT_CRS84); + Table table = QueryExec.dataset(dsg).query(queryStr).table(); + debugPrint(table); + Assert.assertTrue(table.isEmpty()); + } + + @Test + public void mustNotMatchNamedGraph() throws SpatialIndexException { + DatasetGraph dsg = RDFParserBuilder.create().fromString( """ + PREFIX eg: + PREFIX geo: + + eg:graph1 { + eg:feature1 geo:hasGeometry eg:geometry1 . + eg:geometry1 geo:asWKT "POINT (-10 -10)"^^geo:wktLiteral . + } + + eg:graph2 { + eg:feature1 geo:hasGeometry eg:geometry1 . + eg:geometry1 geo:asWKT "POINT (0.7 0.7)"^^geo:wktLiteral . + } + """).lang(Lang.TRIG).toDatasetGraph(); + + String queryStr = """ + PREFIX eg: + PREFIX spatial: + PREFIX geo: + + SELECT * { + VALUES ?search { + "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"^^geo:wktLiteral + } + LATERAL { + GRAPH eg:graph1 { ?feature spatial:intersectBoxGeom(?search) . } + } + } + """; + + buildSpatialIndex(dsg, SRS_URI.DEFAULT_WKT_CRS84); + Table table = QueryExec.dataset(dsg).query(queryStr).table(); + debugPrint(table); + Assert.assertTrue(table.isEmpty()); + } + + @Test + public void mustMatchNamedGraph() throws SpatialIndexException { + DatasetGraph dsg = RDFParserBuilder.create().fromString( """ + PREFIX eg: + PREFIX geo: + + eg:graph1 { + eg:feature1 geo:hasGeometry eg:geometry1 . + eg:geometry1 geo:asWKT "POINT (-10 -10)"^^geo:wktLiteral . + } + + eg:graph2 { + eg:feature1 geo:hasGeometry eg:geometry1 . + eg:geometry1 geo:asWKT "POINT (0.7 0.7)"^^geo:wktLiteral . + } + """).lang(Lang.TRIG).toDatasetGraph(); + + String queryStr = """ + PREFIX eg: + PREFIX spatial: + PREFIX geo: + + SELECT * { + VALUES ?search { + "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))"^^geo:wktLiteral + } + LATERAL { + GRAPH eg:graph2 { ?feature spatial:intersectBoxGeom(?search) . } + } + } + """; + + buildSpatialIndex(dsg, SRS_URI.DEFAULT_WKT_CRS84); + Table table = QueryExec.dataset(dsg).query(queryStr).table(); + debugPrint(table); + Assert.assertFalse(table.isEmpty()); + } +} diff --git a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/SearchEnvelopeTest.java b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/SearchEnvelopeTest.java index 371f18f1ad0..8fcf28d586a 100644 --- a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/SearchEnvelopeTest.java +++ b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/SearchEnvelopeTest.java @@ -17,15 +17,18 @@ */ package org.apache.jena.geosparql.spatial; -import java.util.Arrays; -import java.util.HashSet; +import static org.junit.Assert.assertEquals; + +import java.util.Collection; +import java.util.Set; + import org.apache.jena.geosparql.implementation.GeometryWrapper; import org.apache.jena.geosparql.implementation.datatype.WKTDatatype; import org.apache.jena.geosparql.implementation.vocabulary.Unit_URI; -import org.apache.jena.rdf.model.Resource; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex; +import org.apache.jena.graph.Node; import org.junit.After; import org.junit.AfterClass; -import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -364,8 +367,8 @@ public void testCheck() { SearchEnvelope instance = SearchEnvelope.build(geometryWrapper, SpatialIndexTestData.WGS_84_SRS_INFO, radius, unitsURI); //Function Test - HashSet expResult = new HashSet<>(Arrays.asList(SpatialIndexTestData.LONDON_FEATURE)); - HashSet result = instance.check(spatialIndex); + Set expResult = Set.of(SpatialIndexTestData.LONDON_FEATURE.asNode()); + Collection result = instance.check(spatialIndex); assertEquals(expResult, result); } @@ -384,8 +387,8 @@ public void testCheck_empty() { SearchEnvelope instance = SearchEnvelope.build(geometryWrapper, SpatialIndexTestData.WGS_84_SRS_INFO, radius, unitsURI); //Function Test - HashSet expResult = new HashSet<>(); - HashSet result = instance.check(spatialIndex); + Collection expResult = Set.of(); + Collection result = instance.check(spatialIndex); assertEquals(expResult, result); } diff --git a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/SpatialIndexTest.java b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/SpatialIndexTest.java new file mode 100644 index 00000000000..dea2e040c59 --- /dev/null +++ b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/SpatialIndexTest.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.apache.jena.geosparql.implementation.GeometryWrapperFactory; +import org.apache.jena.geosparql.implementation.SRSInfo; +import org.apache.jena.geosparql.implementation.datatype.WKTDatatype; +import org.apache.jena.geosparql.implementation.vocabulary.SRS_URI; +import org.apache.jena.geosparql.spatial.index.compat.SpatialIndexIo; +import org.apache.jena.geosparql.spatial.index.v1.SpatialIndexV1; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexIoKryo; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexPerGraph; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexUtils; +import org.apache.jena.graph.Node; +import org.apache.jena.query.ResultSet; +import org.apache.jena.query.ResultSetFormatter; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFParserBuilder; +import org.apache.jena.sparql.algebra.Table; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.exec.QueryExec; +import org.junit.Assert; +import org.junit.Test; +import org.locationtech.jts.geom.Envelope; + +public class SpatialIndexTest { + + @Test + public void testLegacyLoading() throws IOException, SpatialIndexException { + Path file = Files.createTempFile("jena", "spatial.index"); + try { + List items = SpatialIndexTestData.getTestItems(); + + SpatialIndexV1.save(file.toFile(), items, SRS_URI.DEFAULT_WKT_CRS84); + + SpatialIndex index = SpatialIndexIo.load(file, true); + Envelope envelope = new Envelope(-90, 0, 0, 90); + Collection actual = index.query(envelope, null); + Set expected = Set.of(SpatialIndexTestData.LONDON_FEATURE.asNode(), SpatialIndexTestData.NEW_YORK_FEATURE.asNode()); + Assert.assertEquals(expected, actual); + } finally { + Files.delete(file); + } + } + + @Test + public void testSerdeSpatialIndex() throws IOException, SpatialIndexException { + // create spatial index + SpatialIndexPerGraph index1 = SpatialIndexTestData.createTestIndex(); + + // query index 1 + SRSInfo srsInfo1 = index1.getSrsInfo(); + SearchEnvelope searchEnvelope1 = SearchEnvelope.build(GeometryWrapperFactory.createPolygon(srsInfo1.getDomainEnvelope(), WKTDatatype.URI), srsInfo1); + Collection res1 = searchEnvelope1.check(index1); + + // save to tmp file + // File file = new File("/tmp/test-spatial.index"); //File.createTempFile( "jena", "spatial.index"); + Path file = Files.createTempFile("jena", "spatial.index"); + try { + SpatialIndexIoKryo.save(file, index1); + + // load from tmp file as new index 2 + SpatialIndex index2 = SpatialIndexIo.load(file); + + // query index 2 + SRSInfo srsInfo2 = index2.getSrsInfo(); + SearchEnvelope searchEnvelope2 = SearchEnvelope.build(GeometryWrapperFactory.createPolygon(srsInfo2.getDomainEnvelope(), WKTDatatype.URI), srsInfo2); + Collection res2 = searchEnvelope2.check(index2); + + assertEquals(srsInfo1, srsInfo2); + assertEquals(res1, res2); + } finally { + Files.delete(file); + } + } + +} diff --git a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/SpatialIndexTestData.java b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/SpatialIndexTestData.java index b3ec8949988..074607deb8f 100644 --- a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/SpatialIndexTestData.java +++ b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/SpatialIndexTestData.java @@ -17,17 +17,29 @@ */ package org.apache.jena.geosparql.spatial; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + import org.apache.jena.geosparql.implementation.GeometryWrapper; import org.apache.jena.geosparql.implementation.SRSInfo; import org.apache.jena.geosparql.implementation.datatype.WKTDatatype; import org.apache.jena.geosparql.implementation.vocabulary.Geo; import org.apache.jena.geosparql.implementation.vocabulary.SRS_URI; +import org.apache.jena.geosparql.spatial.index.v2.STRtreePerGraph; +import org.apache.jena.geosparql.spatial.index.v2.STRtreeUtils; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexPerGraph; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexUtils; +import org.apache.jena.graph.Node; import org.apache.jena.query.Dataset; import org.apache.jena.query.DatasetFactory; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.ResourceFactory; +import org.locationtech.jts.index.strtree.STRtree; /** * @@ -60,21 +72,28 @@ public class SpatialIndexTestData { public static final SRSInfo WGS_84_SRS_INFO = new SRSInfo(SRS_URI.WGS84_CRS); public static final SRSInfo OSGB_SRS_INFO = new SRSInfo(SRS_URI.OSGB36_CRS); - private static SpatialIndex TEST_SPATIAL_INDEX = null; + private static SpatialIndexPerGraph TEST_SPATIAL_INDEX = null; private static Dataset TEST_DATASET = null; - public static final SpatialIndex createTestIndex() { + public static final List getTestItems() { + List items = List.of( + SpatialIndexItem.of(LONDON_GEOMETRY_WRAPPER.getEnvelope(), LONDON_FEATURE), + SpatialIndexItem.of(NEW_YORK_GEOMETRY_WRAPPER.getEnvelope(), NEW_YORK_FEATURE), + SpatialIndexItem.of(HONOLULU_GEOMETRY_WRAPPER.getEnvelope(), HONOLULU_FEATURE), + SpatialIndexItem.of(PERTH_GEOMETRY_WRAPPER.getEnvelope(), PERTH_FEATURE), + SpatialIndexItem.of(AUCKLAND_GEOMETRY_WRAPPER.getEnvelope(), AUCKLAND_FEATURE)); + return items; + } + + public static final SpatialIndexPerGraph createTestIndex() { if (TEST_SPATIAL_INDEX == null) { try { - SpatialIndex spatialIndex = new SpatialIndex(100, SRS_URI.WGS84_CRS); - spatialIndex.insertItem(LONDON_GEOMETRY_WRAPPER.getEnvelope(), LONDON_FEATURE); - spatialIndex.insertItem(NEW_YORK_GEOMETRY_WRAPPER.getEnvelope(), NEW_YORK_FEATURE); - spatialIndex.insertItem(HONOLULU_GEOMETRY_WRAPPER.getEnvelope(), HONOLULU_FEATURE); - spatialIndex.insertItem(PERTH_GEOMETRY_WRAPPER.getEnvelope(), PERTH_FEATURE); - spatialIndex.insertItem(AUCKLAND_GEOMETRY_WRAPPER.getEnvelope(), AUCKLAND_FEATURE); - - spatialIndex.build(); + // SpatialIndexPerGraph spatialIndex = new SpatialIndexPerGraph(100, SRS_URI.WGS84_CRS); + List items = getTestItems(); + STRtree tree = STRtreeUtils.buildSpatialIndexTree(items); + STRtreePerGraph index = new STRtreePerGraph(tree); + SpatialIndexPerGraph spatialIndex = new SpatialIndexPerGraph(index); TEST_SPATIAL_INDEX = spatialIndex; } catch (SpatialIndexException ex) { @@ -102,11 +121,15 @@ public static final Dataset createTestDataset() { dataset.setDefaultModel(model); SpatialIndex spatialIndex = createTestIndex(); - SpatialIndex.setSpatialIndex(dataset, spatialIndex); + SpatialIndexUtils.setSpatialIndex(dataset, spatialIndex); TEST_DATASET = dataset; } return TEST_DATASET; } + public static Set asNodes(Collection resources) { + return resources.stream().map(Resource::asNode).collect(Collectors.toSet()); + } + } diff --git a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/TestSpatialIndexGraphLookupV1.java b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/TestSpatialIndexGraphLookupV1.java new file mode 100644 index 00000000000..4f7a664031d --- /dev/null +++ b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/TestSpatialIndexGraphLookupV1.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial; + +import org.apache.jena.geosparql.spatial.index.v1.SpatialIndexV1; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexAdapterV1; +import org.apache.jena.query.DatasetFactory; +import org.apache.jena.sparql.core.DatasetGraph; +import org.junit.Ignore; + +@Ignore +public class TestSpatialIndexGraphLookupV1 + extends AbstractSpatialIndexGraphLookpTest +{ + @Override + protected SpatialIndex buildSpatialIndex(DatasetGraph dsg, String srsUri) throws SpatialIndexException { + SpatialIndexV1 v1 = SpatialIndexV1.buildSpatialIndex(DatasetFactory.wrap(dsg), srsUri); + return new SpatialIndexAdapterV1(v1); + } +} diff --git a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/TestSpatialIndexGraphLookupV2.java b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/TestSpatialIndexGraphLookupV2.java new file mode 100644 index 00000000000..32a1eb94013 --- /dev/null +++ b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/TestSpatialIndexGraphLookupV2.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.geosparql.spatial; + +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexUtils; +import org.apache.jena.sparql.core.DatasetGraph; + +public class TestSpatialIndexGraphLookupV2 + extends AbstractSpatialIndexGraphLookpTest +{ + @Override + protected SpatialIndex buildSpatialIndex(DatasetGraph dsg, String srsUri) throws SpatialIndexException { + return SpatialIndexUtils.buildSpatialIndex(dsg, srsUri); + } +} diff --git a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/GenericSpatialPropertyFunctionTest.java b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/GenericSpatialPropertyFunctionTest.java index 724f4d2dacc..0b2ff3e6e88 100644 --- a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/GenericSpatialPropertyFunctionTest.java +++ b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/GenericSpatialPropertyFunctionTest.java @@ -17,15 +17,18 @@ */ package org.apache.jena.geosparql.spatial.property_functions; +import static org.junit.Assert.assertEquals; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; + import org.apache.jena.datatypes.xsd.XSDDatatype; import org.apache.jena.geosparql.configuration.GeoSPARQLConfig; import org.apache.jena.geosparql.implementation.vocabulary.SpatialExtension; -import org.apache.jena.geosparql.spatial.SpatialIndex; import org.apache.jena.geosparql.spatial.SpatialIndexException; import org.apache.jena.geosparql.spatial.SpatialIndexTestData; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexUtils; import org.apache.jena.query.Dataset; import org.apache.jena.query.QueryExecution; import org.apache.jena.query.QueryExecutionFactory; @@ -37,7 +40,6 @@ import org.apache.jena.rdf.model.ResourceFactory; import org.junit.After; import org.junit.AfterClass; -import static org.junit.Assert.*; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -201,7 +203,7 @@ public void testExecEvaluated_Nearby_geo() throws SpatialIndexException { Resource geoFeature = ResourceFactory.createResource("http://example.org/GeoFeatureX"); model.add(geoFeature, SpatialExtension.GEO_LAT_PROP, ResourceFactory.createTypedLiteral("0.0", XSDDatatype.XSDfloat)); model.add(geoFeature, SpatialExtension.GEO_LON_PROP, ResourceFactory.createTypedLiteral("0.0", XSDDatatype.XSDfloat)); - Dataset dataset = SpatialIndex.wrapModel(model); + Dataset dataset = SpatialIndexUtils.wrapModel(model); String query = "PREFIX spatial: \n" + "\n" diff --git a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/EastGeomPFTest.java b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/EastGeomPFTest.java index 91e6cb00e73..436aad91879 100644 --- a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/EastGeomPFTest.java +++ b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/EastGeomPFTest.java @@ -17,16 +17,21 @@ */ package org.apache.jena.geosparql.spatial.property_functions.cardinal; +import static org.junit.Assert.assertEquals; + import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; +import java.util.Collection; import java.util.List; +import java.util.Set; + import org.apache.jena.geosparql.configuration.GeoSPARQLConfig; import org.apache.jena.geosparql.implementation.GeometryWrapper; import org.apache.jena.geosparql.spatial.CardinalDirection; import org.apache.jena.geosparql.spatial.SearchEnvelope; -import org.apache.jena.geosparql.spatial.SpatialIndex; import org.apache.jena.geosparql.spatial.SpatialIndexTestData; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex; +import org.apache.jena.graph.Node; import org.apache.jena.query.Dataset; import org.apache.jena.query.QueryExecution; import org.apache.jena.query.QueryExecutionFactory; @@ -35,7 +40,6 @@ import org.apache.jena.rdf.model.Resource; import org.junit.After; import org.junit.AfterClass; -import static org.junit.Assert.*; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -91,8 +95,10 @@ public void testCheckSearchEnvelope_no_wrap() { GeometryWrapper geometryWrapper = SpatialIndexTestData.HONOLULU_GEOMETRY_WRAPPER; EastGeomPF instance = new EastGeomPF(); SearchEnvelope searchEnvelope = instance.buildSearchEnvelope(geometryWrapper, SpatialIndexTestData.WGS_84_SRS_INFO); //Needed to initialise the search. - HashSet expResult = new HashSet<>(Arrays.asList(SpatialIndexTestData.LONDON_FEATURE, SpatialIndexTestData.HONOLULU_FEATURE, SpatialIndexTestData.NEW_YORK_FEATURE)); - HashSet result = searchEnvelope.check(spatialIndex); + Set expResult = SpatialIndexTestData.asNodes( + List.of(SpatialIndexTestData.LONDON_FEATURE, SpatialIndexTestData.HONOLULU_FEATURE, SpatialIndexTestData.NEW_YORK_FEATURE)); + Collection result = searchEnvelope.check(spatialIndex); + assertEquals(expResult, result); } @@ -108,8 +114,9 @@ public void testCheckSearchEnvelope_wrap() { GeometryWrapper geometryWrapper = SpatialIndexTestData.PERTH_GEOMETRY_WRAPPER; EastGeomPF instance = new EastGeomPF(); SearchEnvelope searchEnvelope = instance.buildSearchEnvelope(geometryWrapper, SpatialIndexTestData.WGS_84_SRS_INFO); //Needed to initialise the search. - HashSet expResult = new HashSet<>(Arrays.asList(SpatialIndexTestData.AUCKLAND_FEATURE, SpatialIndexTestData.PERTH_FEATURE, SpatialIndexTestData.HONOLULU_FEATURE, SpatialIndexTestData.NEW_YORK_FEATURE)); - HashSet result = searchEnvelope.check(spatialIndex); + Set expResult = SpatialIndexTestData.asNodes( + List.of(SpatialIndexTestData.AUCKLAND_FEATURE, SpatialIndexTestData.PERTH_FEATURE, SpatialIndexTestData.HONOLULU_FEATURE, SpatialIndexTestData.NEW_YORK_FEATURE)); + Collection result = searchEnvelope.check(spatialIndex); assertEquals(expResult, result); } diff --git a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/EastPFTest.java b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/EastPFTest.java index 38896523c06..4e2f5f8c6b1 100644 --- a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/EastPFTest.java +++ b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/EastPFTest.java @@ -17,16 +17,15 @@ */ package org.apache.jena.geosparql.spatial.property_functions.cardinal; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; +import java.util.*; + import org.apache.jena.geosparql.configuration.GeoSPARQLConfig; import org.apache.jena.geosparql.implementation.GeometryWrapper; import org.apache.jena.geosparql.spatial.CardinalDirection; import org.apache.jena.geosparql.spatial.SearchEnvelope; -import org.apache.jena.geosparql.spatial.SpatialIndex; import org.apache.jena.geosparql.spatial.SpatialIndexTestData; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex; +import org.apache.jena.graph.Node; import org.apache.jena.query.Dataset; import org.apache.jena.query.QueryExecution; import org.apache.jena.query.QueryExecutionFactory; @@ -91,8 +90,8 @@ public void testCheckSearchEnvelope_no_wrap() { GeometryWrapper geometryWrapper = SpatialIndexTestData.HONOLULU_GEOMETRY_WRAPPER; EastPF instance = new EastPF(); SearchEnvelope searchEnvelope = instance.buildSearchEnvelope(geometryWrapper, SpatialIndexTestData.WGS_84_SRS_INFO); //Needed to initialise the search. - HashSet expResult = new HashSet<>(Arrays.asList(SpatialIndexTestData.LONDON_FEATURE, SpatialIndexTestData.HONOLULU_FEATURE, SpatialIndexTestData.NEW_YORK_FEATURE)); - HashSet result = searchEnvelope.check(spatialIndex); + Set expResult = SpatialIndexTestData.asNodes(List.of(SpatialIndexTestData.LONDON_FEATURE, SpatialIndexTestData.HONOLULU_FEATURE, SpatialIndexTestData.NEW_YORK_FEATURE)); + Collection result = searchEnvelope.check(spatialIndex); assertEquals(expResult, result); } @@ -108,8 +107,8 @@ public void testCheckSearchEnvelope_wrap() { GeometryWrapper geometryWrapper = SpatialIndexTestData.PERTH_GEOMETRY_WRAPPER; EastPF instance = new EastPF(); SearchEnvelope searchEnvelope = instance.buildSearchEnvelope(geometryWrapper, SpatialIndexTestData.WGS_84_SRS_INFO); //Needed to initialise the search. - HashSet expResult = new HashSet<>(Arrays.asList(SpatialIndexTestData.AUCKLAND_FEATURE, SpatialIndexTestData.PERTH_FEATURE, SpatialIndexTestData.HONOLULU_FEATURE, SpatialIndexTestData.NEW_YORK_FEATURE)); - HashSet result = searchEnvelope.check(spatialIndex); + Set expResult = SpatialIndexTestData.asNodes(List.of(SpatialIndexTestData.AUCKLAND_FEATURE, SpatialIndexTestData.PERTH_FEATURE, SpatialIndexTestData.HONOLULU_FEATURE, SpatialIndexTestData.NEW_YORK_FEATURE)); + Collection result = searchEnvelope.check(spatialIndex); assertEquals(expResult, result); } diff --git a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/WestGeomPFTest.java b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/WestGeomPFTest.java index abda7afebf2..790f0e05e7b 100644 --- a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/WestGeomPFTest.java +++ b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/WestGeomPFTest.java @@ -17,16 +17,21 @@ */ package org.apache.jena.geosparql.spatial.property_functions.cardinal; +import static org.junit.Assert.assertEquals; + import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; +import java.util.Collection; import java.util.List; +import java.util.Set; + import org.apache.jena.geosparql.configuration.GeoSPARQLConfig; import org.apache.jena.geosparql.implementation.GeometryWrapper; import org.apache.jena.geosparql.spatial.CardinalDirection; import org.apache.jena.geosparql.spatial.SearchEnvelope; -import org.apache.jena.geosparql.spatial.SpatialIndex; import org.apache.jena.geosparql.spatial.SpatialIndexTestData; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex; +import org.apache.jena.graph.Node; import org.apache.jena.query.Dataset; import org.apache.jena.query.QueryExecution; import org.apache.jena.query.QueryExecutionFactory; @@ -35,7 +40,6 @@ import org.apache.jena.rdf.model.Resource; import org.junit.After; import org.junit.AfterClass; -import static org.junit.Assert.*; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -91,8 +95,8 @@ public void testCheckSearchEnvelope_no_wrap() { GeometryWrapper geometryWrapper = SpatialIndexTestData.PERTH_GEOMETRY_WRAPPER; WestGeomPF instance = new WestGeomPF(); SearchEnvelope searchEnvelope = instance.buildSearchEnvelope(geometryWrapper, SpatialIndexTestData.WGS_84_SRS_INFO); //Needed to initialise the search. - HashSet expResult = new HashSet<>(Arrays.asList(SpatialIndexTestData.LONDON_FEATURE, SpatialIndexTestData.PERTH_FEATURE)); - HashSet result = searchEnvelope.check(spatialIndex); + Set expResult = SpatialIndexTestData.asNodes(List.of(SpatialIndexTestData.LONDON_FEATURE, SpatialIndexTestData.PERTH_FEATURE)); + Collection result = searchEnvelope.check(spatialIndex); assertEquals(expResult, result); } @@ -108,8 +112,8 @@ public void testCheckSearchEnvelope_wrap() { GeometryWrapper geometryWrapper = SpatialIndexTestData.HONOLULU_GEOMETRY_WRAPPER; WestGeomPF instance = new WestGeomPF(); SearchEnvelope searchEnvelope = instance.buildSearchEnvelope(geometryWrapper, SpatialIndexTestData.WGS_84_SRS_INFO); //Needed to initialise the search. - HashSet expResult = new HashSet<>(Arrays.asList(SpatialIndexTestData.AUCKLAND_FEATURE, SpatialIndexTestData.PERTH_FEATURE, SpatialIndexTestData.HONOLULU_FEATURE)); - HashSet result = searchEnvelope.check(spatialIndex); + Set expResult = SpatialIndexTestData.asNodes(List.of(SpatialIndexTestData.AUCKLAND_FEATURE, SpatialIndexTestData.PERTH_FEATURE, SpatialIndexTestData.HONOLULU_FEATURE)); + Collection result = searchEnvelope.check(spatialIndex); assertEquals(expResult, result); } diff --git a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/WestPFTest.java b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/WestPFTest.java index 4aea6a59698..41b0ae2e44a 100644 --- a/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/WestPFTest.java +++ b/jena-geosparql/src/test/java/org/apache/jena/geosparql/spatial/property_functions/cardinal/WestPFTest.java @@ -17,16 +17,15 @@ */ package org.apache.jena.geosparql.spatial.property_functions.cardinal; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; +import java.util.*; + import org.apache.jena.geosparql.configuration.GeoSPARQLConfig; import org.apache.jena.geosparql.implementation.GeometryWrapper; import org.apache.jena.geosparql.spatial.CardinalDirection; import org.apache.jena.geosparql.spatial.SearchEnvelope; -import org.apache.jena.geosparql.spatial.SpatialIndex; import org.apache.jena.geosparql.spatial.SpatialIndexTestData; +import org.apache.jena.geosparql.spatial.index.v2.SpatialIndex; +import org.apache.jena.graph.Node; import org.apache.jena.query.Dataset; import org.apache.jena.query.QueryExecution; import org.apache.jena.query.QueryExecutionFactory; @@ -91,8 +90,8 @@ public void testCheckSearchEnvelope_no_wrap() { GeometryWrapper geometryWrapper = SpatialIndexTestData.PERTH_GEOMETRY_WRAPPER; WestPF instance = new WestPF(); SearchEnvelope searchEnvelope = instance.buildSearchEnvelope(geometryWrapper, SpatialIndexTestData.WGS_84_SRS_INFO); //Needed to initialise the search. - HashSet expResult = new HashSet<>(Arrays.asList(SpatialIndexTestData.LONDON_FEATURE, SpatialIndexTestData.PERTH_FEATURE)); - HashSet result = searchEnvelope.check(spatialIndex); + Set expResult = SpatialIndexTestData.asNodes(List.of(SpatialIndexTestData.LONDON_FEATURE, SpatialIndexTestData.PERTH_FEATURE)); + Collection result = searchEnvelope.check(spatialIndex); assertEquals(expResult, result); } @@ -108,8 +107,8 @@ public void testCheckSearchEnvelope_wrap() { GeometryWrapper geometryWrapper = SpatialIndexTestData.HONOLULU_GEOMETRY_WRAPPER; WestPF instance = new WestPF(); SearchEnvelope searchEnvelope = instance.buildSearchEnvelope(geometryWrapper, SpatialIndexTestData.WGS_84_SRS_INFO); //Needed to initialise the search. - HashSet expResult = new HashSet<>(Arrays.asList(SpatialIndexTestData.AUCKLAND_FEATURE, SpatialIndexTestData.PERTH_FEATURE, SpatialIndexTestData.HONOLULU_FEATURE)); - HashSet result = searchEnvelope.check(spatialIndex); + Set expResult = SpatialIndexTestData.asNodes(List.of(SpatialIndexTestData.AUCKLAND_FEATURE, SpatialIndexTestData.PERTH_FEATURE, SpatialIndexTestData.HONOLULU_FEATURE)); + Collection result = searchEnvelope.check(spatialIndex); assertEquals(expResult, result); }