Skip to content

Commit 59897bf

Browse files
committed
GH-3473: GeoSparql: Remove need for 'a geo:Geometry' in GenericPropertyFunction; Spatial Indexer UI: Replace without selection clears index.
1 parent 29e8a56 commit 59897bf

File tree

22 files changed

+1172
-330
lines changed

22 files changed

+1172
-330
lines changed

jena-arq/src/main/java/org/apache/jena/rdfs/DatasetGraphRDFS.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ public Graph getGraph(Node graphNode) {
6767
return new GraphRDFS(base, setup);
6868
}
6969

70+
@Override
71+
public Iterator<Quad> find()
72+
{ return find(Node.ANY, Node.ANY, Node.ANY, Node.ANY); }
73+
7074
// Quad-centric access
7175
@Override
7276
public Iterator<Quad> find(Quad quad) {

jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphQuads.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public void removeGraph(Node graphName) {
4444

4545
@Override
4646
public void addGraph(Node graphName, Graph graph) {
47-
graph.find().forEachRemaining(t -> add(Quad.create(graphName, t)));
47+
graph.find().forEach(t -> add(Quad.create(graphName, t)));
4848
}
4949

5050
// @Override

jena-arq/src/main/java/org/apache/jena/sparql/core/mem/DatasetGraphInMemory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,10 +346,10 @@ public Graph getUnionGraph() {
346346
}
347347

348348
private Consumer<Graph> addGraph(final Node name) {
349-
return g -> g.find().mapWith(t -> new Quad(name, t)).forEachRemaining(this::add);
349+
return g -> g.find().mapWith(t -> new Quad(name, t)).forEach(this::add);
350350
}
351351

352-
private final Consumer<Graph> removeGraph = g -> g.find().forEachRemaining(g::delete);
352+
private final Consumer<Graph> removeGraph = g -> g.find().forEach(g::delete);
353353

354354
@Override
355355
public void addGraph(final Node graphName, final Graph graph) {

jena-arq/src/main/java/org/apache/jena/sparql/util/Context.java

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.concurrent.atomic.AtomicBoolean;
2525
import java.util.function.BiConsumer;
2626
import java.util.function.BiFunction;
27+
import java.util.function.Function;
2728

2829
import org.apache.jena.atlas.lib.Lib;
2930
import org.apache.jena.atlas.logging.Log;
@@ -389,12 +390,6 @@ public void clear() {
389390
context.clear();
390391
}
391392

392-
/** Atomic compute. */
393-
@SuppressWarnings("unchecked")
394-
public <V> V compute(Symbol key, BiFunction<Symbol, Object, ? extends V> remappingFunction) {
395-
return (V)context.compute(key, remappingFunction);
396-
}
397-
398393
@Override
399394
public String toString() {
400395
String x = "";
@@ -439,13 +434,36 @@ public static AtomicBoolean getCancelSignal(Context context) {
439434
}
440435
}
441436

437+
/** Atomic compute. */
438+
@SuppressWarnings("unchecked")
439+
public <V> V compute(Symbol key, BiFunction<Symbol, Object, ? extends V> remappingFunction) {
440+
Object obj = context.compute(key, remappingFunction);
441+
return (V)obj;
442+
}
443+
444+
/** Atomic computeIfAbsent. */
445+
@SuppressWarnings("unchecked")
446+
public <V> V computeIfAbsent(Symbol key, Function<Symbol, ? extends V> mappingFunction) {
447+
Object obj = context.computeIfAbsent(key, mappingFunction);
448+
return (V)obj;
449+
}
450+
451+
/** Atomic computeIfPresent. */
452+
@SuppressWarnings("unchecked")
453+
public <V> V computeIfPresent(Symbol key, BiFunction<Symbol, Object, V> remappingFunction) {
454+
Object obj = context.computeIfPresent(key, remappingFunction);
455+
return (V)obj;
456+
}
457+
458+
/** Get the context's cancel signal. Create and set one if needed. Context must not be null. */
442459
public static AtomicBoolean getOrSetCancelSignal(Context context) {
443-
AtomicBoolean cancelSignal = getCancelSignal(context);
444-
if (cancelSignal == null) {
445-
cancelSignal = new AtomicBoolean(false);
446-
context.set(ARQConstants.symCancelQuery, cancelSignal);
460+
try {
461+
AtomicBoolean result = context.computeIfAbsent(ARQConstants.symCancelQuery, sym -> new AtomicBoolean(false));
462+
return result;
463+
} catch (ClassCastException ex) {
464+
Log.error(Context.class, "Class cast exception: Expected AtomicBoolean for cancel control: "+ex.getMessage());
465+
return null;
447466
}
448-
return cancelSignal;
449467
}
450468

451469
/** Merge an outer (defaults to the system global context)

jena-arq/src/test/java/org/apache/jena/rdfs/TestDatasetGraphRDFS.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import static org.apache.jena.rdfs.engine.ConstRDFS.rdfType;
2525
import static org.junit.jupiter.api.Assertions.assertEquals;
2626
import static org.junit.jupiter.api.Assertions.assertFalse;
27+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
2728
import static org.junit.jupiter.api.Assertions.assertTrue;
2829

2930
import java.io.PrintStream;
@@ -77,6 +78,12 @@ public static void beforeClass() {
7778
Iter.consume(iter3);
7879
}
7980

81+
@Test public void dsg_find_all() {
82+
List<Quad> baseQuads = Iter.toList(dsg.getBase().find());
83+
List<Quad> inferredQuads = Iter.toList(dsg.find());
84+
assertNotEquals(baseQuads, inferredQuads);
85+
}
86+
8087
@Test public void dsg_find_graph() {
8188
List<Quad> x = test(node("g"), node("a"), rdfType, null);
8289
assertTrue(hasNG(x, node("g"))) ;
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.jena.geosparql.query;
19+
20+
import java.io.ByteArrayOutputStream;
21+
import java.nio.charset.StandardCharsets;
22+
import java.time.LocalDateTime;
23+
import java.time.format.DateTimeFormatter;
24+
import java.util.LinkedHashMap;
25+
import java.util.Map;
26+
import java.util.concurrent.TimeUnit;
27+
import java.util.stream.Stream;
28+
29+
import org.apache.jena.geosparql.implementation.GeometryWrapper;
30+
import org.apache.jena.geosparql.implementation.jts.CustomGeometryFactory;
31+
import org.apache.jena.geosparql.implementation.vocabulary.Geo;
32+
import org.apache.jena.geosparql.spatial.index.v2.GeometryGenerator;
33+
import org.apache.jena.geosparql.spatial.index.v2.GeometryGenerator.GeometryType;
34+
import org.apache.jena.graph.Graph;
35+
import org.apache.jena.graph.Node;
36+
import org.apache.jena.graph.NodeFactory;
37+
import org.apache.jena.graph.Triple;
38+
import org.apache.jena.riot.RDFDataMgr;
39+
import org.apache.jena.riot.RDFFormat;
40+
import org.apache.jena.sparql.graph.GraphFactory;
41+
import org.apache.jena.system.G;
42+
import org.apache.jena.vocabulary.RDF;
43+
import org.locationtech.jts.geom.Envelope;
44+
import org.locationtech.jts.geom.Geometry;
45+
import org.locationtech.jts.geom.util.AffineTransformation;
46+
import org.openjdk.jmh.annotations.Benchmark;
47+
import org.openjdk.jmh.annotations.Level;
48+
import org.openjdk.jmh.annotations.Mode;
49+
import org.openjdk.jmh.annotations.Param;
50+
import org.openjdk.jmh.annotations.Scope;
51+
import org.openjdk.jmh.annotations.Setup;
52+
import org.openjdk.jmh.annotations.State;
53+
import org.openjdk.jmh.annotations.TearDown;
54+
import org.openjdk.jmh.results.format.ResultFormatType;
55+
import org.openjdk.jmh.runner.Runner;
56+
import org.openjdk.jmh.runner.RunnerException;
57+
import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
58+
import org.openjdk.jmh.runner.options.Options;
59+
import org.openjdk.jmh.runner.options.OptionsBuilder;
60+
import org.openjdk.jmh.runner.options.TimeValue;
61+
62+
/**
63+
* Benchmarking of spatial queries against test data.
64+
*/
65+
@State(Scope.Benchmark)
66+
public class BenchmarkSpatialQueries {
67+
68+
private static Map<String, String> idToQuery = new LinkedHashMap<>();
69+
70+
private Node featureNode = NodeFactory.createURI("urn:test:geosparql:feature1");
71+
private Node geometryNode = NodeFactory.createURI("urn:test:geosparql:geometry1");
72+
73+
private static final String q1 = """
74+
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
75+
PREFIX ogcsf: <http://www.opengis.net/ont/sf#>
76+
77+
SELECT *
78+
WHERE {
79+
?s geo:sfWithin <urn:test:geosparql:geometry1> .
80+
}
81+
""";
82+
83+
private static final String q2 = """
84+
PREFIX geo: <http://www.opengis.net/ont/geosparql#>
85+
PREFIX ogcsf: <http://www.opengis.net/ont/sf#>
86+
87+
SELECT *
88+
WHERE {
89+
?s a ogcsf:Point .
90+
?s geo:sfWithin <urn:test:geosparql:geometry1> .
91+
}
92+
""";
93+
94+
static {
95+
idToQuery.put("q1", q1);
96+
idToQuery.put("q2", q2);
97+
}
98+
99+
/** Essentially the size of the data. One geometry mix includes every WKT geometry type once (with different coordinates). */
100+
@Param({
101+
"10000",
102+
})
103+
public long p1_geoMixes;
104+
105+
@Param({
106+
"q1",
107+
"q2",
108+
})
109+
public String p2_queryId;
110+
111+
@Param({
112+
"off",
113+
"virtual",
114+
"materialized"
115+
})
116+
public String p3_inferences;
117+
118+
@Param({
119+
"false",
120+
"true"
121+
})
122+
public boolean p4_index;
123+
124+
@Param({
125+
"current",
126+
"5.5.0"
127+
})
128+
public String p5_jenaVersion;
129+
130+
private SpatialQueryTask task;
131+
132+
@Benchmark
133+
public void run() throws Exception {
134+
long count = task.exec();
135+
if (true) {
136+
System.out.println("Counted: " + count);
137+
}
138+
}
139+
140+
private static GeometryWrapper toWrapperWkt(Geometry geometry) {
141+
GeometryWrapper result = new GeometryWrapper(geometry, Geo.WKT);
142+
return result;
143+
}
144+
145+
@Setup(Level.Trial)
146+
public void setupTrial() throws Exception {
147+
Envelope dataBbox = new Envelope(-175, 175, -85, 85);
148+
Map<GeometryType, Number> config = GeometryGenerator.createConfig(p1_geoMixes);
149+
Graph graph = GraphFactory.createDefaultGraph();
150+
GeometryGenerator.generateGraph(graph, dataBbox, config);
151+
152+
// Build a search-bbox by scaling the data-generation-bbox down.
153+
Geometry dataBboxGeom = CustomGeometryFactory.theInstance().toGeometry(dataBbox);
154+
double x = dataBboxGeom.getCentroid().getX();
155+
double y = dataBboxGeom.getCentroid().getY();
156+
Geometry searchBboxGeom = AffineTransformation.scaleInstance(0.25, 0.25, x, y).transform(dataBboxGeom);
157+
158+
// Add search bbox and feature/resource to the benchmark data.
159+
Node searchBboxNode = toWrapperWkt(searchBboxGeom).asNode();
160+
graph.add(featureNode, Geo.HAS_GEOMETRY_NODE, geometryNode);
161+
graph.add(geometryNode, Geo.AS_WKT_NODE, searchBboxNode);
162+
163+
// Post process test data:
164+
// - Add "geom a Point" triples to geometry resources with a Point WKT literal.
165+
// - Add explicit Geometry type to all geometry resources (required by jena-geosparql 5.5.0 and earlier).
166+
Node Point = NodeFactory.createURI("http://www.opengis.net/ont/sf#Point");
167+
Graph extraGraph = GraphFactory.createDefaultGraph();
168+
try (Stream<Triple> stream = graph.stream(null, Geo.AS_WKT_NODE, null)) {
169+
stream.forEach(t -> {
170+
GeometryWrapper gw = GeometryWrapper.extract(t.getObject());
171+
String geoType = gw.getGeometryType();
172+
if (geoType.equals("Point")) {
173+
extraGraph.add(t.getSubject(), RDF.Nodes.type, Point);
174+
}
175+
176+
extraGraph.add(t.getSubject(), RDF.Nodes.type, Geo.GEOMETRY_NODE);
177+
});
178+
}
179+
G.addInto(graph, extraGraph);
180+
181+
String data;
182+
RDFFormat fmt = RDFFormat.TURTLE_PRETTY;
183+
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
184+
RDFDataMgr.write(out, graph, fmt);
185+
out.flush();
186+
data = new String(out.toByteArray(), StandardCharsets.UTF_8);
187+
}
188+
189+
task = switch (p5_jenaVersion) {
190+
case "current" -> new SpatialQueryTaskCurrent();
191+
case "5.5.0" -> new SpatialQueryTask550();
192+
default -> throw new RuntimeException("No task registered for this jena version:" + p5_jenaVersion);
193+
};
194+
195+
task.setData(data);
196+
197+
switch (p3_inferences) {
198+
case "off": task.setInferenceMode(false, false); break;
199+
case "virtual": task.setInferenceMode(true, false); break;
200+
case "materialized": task.setInferenceMode(true, true); break;
201+
default:
202+
throw new IllegalArgumentException("Unsupported inference mode: " + p3_inferences);
203+
}
204+
205+
task.setIndex(p4_index);
206+
207+
String queryString = idToQuery.get(p2_queryId);
208+
task.setQuery(queryString);
209+
}
210+
211+
@TearDown(Level.Trial)
212+
public void tearDownTrial() throws Exception {
213+
}
214+
215+
public static ChainedOptionsBuilder getDefaults(Class<?> c) {
216+
return new OptionsBuilder()
217+
// Specify which benchmarks to run.
218+
// You can be more specific if you'd like to run only one benchmark per test.
219+
.include(c.getName())
220+
// Set the following options as needed
221+
.mode(Mode.AverageTime)
222+
.timeUnit(TimeUnit.SECONDS)
223+
.warmupTime(TimeValue.NONE)
224+
.warmupIterations(5)
225+
.measurementIterations(5)
226+
.measurementTime(TimeValue.NONE)
227+
.threads(1)
228+
.forks(1)
229+
.shouldFailOnError(true)
230+
.shouldDoGC(true)
231+
//.jvmArgs("-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintInlining")
232+
.jvmArgs("-Xmx8G")
233+
//.addProfiler(WinPerfAsmProfiler.class)
234+
.resultFormat(ResultFormatType.JSON)
235+
.result(c.getSimpleName() + "_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".json");
236+
}
237+
238+
public static void main(String[] args) throws RunnerException {
239+
Options opt = getDefaults(BenchmarkSpatialQueries.class).build();
240+
new Runner(opt).run();
241+
}
242+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.jena.geosparql.query;
20+
21+
public interface SpatialQueryTask {
22+
void setData(String trigString) throws Exception;
23+
void setInferenceMode(boolean enableInferences, boolean materialize) throws Exception;
24+
void setQuery(String queryString) throws Exception;
25+
void setIndex(boolean isEnabled);
26+
long exec();
27+
}

0 commit comments

Comments
 (0)