This is an automated email from the ASF dual-hosted git repository.
andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git
The following commit(s) were added to refs/heads/main by this push:
new e4dcc30c7c GH-3473: GeoSparql: Remove need for 'a geo:Geometry' in
GenericPropertyFunction; Spatial Indexer UI: Replace without selection clears
index.
e4dcc30c7c is described below
commit e4dcc30c7ce0409e3cef0a267f21e6f7d8590074
Author: Claus Stadler <[email protected]>
AuthorDate: Tue Sep 30 18:12:50 2025 +0200
GH-3473: GeoSparql: Remove need for 'a geo:Geometry' in
GenericPropertyFunction; Spatial Indexer UI: Replace without selection clears
index.
---
.../org/apache/jena/rdfs/DatasetGraphRDFS.java | 4 +
.../apache/jena/sparql/core/DatasetGraphQuads.java | 2 +-
.../jena/sparql/core/mem/DatasetGraphInMemory.java | 4 +-
.../java/org/apache/jena/sparql/util/Context.java | 40 +++-
.../org/apache/jena/rdfs/TestDatasetGraphRDFS.java | 7 +
.../geosparql/query/BenchmarkSpatialQueries.java | 242 +++++++++++++++++++++
.../jena/geosparql/query/SpatialQueryTask.java | 27 +++
.../jena/geosparql/query/SpatialQueryTask550.java | 95 ++++++++
.../geosparql/query/SpatialQueryTaskCurrent.java | 84 +++++++
.../mod/geosparql/SpatialIndexerService.java | 22 +-
.../src/main/resources/spatial-indexer/index.html | 7 +-
.../GenericGeometryPropertyFunction.java | 60 ++---
.../geo/topological/GenericPropertyFunction.java | 152 ++++++-------
.../topological/SpatialObjectGeometryLiteral.java | 62 +++---
.../implementation/access/AccessGeoSPARQL.java | 232 ++++++++++++++++++++
.../implementation/access/AccessWGS84.java | 155 +++++++++++++
.../implementation/index/QueryRewriteIndex.java | 31 +--
.../geosparql/spatial/SpatialIndexFindUtils.java | 112 ++--------
.../geosparql/spatial/index/v2/STRtreeUtils.java | 2 +-
.../spatial/index/v2/SpatialIndexLib.java | 4 +-
.../GenericSpatialPropertyFunction.java | 59 ++---
.../simple_features/SfPFMiscSparqlTest.java | 99 +++++++++
22 files changed, 1172 insertions(+), 330 deletions(-)
diff --git a/jena-arq/src/main/java/org/apache/jena/rdfs/DatasetGraphRDFS.java
b/jena-arq/src/main/java/org/apache/jena/rdfs/DatasetGraphRDFS.java
index f5e96bf711..1ca880b9c8 100644
--- a/jena-arq/src/main/java/org/apache/jena/rdfs/DatasetGraphRDFS.java
+++ b/jena-arq/src/main/java/org/apache/jena/rdfs/DatasetGraphRDFS.java
@@ -67,6 +67,10 @@ public class DatasetGraphRDFS extends DatasetGraphWrapper
implements DatasetGrap
return new GraphRDFS(base, setup);
}
+ @Override
+ public Iterator<Quad> find()
+ { return find(Node.ANY, Node.ANY, Node.ANY, Node.ANY); }
+
// Quad-centric access
@Override
public Iterator<Quad> find(Quad quad) {
diff --git
a/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphQuads.java
b/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphQuads.java
index 76da75d6bc..de7ee17100 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphQuads.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphQuads.java
@@ -44,7 +44,7 @@ public abstract class DatasetGraphQuads extends
DatasetGraphBase
@Override
public void addGraph(Node graphName, Graph graph) {
- graph.find().forEachRemaining(t -> add(Quad.create(graphName, t)));
+ graph.find().forEach(t -> add(Quad.create(graphName, t)));
}
// @Override
diff --git
a/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/DatasetGraphInMemory.java
b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/DatasetGraphInMemory.java
index 07738018a1..7d0dc6935b 100644
---
a/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/DatasetGraphInMemory.java
+++
b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/DatasetGraphInMemory.java
@@ -346,10 +346,10 @@ public class DatasetGraphInMemory extends
DatasetGraphTriplesQuads implements Tr
}
private Consumer<Graph> addGraph(final Node name) {
- return g -> g.find().mapWith(t -> new Quad(name,
t)).forEachRemaining(this::add);
+ return g -> g.find().mapWith(t -> new Quad(name,
t)).forEach(this::add);
}
- private final Consumer<Graph> removeGraph = g ->
g.find().forEachRemaining(g::delete);
+ private final Consumer<Graph> removeGraph = g ->
g.find().forEach(g::delete);
@Override
public void addGraph(final Node graphName, final Graph graph) {
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/util/Context.java
b/jena-arq/src/main/java/org/apache/jena/sparql/util/Context.java
index fd2a5f261e..370d93bf49 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/util/Context.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/util/Context.java
@@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
+import java.util.function.Function;
import org.apache.jena.atlas.lib.Lib;
import org.apache.jena.atlas.logging.Log;
@@ -389,12 +390,6 @@ public class Context {
context.clear();
}
- /** Atomic compute. */
- @SuppressWarnings("unchecked")
- public <V> V compute(Symbol key, BiFunction<Symbol, Object, ? extends V>
remappingFunction) {
- return (V)context.compute(key, remappingFunction);
- }
-
@Override
public String toString() {
String x = "";
@@ -439,13 +434,36 @@ public class Context {
}
}
+ /** Atomic compute. */
+ @SuppressWarnings("unchecked")
+ public <V> V compute(Symbol key, BiFunction<Symbol, Object, ? extends V>
remappingFunction) {
+ Object obj = context.compute(key, remappingFunction);
+ return (V)obj;
+ }
+
+ /** Atomic computeIfAbsent. */
+ @SuppressWarnings("unchecked")
+ public <V> V computeIfAbsent(Symbol key, Function<Symbol, ? extends V>
mappingFunction) {
+ Object obj = context.computeIfAbsent(key, mappingFunction);
+ return (V)obj;
+ }
+
+ /** Atomic computeIfPresent. */
+ @SuppressWarnings("unchecked")
+ public <V> V computeIfPresent(Symbol key, BiFunction<Symbol, Object, V>
remappingFunction) {
+ Object obj = context.computeIfPresent(key, remappingFunction);
+ return (V)obj;
+ }
+
+ /** Get the context's cancel signal. Create and set one if needed. Context
must not be null. */
public static AtomicBoolean getOrSetCancelSignal(Context context) {
- AtomicBoolean cancelSignal = getCancelSignal(context);
- if (cancelSignal == null) {
- cancelSignal = new AtomicBoolean(false);
- context.set(ARQConstants.symCancelQuery, cancelSignal);
+ try {
+ AtomicBoolean result =
context.computeIfAbsent(ARQConstants.symCancelQuery, sym -> new
AtomicBoolean(false));
+ return result;
+ } catch (ClassCastException ex) {
+ Log.error(Context.class, "Class cast exception: Expected
AtomicBoolean for cancel control: "+ex.getMessage());
+ return null;
}
- return cancelSignal;
}
/** Merge an outer (defaults to the system global context)
diff --git
a/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java
b/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java
index 574bce81a6..095a366c3e 100644
--- a/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java
+++ b/jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java
@@ -24,6 +24,7 @@ import static org.apache.jena.rdfs.LibTestRDFS.node;
import static org.apache.jena.rdfs.engine.ConstRDFS.rdfType;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.PrintStream;
@@ -77,6 +78,12 @@ public class TestDatasetGraphRDFS {
Iter.consume(iter3);
}
+ @Test public void dsg_find_all() {
+ List<Quad> baseQuads = Iter.toList(dsg.getBase().find());
+ List<Quad> inferredQuads = Iter.toList(dsg.find());
+ assertNotEquals(baseQuads, inferredQuads);
+ }
+
@Test public void dsg_find_graph() {
List<Quad> x = test(node("g"), node("a"), rdfType, null);
assertTrue(hasNG(x, node("g"))) ;
diff --git
a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/geosparql/query/BenchmarkSpatialQueries.java
b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/geosparql/query/BenchmarkSpatialQueries.java
new file mode 100644
index 0000000000..476fa8b613
--- /dev/null
+++
b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/geosparql/query/BenchmarkSpatialQueries.java
@@ -0,0 +1,242 @@
+/*
+ * 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.query;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+
+import org.apache.jena.geosparql.implementation.GeometryWrapper;
+import org.apache.jena.geosparql.implementation.jts.CustomGeometryFactory;
+import org.apache.jena.geosparql.implementation.vocabulary.Geo;
+import org.apache.jena.geosparql.spatial.index.v2.GeometryGenerator;
+import
org.apache.jena.geosparql.spatial.index.v2.GeometryGenerator.GeometryType;
+import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.NodeFactory;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.riot.RDFDataMgr;
+import org.apache.jena.riot.RDFFormat;
+import org.apache.jena.sparql.graph.GraphFactory;
+import org.apache.jena.system.G;
+import org.apache.jena.vocabulary.RDF;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.util.AffineTransformation;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.results.format.ResultFormatType;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
+
+/**
+ * Benchmarking of spatial queries against test data.
+ */
+@State(Scope.Benchmark)
+public class BenchmarkSpatialQueries {
+
+ private static Map<String, String> idToQuery = new LinkedHashMap<>();
+
+ private Node featureNode =
NodeFactory.createURI("urn:test:geosparql:feature1");
+ private Node geometryNode =
NodeFactory.createURI("urn:test:geosparql:geometry1");
+
+ private static final String q1 = """
+ PREFIX geo: <http://www.opengis.net/ont/geosparql#>
+ PREFIX ogcsf: <http://www.opengis.net/ont/sf#>
+
+ SELECT *
+ WHERE {
+ ?s geo:sfWithin <urn:test:geosparql:geometry1> .
+ }
+ """;
+
+ private static final String q2 = """
+ PREFIX geo: <http://www.opengis.net/ont/geosparql#>
+ PREFIX ogcsf: <http://www.opengis.net/ont/sf#>
+
+ SELECT *
+ WHERE {
+ ?s a ogcsf:Point .
+ ?s geo:sfWithin <urn:test:geosparql:geometry1> .
+ }
+ """;
+
+ static {
+ idToQuery.put("q1", q1);
+ idToQuery.put("q2", q2);
+ }
+
+ /** Essentially the size of the data. One geometry mix includes every WKT
geometry type once (with different coordinates). */
+ @Param({
+ "10000",
+ })
+ public long p1_geoMixes;
+
+ @Param({
+ "q1",
+ "q2",
+ })
+ public String p2_queryId;
+
+ @Param({
+ "off",
+ "virtual",
+ "materialized"
+ })
+ public String p3_inferences;
+
+ @Param({
+ "false",
+ "true"
+ })
+ public boolean p4_index;
+
+ @Param({
+ "current",
+ "5.5.0"
+ })
+ public String p5_jenaVersion;
+
+ private SpatialQueryTask task;
+
+ @Benchmark
+ public void run() throws Exception {
+ long count = task.exec();
+ if (true) {
+ System.out.println("Counted: " + count);
+ }
+ }
+
+ private static GeometryWrapper toWrapperWkt(Geometry geometry) {
+ GeometryWrapper result = new GeometryWrapper(geometry, Geo.WKT);
+ return result;
+ }
+
+ @Setup(Level.Trial)
+ public void setupTrial() throws Exception {
+ Envelope dataBbox = new Envelope(-175, 175, -85, 85);
+ Map<GeometryType, Number> config =
GeometryGenerator.createConfig(p1_geoMixes);
+ Graph graph = GraphFactory.createDefaultGraph();
+ GeometryGenerator.generateGraph(graph, dataBbox, config);
+
+ // Build a search-bbox by scaling the data-generation-bbox down.
+ Geometry dataBboxGeom =
CustomGeometryFactory.theInstance().toGeometry(dataBbox);
+ double x = dataBboxGeom.getCentroid().getX();
+ double y = dataBboxGeom.getCentroid().getY();
+ Geometry searchBboxGeom = AffineTransformation.scaleInstance(0.25,
0.25, x, y).transform(dataBboxGeom);
+
+ // Add search bbox and feature/resource to the benchmark data.
+ Node searchBboxNode = toWrapperWkt(searchBboxGeom).asNode();
+ graph.add(featureNode, Geo.HAS_GEOMETRY_NODE, geometryNode);
+ graph.add(geometryNode, Geo.AS_WKT_NODE, searchBboxNode);
+
+ // Post process test data:
+ // - Add "geom a Point" triples to geometry resources with a Point WKT
literal.
+ // - Add explicit Geometry type to all geometry resources (required by
jena-geosparql 5.5.0 and earlier).
+ Node Point =
NodeFactory.createURI("http://www.opengis.net/ont/sf#Point");
+ Graph extraGraph = GraphFactory.createDefaultGraph();
+ try (Stream<Triple> stream = graph.stream(null, Geo.AS_WKT_NODE,
null)) {
+ stream.forEach(t -> {
+ GeometryWrapper gw = GeometryWrapper.extract(t.getObject());
+ String geoType = gw.getGeometryType();
+ if (geoType.equals("Point")) {
+ extraGraph.add(t.getSubject(), RDF.Nodes.type, Point);
+ }
+
+ extraGraph.add(t.getSubject(), RDF.Nodes.type,
Geo.GEOMETRY_NODE);
+ });
+ }
+ G.addInto(graph, extraGraph);
+
+ String data;
+ RDFFormat fmt = RDFFormat.TURTLE_PRETTY;
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ RDFDataMgr.write(out, graph, fmt);
+ out.flush();
+ data = new String(out.toByteArray(), StandardCharsets.UTF_8);
+ }
+
+ task = switch (p5_jenaVersion) {
+ case "current" -> new SpatialQueryTaskCurrent();
+ case "5.5.0" -> new SpatialQueryTask550();
+ default -> throw new RuntimeException("No task registered for this
jena version:" + p5_jenaVersion);
+ };
+
+ task.setData(data);
+
+ switch (p3_inferences) {
+ case "off": task.setInferenceMode(false, false); break;
+ case "virtual": task.setInferenceMode(true, false); break;
+ case "materialized": task.setInferenceMode(true, true); break;
+ default:
+ throw new IllegalArgumentException("Unsupported inference mode: "
+ p3_inferences);
+ }
+
+ task.setIndex(p4_index);
+
+ String queryString = idToQuery.get(p2_queryId);
+ task.setQuery(queryString);
+ }
+
+ @TearDown(Level.Trial)
+ public void tearDownTrial() throws Exception {
+ }
+
+ public static ChainedOptionsBuilder getDefaults(Class<?> c) {
+ return new OptionsBuilder()
+ // Specify which benchmarks to run.
+ // You can be more specific if you'd like to run only one
benchmark per test.
+ .include(c.getName())
+ // Set the following options as needed
+ .mode(Mode.AverageTime)
+ .timeUnit(TimeUnit.SECONDS)
+ .warmupTime(TimeValue.NONE)
+ .warmupIterations(5)
+ .measurementIterations(5)
+ .measurementTime(TimeValue.NONE)
+ .threads(1)
+ .forks(1)
+ .shouldFailOnError(true)
+ .shouldDoGC(true)
+ //.jvmArgs("-XX:+UnlockDiagnosticVMOptions",
"-XX:+PrintInlining")
+ .jvmArgs("-Xmx8G")
+ //.addProfiler(WinPerfAsmProfiler.class)
+ .resultFormat(ResultFormatType.JSON)
+ .result(c.getSimpleName() + "_" +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) +
".json");
+ }
+
+ public static void main(String[] args) throws RunnerException {
+ Options opt = getDefaults(BenchmarkSpatialQueries.class).build();
+ new Runner(opt).run();
+ }
+}
diff --git
a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/geosparql/query/SpatialQueryTask.java
b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/geosparql/query/SpatialQueryTask.java
new file mode 100644
index 0000000000..0fe6e4dde0
--- /dev/null
+++
b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/geosparql/query/SpatialQueryTask.java
@@ -0,0 +1,27 @@
+/*
+ * 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.query;
+
+public interface SpatialQueryTask {
+ void setData(String trigString) throws Exception;
+ void setInferenceMode(boolean enableInferences, boolean materialize)
throws Exception;
+ void setQuery(String queryString) throws Exception;
+ void setIndex(boolean isEnabled);
+ long exec();
+}
diff --git
a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/geosparql/query/SpatialQueryTask550.java
b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/geosparql/query/SpatialQueryTask550.java
new file mode 100644
index 0000000000..77bb1a6c6a
--- /dev/null
+++
b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/geosparql/query/SpatialQueryTask550.java
@@ -0,0 +1,95 @@
+/*
+ * 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.query;
+
+import java.util.stream.Stream;
+
+import org.apache.shadedJena550.geosparql.configuration.GeoSPARQLOperations;
+import org.apache.shadedJena550.geosparql.spatial.SpatialIndexException;
+import org.apache.shadedJena550.geosparql.spatial.index.v2.SpatialIndexLib;
+import org.apache.shadedJena550.graph.Graph;
+import org.apache.shadedJena550.rdfs.RDFSFactory;
+import org.apache.shadedJena550.riot.Lang;
+import org.apache.shadedJena550.riot.RDFParser;
+import org.apache.shadedJena550.sparql.core.DatasetGraph;
+import org.apache.shadedJena550.sparql.core.DatasetGraphFactory;
+import org.apache.shadedJena550.sparql.core.Quad;
+import org.apache.shadedJena550.sparql.exec.QueryExec;
+import org.apache.shadedJena550.sparql.exec.RowSetOps;
+
+public class SpatialQueryTask550
+ implements SpatialQueryTask
+{
+ private DatasetGraph baseDsg = null;
+ private DatasetGraph effectiveDsg = null;
+ private String query;
+
+ @Override
+ public void setData(String trigString) throws Exception {
+ baseDsg =
RDFParser.create().fromString(trigString).lang(Lang.TRIG).toDatasetGraph();
+ }
+
+ @Override
+ public void setQuery(String queryString) throws Exception {
+ this.query = queryString;
+ }
+
+ @Override
+ public void setInferenceMode(boolean enableInferences, boolean
materialize) {
+ if (enableInferences) {
+ Graph vocab = GeoSPARQLOperations.loadGeoSPARQLSchema().getGraph();
+ DatasetGraph virtualDsg = RDFSFactory.datasetRDFS(baseDsg, vocab);
+ if (materialize) {
+ effectiveDsg = DatasetGraphFactory.create();
+
+ // Bugged in 5.5.0 because find() is not overridden to yield
inferences:
+ // effectiveDsg.addAll(virtualDsg);
+
+ try (Stream<Quad> stream = virtualDsg.stream(null, null, null,
null)) {
+ stream.forEach(effectiveDsg::add);
+ }
+ } else {
+ effectiveDsg = virtualDsg;
+ }
+ } else {
+ effectiveDsg = baseDsg;
+ }
+
+ // RDFDataMgr.write(System.err, effectiveDsg, RDFFormat.TRIG_PRETTY);
+ }
+
+ @Override
+ public void setIndex(boolean isEnabled) {
+ if (isEnabled) {
+ try {
+ SpatialIndexLib.buildSpatialIndex(effectiveDsg);
+ } catch (SpatialIndexException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Override
+ public long exec() {
+ try (QueryExec qe =
QueryExec.dataset(effectiveDsg).query(query).build()) {
+ long count = RowSetOps.count(qe.select());
+ return count;
+ }
+ }
+}
diff --git
a/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/geosparql/query/SpatialQueryTaskCurrent.java
b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/geosparql/query/SpatialQueryTaskCurrent.java
new file mode 100644
index 0000000000..e90f64fca9
--- /dev/null
+++
b/jena-benchmarks/jena-benchmarks-jmh/src/test/java/org/apache/jena/geosparql/query/SpatialQueryTaskCurrent.java
@@ -0,0 +1,84 @@
+/*
+ * 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.query;
+
+import org.apache.jena.geosparql.configuration.GeoSPARQLOperations;
+import org.apache.jena.geosparql.spatial.SpatialIndexException;
+import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexLib;
+import org.apache.jena.graph.Graph;
+import org.apache.jena.rdfs.RDFSFactory;
+import org.apache.jena.riot.Lang;
+import org.apache.jena.riot.RDFParser;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.DatasetGraphFactory;
+import org.apache.jena.sparql.exec.QueryExec;
+import org.apache.jena.sparql.exec.RowSetOps;
+
+public class SpatialQueryTaskCurrent
+ implements SpatialQueryTask
+{
+ private DatasetGraph baseDsg = null;
+ private DatasetGraph effectiveDsg = null;
+ private String query;
+
+ @Override
+ public void setData(String ttlString) throws Exception {
+ baseDsg =
RDFParser.create().fromString(ttlString).lang(Lang.TRIG).toDatasetGraph();
+ }
+
+ @Override
+ public void setQuery(String queryString) throws Exception {
+ this.query = queryString;
+ }
+
+ @Override
+ public void setInferenceMode(boolean enableInferences, boolean
materialize) {
+ if (enableInferences) {
+ Graph vocab = GeoSPARQLOperations.loadGeoSPARQLSchema().getGraph();
+ DatasetGraph virtualDsg = RDFSFactory.datasetRDFS(baseDsg, vocab);
+ if (materialize) {
+ effectiveDsg = DatasetGraphFactory.create();
+ effectiveDsg.addAll(virtualDsg);
+ } else {
+ effectiveDsg = virtualDsg;
+ }
+ } else {
+ effectiveDsg = baseDsg;
+ }
+ }
+
+ @Override
+ public void setIndex(boolean isEnabled) {
+ if (isEnabled) {
+ try {
+ SpatialIndexLib.buildSpatialIndex(effectiveDsg);
+ } catch (SpatialIndexException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Override
+ public long exec() {
+ try (QueryExec qe =
QueryExec.dataset(effectiveDsg).query(query).build()) {
+ long count = RowSetOps.count(qe.select());
+ return count;
+ }
+ }
+}
diff --git
a/jena-fuseki2/jena-fuseki-mod-geosparql/src/main/java/org/apache/jena/fuseki/mod/geosparql/SpatialIndexerService.java
b/jena-fuseki2/jena-fuseki-mod-geosparql/src/main/java/org/apache/jena/fuseki/mod/geosparql/SpatialIndexerService.java
index 87eaa33aa1..9787f1dba7 100644
---
a/jena-fuseki2/jena-fuseki-mod-geosparql/src/main/java/org/apache/jena/fuseki/mod/geosparql/SpatialIndexerService.java
+++
b/jena-fuseki2/jena-fuseki-mod-geosparql/src/main/java/org/apache/jena/fuseki/mod/geosparql/SpatialIndexerService.java
@@ -120,18 +120,26 @@ public class SpatialIndexerService extends BaseActionREST
{
public SpatialIndexerService() {}
- private static <T, C extends Collection<Node>> Set<Node>
extractGraphsFromRequest(DatasetGraph dsg, HttpAction action) {
+ /**
+ * Extract the explicit set of graphs from the action w.r.t. the dataset
graph.
+ *
+ * @param dsg The dataset graph.
+ * @param action The HTTP action.
+ * @param emptySelectionToAllGraphs Select all graphs if the request
specifies an empty selection of graphs.
+ * @return The explicit set of graphs w.r.t. the dataset graph.
+ */
+ private static Set<Node> extractGraphsFromRequest(DatasetGraph dsg,
HttpAction action, boolean emptySelectionToAllGraphs) {
String uris = action.getRequest().getParameter(HttpNames.paramGraph);
Collection<String> strs;
if (uris == null || uris.isBlank()) {
strs = List.of(Quad.defaultGraphIRI.toString(),
Quad.unionGraph.toString());
} else {
- TypeToken<List<String>> typeToken = new
TypeToken<List<String>>(){};
+ TypeToken<List<String>> typeToken = new TypeToken<>(){};
strs = gsonForSse.fromJson(uris, typeToken);
}
List<Node> rawGraphNodes =
strs.stream().map(NodeFactory::createURI).distinct().toList();
// If the set of specified graphs is empty then index all.
- if (rawGraphNodes.isEmpty()) {
+ if (rawGraphNodes.isEmpty() && emptySelectionToAllGraphs) {
rawGraphNodes = List.of(Quad.defaultGraphIRI, Quad.unionGraph);
}
@@ -453,7 +461,7 @@ public class SpatialIndexerService extends BaseActionREST {
long graphCount = indexComputation.getGraphNodes().size();
- TaskListener<BasicTask> taskListener = new TaskListener<BasicTask>() {
+ TaskListener<BasicTask> taskListener = new TaskListener<>() {
@Override
public void onStateChange(BasicTask task) {
switch (task.getTaskState()) {
@@ -475,6 +483,7 @@ public class SpatialIndexerService extends BaseActionREST {
if (logger.isInfoEnabled()) {
logger.info("Indexing task of {} graphs terminated.",
graphCount);
}
+ break;
}
default:
break;
@@ -496,6 +505,8 @@ public class SpatialIndexerService extends BaseActionREST {
ServletOps.error(HttpSC.SERVICE_UNAVAILABLE_503, msg);
} else {
boolean isReplaceMode = isReplaceMode(action);
+ boolean isUpdateMode = !isReplaceMode;
+
int threadCount = getThreadCount(action);
// Only SpatialIndexPerGraph can be updated.
@@ -503,7 +514,6 @@ public class SpatialIndexerService extends BaseActionREST {
// If not then raise an exception
// that informs that only replace mode can be used in this
situation.
if (!(index instanceof SpatialIndexPerGraph)) {
- boolean isUpdateMode = !isReplaceMode;
if (isUpdateMode) {
throw new RuntimeException("Cannot update existing spatial
index because its type is unsupported. Consider replacing the index.");
}
@@ -516,7 +526,7 @@ public class SpatialIndexerService extends BaseActionREST {
String srsURI = index.getSrsInfo().getSrsURI();
- List<Node> graphNodes = new ArrayList<>(Txn.calculateRead(dsg, ()
-> extractGraphsFromRequest(dsg, action)));
+ List<Node> graphNodes = new ArrayList<>(Txn.calculateRead(dsg, ()
-> extractGraphsFromRequest(dsg, action, isUpdateMode)));
SpatialIndexerComputation task = new
SpatialIndexerComputation(dsg, srsURI, graphNodes, threadCount);
action.log.info(String.format("[%d] spatial index: computation
request accepted.", action.id));
diff --git
a/jena-fuseki2/jena-fuseki-mod-geosparql/src/main/resources/spatial-indexer/index.html
b/jena-fuseki2/jena-fuseki-mod-geosparql/src/main/resources/spatial-indexer/index.html
index be6caaacfb..fdebc8dd55 100644
---
a/jena-fuseki2/jena-fuseki-mod-geosparql/src/main/resources/spatial-indexer/index.html
+++
b/jena-fuseki2/jena-fuseki-mod-geosparql/src/main/resources/spatial-indexer/index.html
@@ -218,8 +218,9 @@
}
function updateApplyButtonLabel() {
+ const replaceMode = replaceCb.checked;
const selectedGraphs = document.querySelectorAll('#graph-list
input:checked');
- applyBtn.textContent = selectedGraphs.length === 0 ? 'Index all
graphs' : `Index ${selectedGraphs.length} graphs`;
+ applyBtn.textContent = (!replaceMode && selectedGraphs.length ===
0) ? 'Index all graphs' : `Index ${selectedGraphs.length} graphs`;
}
document.getElementById('filter').addEventListener('input', function
() {
@@ -303,6 +304,10 @@
updateApplyButtonLabel();
});
+ replaceCb.addEventListener("change", function () {
+ updateApplyButtonLabel();
+ });
+
function updateStatus() {
updateCancelButton();
updateStatusMessage();
diff --git
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/geo/topological/GenericGeometryPropertyFunction.java
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/geo/topological/GenericGeometryPropertyFunction.java
index b76e5c9b32..91d5b4f019 100644
---
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/geo/topological/GenericGeometryPropertyFunction.java
+++
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/geo/topological/GenericGeometryPropertyFunction.java
@@ -19,7 +19,7 @@ package org.apache.jena.geosparql.geo.topological;
import org.apache.jena.datatypes.DatatypeFormatException;
import org.apache.jena.geosparql.implementation.GeometryWrapper;
-import org.apache.jena.geosparql.implementation.vocabulary.Geo;
+import org.apache.jena.geosparql.implementation.access.AccessGeoSPARQL;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
@@ -28,15 +28,15 @@ import org.apache.jena.sparql.engine.ExecutionContext;
import org.apache.jena.sparql.engine.QueryIterator;
import org.apache.jena.sparql.engine.binding.Binding;
import org.apache.jena.sparql.engine.binding.BindingFactory;
-import org.apache.jena.sparql.engine.iterator.QueryIterConcat;
+import org.apache.jena.sparql.engine.iterator.QueryIter;
import org.apache.jena.sparql.engine.iterator.QueryIterNullIterator;
+import org.apache.jena.sparql.engine.iterator.QueryIterPlainWrapper;
import org.apache.jena.sparql.engine.iterator.QueryIterSingleton;
import org.apache.jena.sparql.expr.ExprEvalException;
import org.apache.jena.sparql.expr.NodeValue;
import org.apache.jena.sparql.pfunction.PFuncSimple;
import org.apache.jena.system.G;
import org.apache.jena.util.iterator.ExtendedIterator;
-import org.apache.jena.vocabulary.RDF;
/**
*
@@ -62,7 +62,6 @@ public abstract class GenericGeometryPropertyFunction extends
PFuncSimple {
//Subject unbound and object bound.
return subjectUnbound(binding, subject, predicate, object,
execCxt);
}
-
}
protected Node getGeometryLiteral(Node subject, Node predicate, Graph
graph) throws ExprEvalException {
@@ -73,15 +72,7 @@ public abstract class GenericGeometryPropertyFunction
extends PFuncSimple {
return G.getSP(graph, subject, predicate);
//Check that the Geometry has a serialisation to use.
- Node geomLiteral = G.getSP(graph, subject,
Geo.HAS_SERIALIZATION_NODE);
-
- // If hasSerialization not found then check asWKT and asGML.
- if (geomLiteral == null) {
- geomLiteral = G.getSP(graph, subject, Geo.AS_WKT_NODE);
- if (geomLiteral == null)
- geomLiteral = G.getSP(graph, subject, Geo.AS_GML_NODE);
- }
-
+ Node geomLiteral = AccessGeoSPARQL.getGeoLiteral(graph, subject);
if (geomLiteral != null) {
GeometryWrapper geometryWrapper =
GeometryWrapper.extract(geomLiteral);
NodeValue predicateResult = applyPredicate(geometryWrapper);
@@ -111,25 +102,23 @@ public abstract class GenericGeometryPropertyFunction
extends PFuncSimple {
}
private QueryIterator subjectUnbound(Binding binding, Node subject, Node
predicate, Node object, ExecutionContext execCxt) {
- QueryIterConcat queryIterConcat = new QueryIterConcat(execCxt);
-
Graph graph = execCxt.getActiveGraph();
- ExtendedIterator<Triple> subjectTriples = graph.find(null,
RDF.type.asNode(), Geo.GEOMETRY_NODE);
-
+ ExtendedIterator<Triple> subjectTriples =
AccessGeoSPARQL.findSpecificGeoLiterals(graph);
Var subjectVar = Var.alloc(subject.getName());
- while (subjectTriples.hasNext()) {
- Triple subjectTriple = subjectTriples.next();
- Binding newBind = BindingFactory.binding(binding, subjectVar,
subjectTriple.getSubject());
- QueryIterator queryIter = bothBound(newBind,
subjectTriple.getSubject(), predicate, object, execCxt);
- queryIterConcat.add(queryIter);
- }
+ ExtendedIterator<Binding> iterator = subjectTriples
+ .mapWith(Triple::getSubject)
+ .mapWith(node -> BindingFactory.binding(binding, subjectVar,
node));
- return queryIterConcat;
+ QueryIter queryIter = QueryIter.flatMap(
+ QueryIterPlainWrapper.create(iterator, execCxt),
+ b -> bothBound(b, b.get(subjectVar), predicate, object,
execCxt),
+ execCxt);
+
+ return queryIter;
}
private QueryIterator objectUnbound(Binding binding, Node subject, Node
predicate, Node object, ExecutionContext execCxt) {
-
Graph graph = execCxt.getActiveGraph();
Node geometryLiteral = getGeometryLiteral(subject, predicate, graph);
@@ -141,21 +130,20 @@ public abstract class GenericGeometryPropertyFunction
extends PFuncSimple {
}
private QueryIterator bothUnbound(Binding binding, Node subject, Node
predicate, Node object, ExecutionContext execCxt) {
- QueryIterConcat queryIterConcat = new QueryIterConcat(execCxt);
-
Graph graph = execCxt.getActiveGraph();
- ExtendedIterator<Triple> subjectTriples = graph.find(null,
RDF.type.asNode(), Geo.GEOMETRY_NODE);
-
+ ExtendedIterator<Triple> subjectTriples =
AccessGeoSPARQL.findSpecificGeoLiterals(graph);
Var subjectVar = Var.alloc(subject.getName());
- while (subjectTriples.hasNext()) {
- Triple subjectTriple = subjectTriples.next();
- Binding newBind = BindingFactory.binding(binding, subjectVar,
subjectTriple.getSubject());
- QueryIterator queryIter = objectUnbound(newBind,
subjectTriple.getSubject(), predicate, object, execCxt);
- queryIterConcat.add(queryIter);
- }
+ ExtendedIterator<Binding> iterator = subjectTriples
+ .mapWith(Triple::getSubject)
+ .mapWith(node -> BindingFactory.binding(binding, subjectVar,
node));
+
+ QueryIter queryIter = QueryIter.flatMap(
+ QueryIterPlainWrapper.create(iterator, execCxt),
+ b -> objectUnbound(b, b.get(subjectVar), predicate, object,
execCxt),
+ execCxt);
- return queryIterConcat;
+ return queryIter;
}
}
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 c1185a71ad..9b913abfc9 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
@@ -23,11 +23,10 @@ import java.util.List;
import org.apache.jena.atlas.iterator.Iter;
import org.apache.jena.geosparql.geof.topological.GenericFilterFunction;
import org.apache.jena.geosparql.implementation.GeometryWrapper;
+import org.apache.jena.geosparql.implementation.access.AccessGeoSPARQL;
+import org.apache.jena.geosparql.implementation.access.AccessWGS84;
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.SpatialIndexLib;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
@@ -47,7 +46,6 @@ import org.apache.jena.sparql.pfunction.PFuncSimple;
import org.apache.jena.sparql.util.FmtUtils;
import org.apache.jena.system.G;
import org.apache.jena.util.iterator.ExtendedIterator;
-import org.apache.jena.vocabulary.RDF;
import org.locationtech.jts.geom.Envelope;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.operation.TransformException;
@@ -68,38 +66,42 @@ public abstract class GenericPropertyFunction extends
PFuncSimple {
@Override
public QueryIterator execEvaluated(Binding binding, Node subject, Node
predicate, Node object, ExecutionContext execCxt) {
- // optionally accept bound literals for simpler usage
-
-// if (object.isLiteral()) {
-// //These Property Functions do not accept literals as objects so
exit quickly.
-// return QueryIterNullIterator.create(execCxt);
-// }
-
- if (subject.isConcrete() && object.isConcrete()) {
- //Both are bound.
- return bothBound(binding, subject, predicate, object, execCxt);
- } else if (subject.isVariable() && object.isVariable()) {
- //Both are unbound.
- return bothUnbound(binding, subject, predicate, object, execCxt);
+ // These Property Functions accept literals as objects as an extension
over GeoSPARQL 1.0.
+
+ QueryRewriteIndex queryRewriteIndex =
QueryRewriteIndex.getOrCreate(execCxt);
+ SpatialIndex spatialIndex =
SpatialIndexLib.getSpatialIndex(execCxt.getContext());
+
+ if (subject.isConcrete()) {
+ if (object.isConcrete()) {
+ //Both are bound.
+ return bothBound(binding, subject, predicate, object, execCxt,
queryRewriteIndex);
+ } else {
+ //One bound and one unbound.
+ return oneBoundChecked(binding, subject, predicate, object,
true, execCxt, spatialIndex, queryRewriteIndex);
+ }
} else {
- //One bound and one unbound.
- return oneBound(binding, subject, predicate, object, execCxt);
+ if (object.isConcrete()) {
+ //One bound and one unbound.
+ return oneBoundChecked(binding, object, predicate, subject,
false, execCxt, spatialIndex, queryRewriteIndex);
+ } else {
+ //Both are unbound.
+ return bothUnbound(binding, subject, predicate, object,
execCxt, spatialIndex, queryRewriteIndex);
+ }
}
}
- private QueryIterator bothBound(Binding binding, boolean isSubjectBound,
Node subject, Node predicate, Node object, ExecutionContext execCxt) {
+ private QueryIterator bothBound(Binding binding, boolean isSubjectBound,
Node subject, Node predicate, Node object, ExecutionContext execCxt,
QueryRewriteIndex queryRewriteIndex) {
QueryIterator iter = isSubjectBound
- ? bothBound(binding, subject, predicate, object, execCxt)
- : bothBound(binding, object, predicate, subject, execCxt);
+ ? bothBound(binding, subject, predicate, object, execCxt,
queryRewriteIndex)
+ : bothBound(binding, object, predicate, subject, execCxt,
queryRewriteIndex);
return iter;
}
- private QueryIterator bothBound(Binding binding, Node subject, Node
predicate, Node object, ExecutionContext execCxt) {
+ private QueryIterator bothBound(Binding binding, Node subject, Node
predicate, Node object, ExecutionContext execCxt, QueryRewriteIndex
queryRewriteIndex) {
Graph graph = execCxt.getActiveGraph();
- QueryRewriteIndex queryRewriteIndex =
QueryRewriteIndex.retrieve(execCxt);
Boolean isPositiveResult = queryRewrite(graph, subject, predicate,
object, queryRewriteIndex);
if (isPositiveResult) {
- //Filter function test succeded so retain binding.
+ //Filter function test succeeded so retain binding.
return QueryIterSingleton.create(binding, execCxt);
} else {
//Filter function test failed so null result.
@@ -107,103 +109,83 @@ public abstract class GenericPropertyFunction extends
PFuncSimple {
}
}
- private QueryIterator bothUnbound(Binding binding, Node subject, Node
predicate, Node object, ExecutionContext execCxt) {
+ private QueryIterator bothUnbound(Binding binding, Node subject, Node
predicate, Node object, ExecutionContext execCxt, SpatialIndex spatialIndex,
QueryRewriteIndex queryRewriteIndex) {
Var subjectVar = Var.alloc(subject.getName());
-
Graph graph = execCxt.getActiveGraph();
//Search for both Features and Geometry in the Graph. Reliant upon
consistent usage of SpatialObject (which is base class of Feature and Geometry)
if present.
- ExtendedIterator<Triple> spatialTriples = findSpatialTriples(graph);
- ExtendedIterator<Binding> iterator = spatialTriples
- .mapWith(Triple::getSubject)
+ ExtendedIterator<Binding> iterator = findSpatialObjects(graph)
.mapWith(node -> BindingFactory.binding(binding, subjectVar,
node));
QueryIter queryIter = QueryIter.flatMap(
QueryIterPlainWrapper.create(iterator, execCxt),
- b -> oneBound(b, b.get(subjectVar), predicate, object, execCxt),
+ b -> oneBound(graph, b, b.get(subjectVar), predicate, object,
true, execCxt, spatialIndex, queryRewriteIndex),
execCxt
);
return queryIter;
}
- private QueryIterator oneBound(Binding binding, Node subject, Node
predicate, Node object, ExecutionContext execCxt) {
-
+ /** Validate the bound node for whether it is a literal or spatial object.
*/
+ private QueryIterator oneBoundChecked(Binding binding, Node boundNode,
Node predicate, Node unboundNode, boolean isSubjectBound, ExecutionContext
execCxt, SpatialIndex spatialIndex, QueryRewriteIndex queryRewriteIndex) {
Graph graph = execCxt.getActiveGraph();
- Node boundNode;
- Node unboundNode;
- boolean isSubjectBound;
- if (subject.isConcrete()) {
- //Subject is bound, object is unbound.
- boundNode = subject;
- unboundNode = object;
- isSubjectBound = true;
- } else {
- //Object is bound, subject is unbound.
- boundNode = object;
- unboundNode = subject;
- isSubjectBound = false;
- }
- if (!(boundNode.isLiteral() ||
- graph.contains(boundNode, RDF.type.asNode(),
Geo.SPATIAL_OBJECT_NODE) ||
- graph.contains(boundNode, RDF.type.asNode(), Geo.FEATURE_NODE)
||
- graph.contains(boundNode, RDF.type.asNode(),
Geo.GEOMETRY_NODE))) {
- if (!graph.contains(boundNode, SpatialExtension.GEO_LAT_NODE,
null)) {
- //Bound node is not a Feature or a Geometry or has Geo
predicates so exit.
- return QueryIterNullIterator.create(execCxt);
- }
+ // If the bound node can't match in the first place then bail out
early.
+ // Otherwise, the whole unbound side would be scanned and tested
against the bound node.
+ if (!(boundNode.isLiteral() ||
AccessGeoSPARQL.isSpatialObjectByProperties(graph, boundNode))) {
+ return QueryIterNullIterator.create(execCxt);
}
- boolean isSpatialIndex = SpatialIndexLib.isDefined(execCxt);
+ return oneBound(graph, binding, boundNode, predicate, unboundNode,
isSubjectBound, execCxt, spatialIndex, queryRewriteIndex);
+ }
+
+ private QueryIterator oneBound(Graph graph, Binding binding, Node
boundNode, Node predicate, Node unboundNode, boolean isSubjectBound,
ExecutionContext execCxt, SpatialIndex spatialIndex, QueryRewriteIndex
queryRewriteIndex) {
QueryIterator result;
- if (!isSpatialIndex || filterFunction.isDisjoint() ||
filterFunction.isDisconnected()) {
+ if (spatialIndex == null || filterFunction.isDisjoint() ||
filterFunction.isDisconnected()) {
//Disjointed so retrieve all cases.
- result = findAll(graph, boundNode, unboundNode, binding,
isSubjectBound, predicate, execCxt);
+ result = findAll(graph, binding, boundNode, predicate,
unboundNode, isSubjectBound, execCxt, queryRewriteIndex);
} else {
//Only retrieve those in the spatial index which are within same
bounding box.
- result = findIndex(graph, boundNode, unboundNode, binding,
isSubjectBound, predicate, execCxt);
+ result = findIndex(graph, binding, boundNode, predicate,
unboundNode, isSubjectBound, execCxt, spatialIndex, queryRewriteIndex);
}
return result;
}
- private QueryIterator findAll(Graph graph, Node boundNode, Node
unboundNode, Binding binding, boolean isSubjectBound, Node predicate,
ExecutionContext execCxt) {
+ private QueryIterator findAll(Graph graph, Binding binding, Node
boundNode, Node predicate, Node unboundNode, boolean isSubjectBound,
ExecutionContext execCxt, QueryRewriteIndex queryRewriteIndex) {
//Prepare the results.
Var unboundVar = Var.alloc(unboundNode.getName());
//Search for both Features and Geometry in the Graph. Reliant upon
consistent usage of SpatialObject (which is base class of Feature and Geometry)
if present.
- ExtendedIterator<Triple> spatialTriples = findSpatialTriples(graph);
-
- ExtendedIterator<Binding> iterator = spatialTriples
- .mapWith(Triple::getSubject)
+ ExtendedIterator<Binding> iterator = findSpatialObjects(graph)
.mapWith(node -> BindingFactory.binding(binding, unboundVar,
node));
return QueryIter.flatMap(
QueryIterPlainWrapper.create(iterator, execCxt),
b -> {
Node spatialNode = b.get(unboundVar);
- QueryIterator iter = bothBound(b, isSubjectBound, boundNode,
predicate, spatialNode, execCxt);
+ QueryIterator iter = bothBound(b, isSubjectBound, boundNode,
predicate, spatialNode, execCxt, queryRewriteIndex);
return iter;
},
execCxt);
}
- private static ExtendedIterator<Triple> findSpatialTriples(Graph graph) {
- ExtendedIterator<Triple> spatialTriples;
- if (graph.contains(null, RDF.type.asNode(), Geo.SPATIAL_OBJECT_NODE)) {
- spatialTriples = graph.find(null, RDF.type.asNode(),
Geo.SPATIAL_OBJECT_NODE);
- } else if (graph.contains(null, RDF.type.asNode(), Geo.FEATURE_NODE)
|| graph.contains(null, RDF.type.asNode(), Geo.GEOMETRY_NODE)) {
- ExtendedIterator<Triple> featureTriples = graph.find(null,
RDF.type.asNode(), Geo.FEATURE_NODE);
- ExtendedIterator<Triple> geometryTriples = graph.find(null,
RDF.type.asNode(), Geo.GEOMETRY_NODE);
- spatialTriples = featureTriples.andThen(geometryTriples);
- } else {
- //Check for Geo Predicate Features in the Graph if no
GeometryLiterals found.
- spatialTriples = graph.find(null, SpatialExtension.GEO_LAT_NODE,
null);
+ private static ExtendedIterator<Node> findSpatialObjects(Graph graph) {
+ // The found nodes are passed to SpatialObjectGeometryLiteral.retrieve
which:
+ // - Filters out all features unless they have a
geo:hasDefaultGeometry property or wgs84 vocab.
+ // - Retrieves only a single specific geoLiteral for a geoResource
+ // There would be performance potential by leveraging the triples here
for retrieve.
+ ExtendedIterator<Triple> result =
AccessGeoSPARQL.findSpecificGeoLiterals(graph);
+ try {
+ result =
result.andThen(AccessGeoSPARQL.findDefaultGeoResources(graph));
+ result =
result.andThen(AccessWGS84.findGeoLiteralsAsTriples(graph, null));
+ } catch (RuntimeException t) {
+ result.close();
+ throw new RuntimeException(t);
}
- return spatialTriples;
+ return result.mapWith(Triple::getSubject);
}
- private QueryIterator findIndex(Graph graph, Node boundNode, Node
unboundNode, Binding binding, boolean isSubjectBound, Node predicate,
ExecutionContext execCxt) throws ExprEvalException {
+ private QueryIterator findIndex(Graph graph, Binding binding, Node
boundNode, Node predicate, Node unboundNode, boolean isSubjectBound,
ExecutionContext execCxt, SpatialIndex spatialIndex, QueryRewriteIndex
queryRewriteIndex) throws ExprEvalException {
try {
//Prepare for results.
Var unboundVar = Var.alloc(unboundNode);
@@ -230,7 +212,6 @@ public abstract class GenericPropertyFunction extends
PFuncSimple {
Node geometryLiteral = boundGeometryLiteral.getGeometryLiteral();
// Perform the search of the Spatial Index of the Dataset.
- SpatialIndex spatialIndex = SpatialIndexLib.retrieve(execCxt);
GeometryWrapper geom = GeometryWrapper.extract(geometryLiteral);
GeometryWrapper transformedGeom =
geom.transform(spatialIndex.getSrsInfo());
@@ -247,32 +228,33 @@ public abstract class GenericPropertyFunction extends
PFuncSimple {
featureBinding -> {
return findByFeature(graph, binding, featureBinding,
isSubjectBound, boundNode, predicate, unboundVar,
- execCxt, assertedNodes);
+ execCxt, assertedNodes, queryRewriteIndex);
},
execCxt);
queryIterConcat.add(queryIterator);
return queryIterConcat;
- } catch (MismatchedDimensionException | TransformException |
FactoryException | SpatialIndexException ex) {
+ } catch (MismatchedDimensionException | TransformException |
FactoryException ex) {
throw new ExprEvalException(ex.getMessage() + ": " +
FmtUtils.stringForNode(boundNode) + ", " + FmtUtils.stringForNode(unboundNode)
+ ", " + FmtUtils.stringForNode(predicate), ex);
}
}
private QueryIterator findByFeature(Graph graph, Binding binding, Binding
featureBinding,
boolean isSubjectBound, Node boundNode, Node predicate, Var
unboundVar,
- ExecutionContext execCxt, Collection<Node> assertedNodes) {
+ ExecutionContext execCxt, Collection<Node> assertedNodes,
QueryRewriteIndex queryRewriteIndex) {
Node featureNode = featureBinding.get(unboundVar);
QueryIterConcat featureIterConcat = new QueryIterConcat(execCxt);
// Check Features directly if not already asserted
if (!assertedNodes.contains(featureNode)) {
- QueryIterator tmpIter = bothBound(featureBinding, isSubjectBound,
boundNode, predicate, featureNode, execCxt);
+ QueryIterator tmpIter = bothBound(featureBinding, isSubjectBound,
boundNode, predicate, featureNode, execCxt, queryRewriteIndex);
featureIterConcat.add(tmpIter);
}
// Also test all Geometry of the Features. All, some or one Geometry
may have matched.
- ExtendedIterator<Node> featureGeometries = G.iterSP(graph,
featureNode, Geo.HAS_GEOMETRY_NODE);
+ // ExtendedIterator<Node> featureGeometries = G.iterSP(graph,
featureNode, Geo.HAS_GEOMETRY_NODE);
+ ExtendedIterator<Node> featureGeometries =
AccessGeoSPARQL.findSpecificGeoResources(graph,
featureNode).mapWith(Triple::getObject);
QueryIterator geometriesQueryIterator = QueryIterPlainWrapper.create(
Iter.map(
Iter.filter( // omit asserted
@@ -286,7 +268,7 @@ public abstract class GenericPropertyFunction extends
PFuncSimple {
geometriesQueryIterator,
b2 -> {
Node geomNode = b2.get(unboundVar);
- return bothBound(b2, isSubjectBound, boundNode, predicate,
geomNode, execCxt);
+ return bothBound(b2, isSubjectBound, boundNode, predicate,
geomNode, execCxt, queryRewriteIndex);
},
execCxt);
diff --git
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/geo/topological/SpatialObjectGeometryLiteral.java
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/geo/topological/SpatialObjectGeometryLiteral.java
index 42484cec1e..35373e96be 100644
---
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/geo/topological/SpatialObjectGeometryLiteral.java
+++
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/geo/topological/SpatialObjectGeometryLiteral.java
@@ -20,16 +20,14 @@ package org.apache.jena.geosparql.geo.topological;
import java.util.Objects;
import org.apache.jena.datatypes.DatatypeFormatException;
+import org.apache.jena.geosparql.implementation.access.AccessGeoSPARQL;
+import org.apache.jena.geosparql.implementation.access.AccessWGS84;
import org.apache.jena.geosparql.implementation.datatype.GeometryDatatype;
import org.apache.jena.geosparql.implementation.vocabulary.Geo;
-import org.apache.jena.geosparql.implementation.vocabulary.SpatialExtension;
-import org.apache.jena.geosparql.spatial.ConvertLatLon;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.NodeFactory;
import org.apache.jena.system.G;
-import org.apache.jena.system.RDFDataException;
-import org.apache.jena.vocabulary.RDF;
/**
*
@@ -99,13 +97,17 @@ public class SpatialObjectGeometryLiteral {
* Objects).
*
* @param graph
- * @param targetSpatialObject
+ * @param targetSpatialObject The spatial object.
* @return SpatialObject/GeometryLiteral pair.
*/
+ // XXX This should return an iterator over all geometry literals rather
than just picking an arbitrary one.
protected static final SpatialObjectGeometryLiteral retrieve(Graph graph,
Node targetSpatialObject) {
+ if (targetSpatialObject == null) {
+ return new SpatialObjectGeometryLiteral(null, null);
+ }
- Node geometry = null;
- if (targetSpatialObject != null && targetSpatialObject.isLiteral()) {
+ // Special case: Directly supplied literal - must be a geometry.
+ if (targetSpatialObject.isLiteral()) {
if (targetSpatialObject.getLiteralDatatype() instanceof
GeometryDatatype) {
return new
SpatialObjectGeometryLiteral(NodeFactory.createBlankNode(),
targetSpatialObject);
} else {
@@ -113,42 +115,28 @@ public class SpatialObjectGeometryLiteral {
}
}
- if (graph.contains(targetSpatialObject, RDF.type.asNode(),
Geo.FEATURE_NODE)) {
- //Target is Feature - find the default Geometry.
- geometry = G.getSP(graph, targetSpatialObject,
Geo.HAS_DEFAULT_GEOMETRY_NODE);
+ // If target has a default geometry then it is implicitly a feature.
+ // Use the feature's default geometry if present ...
+ // XXX The original code did not consider geo:hasGeometry here - does
the spec really only mandate handling of default geometry?
+ Node geometry = G.getSP(graph, targetSpatialObject,
Geo.HAS_DEFAULT_GEOMETRY_NODE);
- } else if (graph.contains(targetSpatialObject, RDF.type.asNode(),
Geo.GEOMETRY_NODE)) {
- //Target is a Geometry.
+ // ... otherwise try to treat the target itself as the geometry
resource.
+ if (geometry == null) {
geometry = targetSpatialObject;
}
- if (geometry != null) {
- //Find the Geometry Literal of the Geometry.
- Node literalNode = G.getSP(graph, geometry,
Geo.HAS_SERIALIZATION_NODE);
- // If hasSerialization not found then check asWKT.
- if (literalNode == null)
- literalNode = G.getSP(graph, geometry, Geo.AS_WKT_NODE);
- // If asWKT not found then check asGML.
- if (literalNode == null)
- literalNode = G.getSP(graph, geometry, Geo.AS_GML_NODE);
- if (literalNode != null)
- return new SpatialObjectGeometryLiteral(targetSpatialObject,
literalNode);
- } else {
- //Target is not a Feature or Geometry but could have Geo
Predicates.
- if ( graph.contains(targetSpatialObject,
SpatialExtension.GEO_LAT_NODE, null)
- && graph.contains(targetSpatialObject,
SpatialExtension.GEO_LON_NODE, null)) {
- try {
- //Extract Lat,Lon coordinate.
- Node lat = G.getOneSP(graph, targetSpatialObject,
SpatialExtension.GEO_LAT_NODE);
- Node lon = G.getOneSP(graph, targetSpatialObject,
SpatialExtension.GEO_LON_NODE);
- Node latLonGeometryLiteral = ConvertLatLon.toNode(lat,
lon);
- return new
SpatialObjectGeometryLiteral(targetSpatialObject, latLonGeometryLiteral);
- } catch ( RDFDataException ex) {
- throw new
DatatypeFormatException(targetSpatialObject.getURI() + " has more than one
geo:lat or geo:lon property.");
- }
- }
+ Node literalNode = AccessGeoSPARQL.getGeoLiteral(graph, geometry);
+
+ // Last resort: Try the legacy WGS84 Geo Positioning vocabulary on the
targetSpatialObject.
+ if (literalNode == null) {
+ literalNode = AccessWGS84.getGeoLiteral(graph,
targetSpatialObject);
+ }
+
+ if (literalNode != null) {
+ return new SpatialObjectGeometryLiteral(targetSpatialObject,
literalNode);
}
+ // No geometry literal found.
return new SpatialObjectGeometryLiteral(null, null);
}
}
diff --git
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/access/AccessGeoSPARQL.java
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/access/AccessGeoSPARQL.java
new file mode 100644
index 0000000000..fd6bfceaf4
--- /dev/null
+++
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/access/AccessGeoSPARQL.java
@@ -0,0 +1,232 @@
+/*
+ * 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.implementation.access;
+
+import java.util.Iterator;
+import java.util.Objects;
+
+import org.apache.jena.atlas.iterator.Iter;
+import org.apache.jena.geosparql.implementation.vocabulary.Geo;
+import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.system.G;
+import org.apache.jena.util.iterator.ExtendedIterator;
+
+/**
+ * Central place for accessing GeoSparql spatial objects in a {@link Graph}.
+ *
+ * Note: Using the "GeoLiterals" methods on RDF data that do not conform to
the GeoSparql
+ * specification will return whatever values are present -
+ * regardless of whether those values are valid literals.
+ */
+public class AccessGeoSPARQL {
+ public static boolean isPredicateOfFeature(Node n) {
+ return n.equals(Geo.HAS_GEOMETRY_NODE) ||
n.equals(Geo.HAS_DEFAULT_GEOMETRY_NODE);
+ }
+
+ public static boolean isPredicateOfGeoResource(Node n) {
+ return n.equals(Geo.AS_WKT_NODE) || n.equals(Geo.AS_GML_NODE) ||
n.equals(Geo.HAS_SERIALIZATION_NODE);
+ }
+
+ public static boolean isTripleOfFeature(Triple t) {
+ return isPredicateOfFeature(t.getPredicate());
+ }
+
+ public static boolean isTripleOfGeoResource(Triple t) {
+ return isPredicateOfGeoResource(t.getPredicate());
+ }
+
+ /** True iff the graph contains geometry literals. */
+ public static boolean containsGeoLiterals(Graph graph) {
+ return containsGeoLiterals(graph, null);
+ }
+
+ /** True iff the node has geometry literals. Arguments must not be null. */
+ public static boolean hasGeoLiterals(Graph graph, Node geometry) {
+ Objects.requireNonNull(geometry);
+ return containsGeoLiterals(graph, geometry);
+ }
+
+ /** True if the node has a geometry or default geometry. Arguments must
not be null. */
+ public static boolean hasGeoResources(Graph graph, Node feature) {
+ Objects.requireNonNull(feature);
+ boolean result =
+ graph.contains(feature, Geo.HAS_DEFAULT_GEOMETRY_NODE, null) ||
+ graph.contains(feature, Geo.HAS_GEOMETRY_NODE, null);
+ return result;
+ }
+
+ /**
+ * True if the node is a geosparql spatial object by the present
(geometry-related) properties.
+ * A mere "SpatialObject" type does not count.
+ * Arguments must not be null. Wgs84 does not count
+ */
+ public static boolean isSpatialObjectByProperties(Graph graph, Node
featureOrGeometry) {
+ return hasGeoLiterals(graph, featureOrGeometry) ||
hasGeoResources(graph, featureOrGeometry);
+ }
+
+ /**
+ * Find all triples with geo:hasDefaultGeometry and geo:hasGeometry
predicates.
+ * If a feature has a default geometry, then this method will omit all its
(non-default) geometries.
+ */
+ public static ExtendedIterator<Triple> findSpecificGeoResources(Graph
graph) {
+ // List resources that have a default geometry followed by those that
+ // only have a non-default one.
+ ExtendedIterator<Triple> result = graph.find(null,
Geo.HAS_DEFAULT_GEOMETRY_NODE, null);
+ try {
+ boolean hasDefaultGeometry = result.hasNext();
+ ExtendedIterator<Triple> it = graph.find(null,
Geo.HAS_GEOMETRY_NODE, null);
+
+ // No default geometry -> no need to filter.
+ result = hasDefaultGeometry
+ ? result.andThen(it.filterDrop(t -> G.hasProperty(graph,
t.getSubject(), Geo.HAS_DEFAULT_GEOMETRY_NODE)))
+ : result.andThen(it);
+ } catch (RuntimeException t) {
+ result.close();
+ throw new RuntimeException(t);
+ }
+ return result;
+ }
+
+ public static ExtendedIterator<Triple> findDefaultGeoResources(Graph
graph) {
+ return graph.find(null, Geo.HAS_DEFAULT_GEOMETRY_NODE, null);
+ }
+
+ public static ExtendedIterator<Triple> findSpecificGeoResources(Graph
graph, Node feature) {
+ Objects.requireNonNull(feature);
+ ExtendedIterator<Triple> result = graph.find(feature,
Geo.HAS_DEFAULT_GEOMETRY_NODE, null);
+ try {
+ if (!result.hasNext()) {
+ result.close();
+ }
+ result = graph.find(feature, Geo.HAS_GEOMETRY_NODE, null);
+ } catch (RuntimeException t) {
+ result.close();
+ throw new RuntimeException(t);
+ }
+ return result;
+ }
+
+ /**
+ * Resolve a feature to its set of specific geometries via the following
chain:
+ * <pre>
+ * feature -> (geo:hasDefaultGeometry, geo:hasGeometry) ->
+ * ({geo:asWKT, geo:asGML}, geo:hasSerialization) -> geo-literal.
+ * </pre>
+ *
+ * If a geo:hasDefaultGeometry does not lead to a valid geo-literal there
is no backtracking to geo:hasGeometry.
+ */
+ public static Iterator<Triple> findSpecificGeoLiteralsByFeature(Graph
graph, Node feature) {
+ return Iter.flatMap(findSpecificGeoResources(graph, feature),
+ t -> findSpecificGeoLiterals(graph, t.getObject()));
+ }
+
+ /**
+ * Iterate all triples of geometry resources with their most specific
serialization form.
+ * The specific properties geo:asWKT and geo:asGML take precedence over
the more general geo:hasSerialization.
+ * This means if a resource has wkt and/or gml then all
geo:hasSerialization triples will be omitted for it.
+ */
+ public static ExtendedIterator<Triple> findSpecificGeoLiterals(Graph
graph) {
+ ExtendedIterator<Triple> result = graph.find(null, Geo.AS_WKT_NODE,
null);
+ try {
+ result = result.andThen(graph.find(null, Geo.AS_GML_NODE, null));
+ // If there is no specific serialization property use the general
one.
+ if (!result.hasNext()) {
+ result.close();
+ result = graph.find(null, Geo.HAS_SERIALIZATION_NODE, null);
+ } else {
+ // Append more general serializations for those resources that
lack a specific one.
+ ExtendedIterator<Triple> it = graph.find(null,
Geo.HAS_SERIALIZATION_NODE, null).filterDrop(t ->
+ G.hasProperty(graph, t.getSubject(), Geo.AS_WKT_NODE) ||
+ G.hasProperty(graph, t.getSubject(), Geo.AS_GML_NODE));
+ result = result.andThen(it);
+ }
+ } catch (RuntimeException t) {
+ result.close();
+ throw new RuntimeException(t);
+ }
+ return result;
+ }
+
+ /**
+ * Iterate a given geometry resource's most specific geometry literals.
+ * The geometry resource must not be null.
+ * A specific serialization (WKT, GML) takes precedence over the more
general hasSerialization property.
+ */
+ public static ExtendedIterator<Triple> findSpecificGeoLiterals(Graph
graph, Node geometry) {
+ Objects.requireNonNull(geometry);
+ ExtendedIterator<Triple> result = graph.find(geometry,
Geo.AS_WKT_NODE, null);
+ try {
+ result = result.andThen(graph.find(geometry, Geo.AS_GML_NODE,
null));
+ if (!result.hasNext()) {
+ result.close();
+ // Fallback to the more generic property.
+ result = graph.find(geometry, Geo.HAS_SERIALIZATION_NODE,
null);
+ }
+ } catch (RuntimeException t) {
+ result.close();
+ throw new RuntimeException(t);
+ }
+ return result;
+ }
+
+ public static Node getGeoLiteral(Graph graph, Node geometry) {
+ Triple t = getGeoLiteralTriple(graph, geometry);
+ Node n = (t == null) ? null : t.getObject();
+ return n;
+ }
+
+ public static Triple getGeoLiteralTriple(Graph graph, Node geometry) {
+ Objects.requireNonNull(geometry);
+
+ // Find the geometry literal of the geometry resource.
+ Triple t;
+ if ((t = getTripleSP(graph, geometry, Geo.HAS_SERIALIZATION_NODE)) !=
null) {
+ return t;
+ }
+
+ // If hasSerialization not found then check asWKT.
+ if ((t = getTripleSP(graph, geometry, Geo.AS_WKT_NODE)) != null) {
+ return t;
+ }
+
+ // If asWKT not found then check asGML.
+ if ((t = getTripleSP(graph, geometry, Geo.AS_GML_NODE)) != null) {
+ return t;
+ }
+
+ return null;
+ }
+
+ private static Triple getTripleSP(Graph graph, Node s, Node p) {
+ Node o = G.getSP(graph, s, p);
+ Triple t = (o == null) ? null : Triple.create(s, p, o);
+ return t;
+ }
+
+ /** Shared code to test whether a node or graph has serialization
properties. */
+ private static boolean containsGeoLiterals(Graph graph, Node node) {
+ boolean result =
+ graph.contains(node, Geo.HAS_SERIALIZATION_NODE, null) ||
+ graph.contains(node, Geo.AS_WKT_NODE, null) ||
+ graph.contains(node, Geo.AS_GML_NODE, null);
+ return result;
+ }
+}
diff --git
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/access/AccessWGS84.java
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/access/AccessWGS84.java
new file mode 100644
index 0000000000..b049c77bd8
--- /dev/null
+++
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/access/AccessWGS84.java
@@ -0,0 +1,155 @@
+/*
+ * 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.implementation.access;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+
+import org.apache.jena.atlas.iterator.Iter;
+import org.apache.jena.datatypes.DatatypeFormatException;
+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.geosparql.spatial.ConvertLatLon;
+import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.system.G;
+import org.apache.jena.system.RDFDataException;
+import org.apache.jena.util.iterator.ExtendedIterator;
+import org.apache.jena.util.iterator.WrappedIterator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Central place for accessing Wgs84 point geometries in a {@link Graph}.
+ */
+public class AccessWGS84 {
+ private static final Logger LOGGER =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ /** True iff the graph contains wgs84:{lat, long} triples.
+ * True does not imply that there are resources that have both lat AND
long properties. */
+ public static boolean containsGeoLiteralProperties(Graph graph) {
+ return containsGeoLiteralProperties(graph, null);
+ }
+
+ /** True iff the node has wgs84:{lat, long} triples.
+ * True does not imply that both lat AND long are present on the node. */
+ public static boolean hasGeoLiteralProperties(Graph graph, Node feature) {
+ Objects.requireNonNull(feature);
+ return containsGeoLiteralProperties(graph, feature);
+ }
+
+ /** For each matching resource, build triples of format 's geo:hasGeometry
geometryLiteral'. */
+ // XXX geo:hasSerialization might seem a better choice but the original
jena-geosparql implementation used geo:hasGeometry.
+ public static ExtendedIterator<Triple> findGeoLiteralsAsTriples(Graph
graph, Node s) {
+ return findGeoLiteralsAsTriples(graph, s, Geo.HAS_GEOMETRY_NODE);
+ }
+
+ /**
+ * For each matching resource and its geometries, create triples of format
's p geometryLiteral'
+ *
+ * @param graph
+ * @param s The match subject. May be null.
+ * @param p The predicate to use for creating triples. Can be chosen
freely but must not be null.
+ * @return Iterator of created triples (not obtained from the graph
directly).
+ */
+ public static ExtendedIterator<Triple> findGeoLiteralsAsTriples(Graph
graph, Node s, Node p) {
+ return findGeoLiterals(graph, s).mapWith(e ->
Triple.create(e.getKey(), p, e.getValue().asNode()));
+ }
+
+ /**
+ * For each matching resource, build geometry literals from the cartesian
product of the WGS84 lat/long properties.
+ * Resources must have both properties, lat and long, to be matched by
this method.
+ */
+ public static ExtendedIterator<Entry<Node, GeometryWrapper>>
findGeoLiterals(Graph graph, Node s) {
+ // Warn about multiple lat/lon combinations only at most once per
graph.
+ boolean enableWarnings = false;
+ boolean[] loggedMultipleLatLons = { false };
+ ExtendedIterator<Triple> latIt = graph.find(s,
SpatialExtension.GEO_LAT_NODE, Node.ANY);
+ ExtendedIterator<Entry<Node, GeometryWrapper>> result =
WrappedIterator.create(Iter.iter(latIt).flatMap(triple -> {
+ Node feature = triple.getSubject();
+ Node lat = triple.getObject();
+
+ // Create the cross-product between lats and lons.
+ ExtendedIterator<Node> lons = G.iterSP(graph, feature,
SpatialExtension.GEO_LON_NODE);
+
+ // On malformed data this can cause lots of log output. Perhaps
it's better to keep validation separate from indexing.
+ int[] lonCounter = {0};
+ ExtendedIterator<Entry<Node, GeometryWrapper>> r =
lons.mapWith(lon -> {
+ if (enableWarnings) {
+ if (lonCounter[0] == 1) {
+ if (!loggedMultipleLatLons[0]) {
+ LOGGER.warn("Geo predicates: multiple longitudes
detected on feature " + feature + ". Further warnings will be omitted.");
+ loggedMultipleLatLons[0] = true;
+ }
+ }
+ ++lonCounter[0];
+ }
+ GeometryWrapper geometryWrapper =
ConvertLatLon.toGeometryWrapper(lat, lon);
+ return Map.entry(feature, geometryWrapper);
+ });
+ return r;
+ }));
+ return result;
+ }
+
+ /**
+ * Read lat/lon values for the given subject. Null if there are no such
properties.
+ * Throws {@link DatatypeFormatException} when detecting incorrect use of
these properties.
+ */
+ public static Node getGeoLiteral(Graph graph, Node s) {
+ Node lat = null;
+ try {
+ lat = G.getZeroOrOneSP(graph, s, SpatialExtension.GEO_LAT_NODE);
+ } catch (RDFDataException ex) {
+ throw new DatatypeFormatException(s + " has more than one geo:lat
property.");
+ }
+
+ Node lon = null;
+ try {
+ lon = G.getZeroOrOneSP(graph, s, SpatialExtension.GEO_LON_NODE);
+ } catch ( RDFDataException ex) {
+ throw new DatatypeFormatException(s + " has more than one geo:lon
property.");
+ }
+
+ // Both null -> return null.
+ if (lat == null && lon == null) {
+ return null;
+ }
+
+ if (lat == null) {
+ throw new DatatypeFormatException(s + " has a geo:lon property but
is missing geo:lat.");
+ }
+ if (lon == null) {
+ throw new DatatypeFormatException(s + " has a geo:lat property but
is missing geo:lon.");
+ }
+ Node geometryLiteral = ConvertLatLon.toNode(lat, lon);
+ return geometryLiteral;
+ }
+
+ private static boolean containsGeoLiteralProperties(Graph graph, Node s) {
+ boolean result =
+ graph.contains(s, SpatialExtension.GEO_LAT_NODE, null) ||
+ graph.contains(s, SpatialExtension.GEO_LON_NODE, null);
+ return result;
+ }
+}
diff --git
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/index/QueryRewriteIndex.java
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/index/QueryRewriteIndex.java
index 6045174e65..c516da0ced 100644
---
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/index/QueryRewriteIndex.java
+++
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/implementation/index/QueryRewriteIndex.java
@@ -181,7 +181,7 @@ public class QueryRewriteIndex {
*/
public static final void prepare(Dataset dataset) {
Context context = dataset.getContext();
- context.set(QUERY_REWRITE_INDEX_SYMBOL, createDefault());
+ set(context, createDefault());
}
/**
@@ -194,7 +194,7 @@ public class QueryRewriteIndex {
*/
public static final void prepare(Dataset dataset, String
queryRewriteLabel, int maxSize, long expiryInterval) {
Context context = dataset.getContext();
- context.set(QUERY_REWRITE_INDEX_SYMBOL, new
QueryRewriteIndex(queryRewriteLabel, maxSize, expiryInterval));
+ set(context, new QueryRewriteIndex(queryRewriteLabel, maxSize,
expiryInterval));
}
/**
@@ -204,10 +204,9 @@ public class QueryRewriteIndex {
* @param execCxt
* @return QueryRewriteIndex contained in the Context.
*/
- public static final QueryRewriteIndex retrieve(ExecutionContext execCxt) {
-
+ public static final QueryRewriteIndex getOrCreate(ExecutionContext
execCxt) {
Context context = execCxt.getContext();
- return retrieve(context);
+ return getOrCreate(context);
}
/**
@@ -217,10 +216,9 @@ public class QueryRewriteIndex {
* @param dataset
* @return QueryRewriteIndex contained in the Context.
*/
- public static final QueryRewriteIndex retrieve(Dataset dataset) {
-
+ public static final QueryRewriteIndex getOrCreate(Dataset dataset) {
Context context = dataset.getContext();
- return retrieve(context);
+ return getOrCreate(context);
}
/**
@@ -230,15 +228,18 @@ public class QueryRewriteIndex {
* @param context
* @return QueryRewriteIndex contained in the Context.
*/
- public static final QueryRewriteIndex retrieve(Context context) {
- QueryRewriteIndex queryRewriteIndex =
context.get(QUERY_REWRITE_INDEX_SYMBOL, null);
+ public static final QueryRewriteIndex getOrCreate(Context context) {
+ QueryRewriteIndex queryRewriteIndex =
context.computeIfAbsent(QUERY_REWRITE_INDEX_SYMBOL, k -> createDefault());
+ return queryRewriteIndex;
+ }
- if (queryRewriteIndex == null) {
- queryRewriteIndex = createDefault();
- context.set(QUERY_REWRITE_INDEX_SYMBOL, queryRewriteIndex);
- }
+ public static final QueryRewriteIndex get(Context context) {
+ return (context == null) ? null :
context.get(QUERY_REWRITE_INDEX_SYMBOL);
+ }
- return queryRewriteIndex;
+ public static final Context set(Context context, QueryRewriteIndex
queryRewriteIndex) {
+ context.set(QUERY_REWRITE_INDEX_SYMBOL, queryRewriteIndex);
+ return context;
}
/**
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
index cc9efa7e42..c2264faad4 100644
---
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
@@ -17,29 +17,23 @@
*/
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.geosparql.implementation.access.AccessGeoSPARQL;
+import org.apache.jena.geosparql.implementation.access.AccessWGS84;
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.<br>
*
@@ -47,22 +41,21 @@ public class SpatialIndexFindUtils {
* @param srsURI
* @return SpatialIndexItems found.
*/
- public static IteratorCloseable<SpatialIndexItem>
findSpatialIndexItems(DatasetGraph datasetGraph, String srsURI) {
+ public static IteratorCloseable<SpatialIndexItem>
findIndexItems(DatasetGraph datasetGraph, String srsURI) {
Graph defaultGraph = datasetGraph.getDefaultGraph();
- IteratorCloseable<SpatialIndexItem> itemsIter =
findSpatialIndexItems(defaultGraph, srsURI);
+ IteratorCloseable<SpatialIndexItem> itemsIter =
findIndexItems(defaultGraph, srsURI);
try {
//Named Models
Iterator<Node> graphNodeIt = datasetGraph.listGraphNodes();
Iterator<SpatialIndexItem> namedGraphItemsIt =
Iter.iter(graphNodeIt).flatMap(graphNode -> {
Graph namedGraph = datasetGraph.getGraph(graphNode);
- IteratorCloseable<SpatialIndexItem> graphItems =
findSpatialIndexItems(namedGraph, srsURI);
+ IteratorCloseable<SpatialIndexItem> graphItems =
findIndexItems(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;
+ throw new RuntimeException(t);
}
return itemsIter;
}
@@ -74,69 +67,33 @@ public class SpatialIndexFindUtils {
* @param srsURI
* @return Items found in the Model in the SRS URI.
*/
- public static final IteratorCloseable<SpatialIndexItem>
findSpatialIndexItems(Graph graph, String srsURI) {
+ public static final IteratorCloseable<SpatialIndexItem>
findIndexItems(Graph graph, String srsURI) {
IteratorCloseable<SpatialIndexItem> 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);
+ if (AccessGeoSPARQL.containsGeoLiterals(graph)) {
+ result = findIndexItemsGeoSparql(graph, srsURI);
+ } else if (AccessWGS84.containsGeoLiteralProperties(graph)) {
+ result = findIndexItemsWgs84(graph, srsURI);
} else {
result = Iter.empty();
}
return result;
}
- /** Print out log messages for what type of spatial data is found in the
given graph. */
- public static final void checkSpatialIndexItems(Graph graph) {
- // 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.");
- }
- } else if (graph.contains(null, SpatialExtension.GEO_LAT_NODE, null)) {
- LOGGER.info("Geo predicate statements found.");
- }
- }
-
/**
*
* @param graph
* @param srsURI
* @return SpatialIndexItem items prepared for adding to SpatialIndex.
*/
- public static IteratorCloseable<SpatialIndexItem>
findGeometryIndexItems(Graph graph, String srsURI) {
- Iterator<Triple> stmtIter = graph.find(null, Geo.HAS_GEOMETRY_NODE,
null);
+ public static IteratorCloseable<SpatialIndexItem>
findIndexItemsGeoSparql(Graph graph, String srsURI) {
+ Iterator<Triple> stmtIter =
AccessGeoSPARQL.findSpecificGeoResources(graph);
IteratorCloseable<SpatialIndexItem> result =
Iter.iter(stmtIter).flatMap(stmt -> {
Node feature = stmt.getSubject();
Node geometry = stmt.getObject();
-
- Iterator<Node> nodeIter = G.iterSP(graph, geometry,
Geo.HAS_SERIALIZATION_NODE);
-
- // XXX If there is a super-property then the concrete
serializations are not tried.
- try {
- if (!nodeIter.hasNext()) {
- Iter.close(nodeIter);
-
- Iterator<Node> wktNodeIter = G.iterSP(graph, geometry,
Geo.AS_WKT_NODE);
- nodeIter = wktNodeIter;
-
- Iterator<Node> 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<SpatialIndexItem> itemIter = Iter.map(nodeIter,
geometryNode -> {
+ Iterator<Triple> serializationIter =
AccessGeoSPARQL.findSpecificGeoLiterals(graph, geometry);
+ Iterator<SpatialIndexItem> itemIter = Iter.map(serializationIter,
triple -> {
+ Node geometryNode = triple.getObject();
GeometryWrapper geometryWrapper =
GeometryWrapper.extract(geometryNode);
SpatialIndexItem item = makeSpatialIndexItem(feature,
geometryWrapper, srsURI);
return item;
@@ -147,42 +104,19 @@ public class SpatialIndexFindUtils {
}
/**
+ *
*
* @param graph
* @param srsURI
* @return Geo predicate objects prepared for adding to SpatialIndex.
*/
- public static IteratorCloseable<SpatialIndexItem>
findGeoPredicateIndexItems(Graph graph, String srsURI) {
- // Warn about multiple lat/lon combinations only at most once per
graph.
- boolean enableWarnings = false;
- boolean[] loggedMultipleLatLons = { false };
- Iterator<Triple> latIt = graph.find(Node.ANY,
SpatialExtension.GEO_LAT_NODE, Node.ANY);
- IteratorCloseable<SpatialIndexItem> result =
Iter.iter(latIt).flatMap(triple -> {
- Node feature = triple.getSubject();
- Node lat = triple.getObject();
-
- // Create the cross-product between lats and lons.
- Iterator<Node> lons = G.iterSP(graph, feature,
SpatialExtension.GEO_LON_NODE);
-
- // On malformed data this can cause lots of log output. Perhaps
it's better to keep validation separate from indexing.
- int[] lonCounter = {0};
- Iterator<SpatialIndexItem> r = Iter.iter(lons).map(lon -> {
- if (enableWarnings) {
- if (lonCounter[0] == 1) {
- if (!loggedMultipleLatLons[0]) {
- LOGGER.warn("Geo predicates: multiple longitudes
detected on feature " + feature + ". Further warnings will be omitted.");
- loggedMultipleLatLons[0] = true;
- }
- }
- ++lonCounter[0];
- }
- GeometryWrapper geometryWrapper =
ConvertLatLon.toGeometryWrapper(lat, lon);
- SpatialIndexItem item = makeSpatialIndexItem(feature,
geometryWrapper, srsURI);
- return item;
- });
- return r;
+ public static IteratorCloseable<SpatialIndexItem>
findIndexItemsWgs84(Graph graph, String srsURI) {
+ return Iter.iter(AccessWGS84.findGeoLiterals(graph, null)).map(e -> {
+ Node feature = e.getKey();
+ GeometryWrapper geometryWrapper = e.getValue();
+ SpatialIndexItem item = makeSpatialIndexItem(feature,
geometryWrapper, srsURI);
+ return item;
});
- return result;
}
public static SpatialIndexItem makeSpatialIndexItem(Node feature,
GeometryWrapper geometryWrapper, String srsURI) {
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
index 37775eae53..15e40b2754 100644
---
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
@@ -40,7 +40,7 @@ public class STRtreeUtils {
public static STRtree buildSpatialIndexTree(Graph graph, String srsURI)
throws SpatialIndexException {
try {
STRtree tree;
- IteratorCloseable<SpatialIndexItem> it =
SpatialIndexFindUtils.findSpatialIndexItems(graph, srsURI);
+ IteratorCloseable<SpatialIndexItem> it =
SpatialIndexFindUtils.findIndexItems(graph, srsURI);
try {
tree = buildSpatialIndexTree(it);
} finally {
diff --git
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexLib.java
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexLib.java
index 4395de06b4..7541912e9d 100644
---
a/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexLib.java
+++
b/jena-geosparql/src/main/java/org/apache/jena/geosparql/spatial/index/v2/SpatialIndexLib.java
@@ -112,13 +112,13 @@ public class SpatialIndexLib {
}
/**
- * Retrieve the SpatialIndex from the Context.
+ * Get the SpatialIndex from the Context. Fail if absent.
*
* @param execCxt
* @return SpatialIndex contained in the Context.
* @throws SpatialIndexException
*/
- public static final SpatialIndex retrieve(ExecutionContext execCxt) throws
SpatialIndexException {
+ public static final SpatialIndex require(ExecutionContext execCxt) throws
SpatialIndexException {
Context context = execCxt.getContext();
SpatialIndex spatialIndex = (SpatialIndex)
context.get(SpatialIndexConstants.symSpatialIndex, null);
if (spatialIndex == null) {
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 12625b59ac..a932ffc43c 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
@@ -17,19 +17,16 @@
*/
package org.apache.jena.geosparql.spatial.property_functions;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
-import java.util.List;
import java.util.stream.Stream;
-import org.apache.commons.collections4.iterators.IteratorChain;
+import org.apache.jena.atlas.iterator.Iter;
import org.apache.jena.datatypes.DatatypeFormatException;
import org.apache.jena.geosparql.implementation.GeometryWrapper;
import org.apache.jena.geosparql.implementation.SRSInfo;
-import org.apache.jena.geosparql.implementation.vocabulary.Geo;
-import org.apache.jena.geosparql.implementation.vocabulary.SpatialExtension;
-import org.apache.jena.geosparql.spatial.ConvertLatLon;
+import org.apache.jena.geosparql.implementation.access.AccessGeoSPARQL;
+import org.apache.jena.geosparql.implementation.access.AccessWGS84;
import org.apache.jena.geosparql.spatial.SearchEnvelope;
import org.apache.jena.geosparql.spatial.SpatialIndex;
import org.apache.jena.geosparql.spatial.SpatialIndexException;
@@ -49,8 +46,6 @@ import org.apache.jena.sparql.expr.ExprEvalException;
import org.apache.jena.sparql.pfunction.PFuncSimpleAndList;
import org.apache.jena.sparql.pfunction.PropFuncArg;
import org.apache.jena.sparql.util.FmtUtils;
-import org.apache.jena.system.G;
-import org.apache.jena.util.iterator.ExtendedIterator;
/**
*
@@ -66,7 +61,7 @@ public abstract class GenericSpatialPropertyFunction extends
PFuncSimpleAndList
@Override
public final QueryIterator execEvaluated(Binding binding, Node subject,
Node predicate, PropFuncArg object, ExecutionContext execCxt) {
try {
- spatialIndex = SpatialIndexLib.retrieve(execCxt);
+ spatialIndex = SpatialIndexLib.require(execCxt);
spatialArguments = extractObjectArguments(predicate, object,
spatialIndex.getSrsInfo());
return search(binding, execCxt, subject, spatialArguments.limit);
} catch (SpatialIndexException ex) {
@@ -110,47 +105,23 @@ public abstract class GenericSpatialPropertyFunction
extends PFuncSimpleAndList
try {
Graph graph = execCxt.getActiveGraph();
- IteratorChain<Triple> spatialTriples = new IteratorChain<>();
-
- //Check for Geometry and so GeometryLiterals.
- if (graph.contains(subject, Geo.HAS_GEOMETRY_NODE, null)) {
- //A Feature can have many geometries so add each of them. The
check Geo.HAS_DEFAULT_GEOMETRY_NODE will only return one but requires the data
to have these present.
- List<Node> geometryNodes = G.listSP(graph, subject,
Geo.HAS_GEOMETRY_NODE);
- geometryNodes.forEach(geometry->{
- ExtendedIterator<Triple> iter = graph.find(geometry,
Geo.HAS_SERIALIZATION_NODE, null);
- // Check for asWKT
- if (!iter.hasNext()) {
- iter = graph.find(geometry, Geo.AS_WKT_NODE, null);
- }
- // Check for asGML
- if (!iter.hasNext()) {
- iter = graph.find(geometry, Geo.AS_GML_NODE, null);
- }
- spatialTriples.addIterator(iter);
- });
+ Iterator<Triple> spatialTriples;
+
+ if (AccessGeoSPARQL.containsGeoLiterals(graph)) {
+ spatialTriples =
AccessGeoSPARQL.findSpecificGeoLiteralsByFeature(graph, subject);
+ } else if (AccessWGS84.containsGeoLiteralProperties(graph)) {
+ spatialTriples = AccessWGS84.findGeoLiteralsAsTriples(graph,
subject);
} else {
- //Check for Geo predicates against the feature when no
geometry literals found.
- if (graph.contains(subject, SpatialExtension.GEO_LAT_NODE,
null) && graph.contains(subject, SpatialExtension.GEO_LON_NODE, null)) {
- Node lat = G.getOneSP(graph, subject,
SpatialExtension.GEO_LAT_NODE);
- Node lon = G.getOneSP(graph, subject,
SpatialExtension.GEO_LON_NODE);
- Node latLonGeometryLiteral = ConvertLatLon.toNode(lat,
lon);
- Triple triple = Triple.create(subject,
Geo.HAS_GEOMETRY_NODE, latLonGeometryLiteral);
-
spatialTriples.addIterator(Arrays.asList(triple).iterator());
- }
+ spatialTriples = Iter.empty();
}
//Check through each Geometry and stop if one is accepted.
- boolean isMatched = false;
- while (spatialTriples.hasNext()) {
- Triple triple = spatialTriples.next();
+ boolean isMatched = Iter.anyMatch(spatialTriples, triple -> {
Node geometryLiteral = triple.getObject();
GeometryWrapper targetGeometryWrapper =
GeometryWrapper.extract(geometryLiteral);
- isMatched = checkSecondFilter(spatialArguments,
targetGeometryWrapper);
- if (isMatched) {
- //Stop checking when match is true.
- break;
- }
- }
+ boolean isMatch = checkSecondFilter(spatialArguments,
targetGeometryWrapper);
+ return isMatch;
+ });
return isMatched;
} catch (DatatypeFormatException ex) {
diff --git
a/jena-geosparql/src/test/java/org/apache/jena/geosparql/geo/topological/property_functions/simple_features/SfPFMiscSparqlTest.java
b/jena-geosparql/src/test/java/org/apache/jena/geosparql/geo/topological/property_functions/simple_features/SfPFMiscSparqlTest.java
new file mode 100644
index 0000000000..a329e1cb32
--- /dev/null
+++
b/jena-geosparql/src/test/java/org/apache/jena/geosparql/geo/topological/property_functions/simple_features/SfPFMiscSparqlTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.geo.topological.property_functions.simple_features;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.jena.riot.Lang;
+import org.apache.jena.riot.RDFParser;
+import org.apache.jena.sparql.algebra.Table;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.exec.QueryExec;
+import org.apache.jena.sparql.sse.SSE;
+import org.junit.Test;
+
+/** Miscellaneous SPARQL-based tests across the simple feature family of
property functions. */
+public class SfPFMiscSparqlTest {
+
+ @Test
+ public void test01() {
+ String query = """
+ PREFIX geo: <http://www.opengis.net/ont/geosparql#>
+ PREFIX ogcsf: <http://www.opengis.net/ont/sf#>
+ SELECT * {
+ ?s a ogcsf:Point .
+ ?s geo:sfWithin <urn:test:geosparql#geoFrance> .
+ } ORDER BY ?s
+ """;
+
+ DatasetGraph dsg = createTestDataFrance();
+ Table actual = QueryExec.dataset(dsg).query(query).table();
+ Table expected = SSE.parseTable("(table (row (?s
<urn:test:geosparql#geoStrasbourg>) ))");
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void test02() {
+ String query = """
+ PREFIX geo: <http://www.opengis.net/ont/geosparql#>
+ SELECT * {
+ ?s geo:sfWithin <urn:test:geosparql#geoFrance> .
+ } ORDER BY ?s
+ """;
+
+ // Note: sfWithin is reflexive so 'geoFrance' is really expected as a
result.
+ DatasetGraph dsg = createTestDataFrance();
+ Table actual = QueryExec.dataset(dsg).query(query).table();
+ Table expected = SSE.parseTable("(table (row (?s
<urn:test:geosparql#geoFrance>) ) (row (?s <urn:test:geosparql#geoStrasbourg>)
))");
+ assertEquals(expected, actual);
+ }
+
+ // Test data derived from GH-3473.
+ private static DatasetGraph createTestDataFrance() {
+ String data = """
+ PREFIX : <urn:test:geosparql#>
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ PREFIX geo: <http://www.opengis.net/ont/geosparql#>
+ PREFIX ogcsf: <http://www.opengis.net/ont/sf#>
+
+ :France
+ rdfs:label "France";
+ geo:hasGeometry :geoFrance.
+
+ :geoFrance a ogcsf:Polygon ;
+ # This is a bounding box of France.
+ geo:asWKT "POLYGON((-4.9423 41.3247, -4.9423 51.1496, 10.02105
51.1496, 10.0210 41.3247, -4.9423 41.3247))"^^geo:wktLiteral .
+
+ :Strasbourg
+ rdfs:label "Strasbourg";
+ geo:hasGeometry :geoStrasbourg.
+
+ :geoStrasbourg a ogcsf:Point ;
+ geo:asWKT "POINT(7.7510 48.5819)"^^geo:wktLiteral .
+
+ # This point is outside of France's BBOX.
+ :Berlin
+ rdfs:label "Berlin";
+ geo:hasGeometry :geoBerlin.
+
+ :geoBerlin a ogcsf:Point ;
+ geo:asWKT "POINT (13.4050 52.5200)"^^geo:wktLiteral .
+ """;
+ return
RDFParser.create().fromString(data).lang(Lang.TRIG).toDatasetGraph();
+ }
+}