Skip to content

Commit 1a9c030

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

File tree

8 files changed

+334
-123
lines changed

8 files changed

+334
-123
lines changed

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 compute. */
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 compute. */
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-fuseki2/jena-fuseki-mod-geosparql/src/main/java/org/apache/jena/fuseki/mod/geosparql/SpatialIndexerService.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,18 +120,26 @@ private class EndpointClients {
120120

121121
public SpatialIndexerService() {}
122122

123-
private static <T, C extends Collection<Node>> Set<Node> extractGraphsFromRequest(DatasetGraph dsg, HttpAction action) {
123+
/**
124+
* Extract the explicit set of graphs from the action w.r.t. the gataset graph.
125+
*
126+
* @param dsg The dataset graph.
127+
* @param action The HTTP action.
128+
* @param emptySelectionToAllGraphs Select all graphs if the request specifies an empty selection of graphs.
129+
* @return The explicit set of graphs w.r.t. the dataset graph.
130+
*/
131+
private static Set<Node> extractGraphsFromRequest(DatasetGraph dsg, HttpAction action, boolean emptySelectionToAllGraphs) {
124132
String uris = action.getRequest().getParameter(HttpNames.paramGraph);
125133
Collection<String> strs;
126134
if (uris == null || uris.isBlank()) {
127135
strs = List.of(Quad.defaultGraphIRI.toString(), Quad.unionGraph.toString());
128136
} else {
129-
TypeToken<List<String>> typeToken = new TypeToken<List<String>>(){};
137+
TypeToken<List<String>> typeToken = new TypeToken<>(){};
130138
strs = gsonForSse.fromJson(uris, typeToken);
131139
}
132140
List<Node> rawGraphNodes = strs.stream().map(NodeFactory::createURI).distinct().toList();
133141
// If the set of specified graphs is empty then index all.
134-
if (rawGraphNodes.isEmpty()) {
142+
if (rawGraphNodes.isEmpty() && emptySelectionToAllGraphs) {
135143
rawGraphNodes = List.of(Quad.defaultGraphIRI, Quad.unionGraph);
136144
}
137145

@@ -453,7 +461,7 @@ protected BasicTask scheduleIndexTask(HttpAction action, SpatialIndexerComputati
453461

454462
long graphCount = indexComputation.getGraphNodes().size();
455463

456-
TaskListener<BasicTask> taskListener = new TaskListener<BasicTask>() {
464+
TaskListener<BasicTask> taskListener = new TaskListener<>() {
457465
@Override
458466
public void onStateChange(BasicTask task) {
459467
switch (task.getTaskState()) {
@@ -475,6 +483,7 @@ public void onStateChange(BasicTask task) {
475483
if (logger.isInfoEnabled()) {
476484
logger.info("Indexing task of {} graphs terminated.", graphCount);
477485
}
486+
break;
478487
}
479488
default:
480489
break;
@@ -496,14 +505,15 @@ protected void doIndex(HttpAction action) throws Exception {
496505
ServletOps.error(HttpSC.SERVICE_UNAVAILABLE_503, msg);
497506
} else {
498507
boolean isReplaceMode = isReplaceMode(action);
508+
boolean isUpdateMode = !isReplaceMode;
509+
499510
int threadCount = getThreadCount(action);
500511

501512
// Only SpatialIndexPerGraph can be updated.
502513
// Check if the index can be updated.
503514
// If not then raise an exception
504515
// that informs that only replace mode can be used in this situation.
505516
if (!(index instanceof SpatialIndexPerGraph)) {
506-
boolean isUpdateMode = !isReplaceMode;
507517
if (isUpdateMode) {
508518
throw new RuntimeException("Cannot update existing spatial index because its type is unsupported. Consider replacing the index.");
509519
}
@@ -516,7 +526,7 @@ protected void doIndex(HttpAction action) throws Exception {
516526

517527
String srsURI = index.getSrsInfo().getSrsURI();
518528

519-
List<Node> graphNodes = new ArrayList<>(Txn.calculateRead(dsg, () -> extractGraphsFromRequest(dsg, action)));
529+
List<Node> graphNodes = new ArrayList<>(Txn.calculateRead(dsg, () -> extractGraphsFromRequest(dsg, action, isUpdateMode)));
520530
SpatialIndexerComputation task = new SpatialIndexerComputation(dsg, srsURI, graphNodes, threadCount);
521531

522532
action.log.info(String.format("[%d] spatial index: computation request accepted.", action.id));

jena-fuseki2/jena-fuseki-mod-geosparql/src/main/resources/spatial-indexer/index.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,9 @@ <h1>Rebuild Spatial Index for Selected Graphs</h1>
218218
}
219219

220220
function updateApplyButtonLabel() {
221+
const replaceMode = replaceCb.checked;
221222
const selectedGraphs = document.querySelectorAll('#graph-list input:checked');
222-
applyBtn.textContent = selectedGraphs.length === 0 ? 'Index all graphs' : `Index ${selectedGraphs.length} graphs`;
223+
applyBtn.textContent = (!replaceMode && selectedGraphs.length === 0) ? 'Index all graphs' : `Index ${selectedGraphs.length} graphs`;
223224
}
224225

225226
document.getElementById('filter').addEventListener('input', function () {
@@ -303,6 +304,10 @@ <h1>Rebuild Spatial Index for Selected Graphs</h1>
303304
updateApplyButtonLabel();
304305
});
305306

307+
replaceCb.addEventListener("change", function () {
308+
updateApplyButtonLabel();
309+
});
310+
306311
function updateStatus() {
307312
updateCancelButton();
308313
updateStatusMessage();

jena-geosparql/src/main/java/org/apache/jena/geosparql/geo/topological/GenericGeometryPropertyFunction.java

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
import org.apache.jena.datatypes.DatatypeFormatException;
2121
import org.apache.jena.geosparql.implementation.GeometryWrapper;
22-
import org.apache.jena.geosparql.implementation.vocabulary.Geo;
2322
import org.apache.jena.graph.Graph;
2423
import org.apache.jena.graph.Node;
2524
import org.apache.jena.graph.Triple;
@@ -28,15 +27,15 @@
2827
import org.apache.jena.sparql.engine.QueryIterator;
2928
import org.apache.jena.sparql.engine.binding.Binding;
3029
import org.apache.jena.sparql.engine.binding.BindingFactory;
31-
import org.apache.jena.sparql.engine.iterator.QueryIterConcat;
30+
import org.apache.jena.sparql.engine.iterator.QueryIter;
3231
import org.apache.jena.sparql.engine.iterator.QueryIterNullIterator;
32+
import org.apache.jena.sparql.engine.iterator.QueryIterPlainWrapper;
3333
import org.apache.jena.sparql.engine.iterator.QueryIterSingleton;
3434
import org.apache.jena.sparql.expr.ExprEvalException;
3535
import org.apache.jena.sparql.expr.NodeValue;
3636
import org.apache.jena.sparql.pfunction.PFuncSimple;
3737
import org.apache.jena.system.G;
3838
import org.apache.jena.util.iterator.ExtendedIterator;
39-
import org.apache.jena.vocabulary.RDF;
4039

4140
/**
4241
*
@@ -73,15 +72,7 @@ protected Node getGeometryLiteral(Node subject, Node predicate, Graph graph) thr
7372
return G.getSP(graph, subject, predicate);
7473

7574
//Check that the Geometry has a serialisation to use.
76-
Node geomLiteral = G.getSP(graph, subject, Geo.HAS_SERIALIZATION_NODE);
77-
78-
// If hasSerialization not found then check asWKT and asGML.
79-
if (geomLiteral == null) {
80-
geomLiteral = G.getSP(graph, subject, Geo.AS_WKT_NODE);
81-
if (geomLiteral == null)
82-
geomLiteral = G.getSP(graph, subject, Geo.AS_GML_NODE);
83-
}
84-
75+
Node geomLiteral = SpatialObjectAccess.readGeoSparqlGeometryNode(graph, subject);
8576
if (geomLiteral != null) {
8677
GeometryWrapper geometryWrapper = GeometryWrapper.extract(geomLiteral);
8778
NodeValue predicateResult = applyPredicate(geometryWrapper);
@@ -111,25 +102,23 @@ private QueryIterator bothBound(Binding binding, Node subject, Node predicate, N
111102
}
112103

113104
private QueryIterator subjectUnbound(Binding binding, Node subject, Node predicate, Node object, ExecutionContext execCxt) {
114-
QueryIterConcat queryIterConcat = new QueryIterConcat(execCxt);
115-
116105
Graph graph = execCxt.getActiveGraph();
117106

118-
ExtendedIterator<Triple> subjectTriples = graph.find(null, RDF.type.asNode(), Geo.GEOMETRY_NODE);
119-
107+
ExtendedIterator<Triple> subjectTriples = SpatialObjectAccess.findGeometrySerializationTriples(graph, null);
120108
Var subjectVar = Var.alloc(subject.getName());
121-
while (subjectTriples.hasNext()) {
122-
Triple subjectTriple = subjectTriples.next();
123-
Binding newBind = BindingFactory.binding(binding, subjectVar, subjectTriple.getSubject());
124-
QueryIterator queryIter = bothBound(newBind, subjectTriple.getSubject(), predicate, object, execCxt);
125-
queryIterConcat.add(queryIter);
126-
}
109+
ExtendedIterator<Binding> iterator = subjectTriples
110+
.mapWith(Triple::getSubject)
111+
.mapWith(node -> BindingFactory.binding(binding, subjectVar, node));
127112

128-
return queryIterConcat;
113+
QueryIter queryIter = QueryIter.flatMap(
114+
QueryIterPlainWrapper.create(iterator, execCxt),
115+
b -> bothBound(b, b.get(subjectVar), predicate, object, execCxt),
116+
execCxt);
117+
118+
return queryIter;
129119
}
130120

131121
private QueryIterator objectUnbound(Binding binding, Node subject, Node predicate, Node object, ExecutionContext execCxt) {
132-
133122
Graph graph = execCxt.getActiveGraph();
134123
Node geometryLiteral = getGeometryLiteral(subject, predicate, graph);
135124

@@ -141,21 +130,20 @@ private QueryIterator objectUnbound(Binding binding, Node subject, Node predicat
141130
}
142131

143132
private QueryIterator bothUnbound(Binding binding, Node subject, Node predicate, Node object, ExecutionContext execCxt) {
144-
QueryIterConcat queryIterConcat = new QueryIterConcat(execCxt);
145-
146133
Graph graph = execCxt.getActiveGraph();
147134

148-
ExtendedIterator<Triple> subjectTriples = graph.find(null, RDF.type.asNode(), Geo.GEOMETRY_NODE);
149-
135+
ExtendedIterator<Triple> subjectTriples = SpatialObjectAccess.findGeometrySerializationTriples(graph, null);
150136
Var subjectVar = Var.alloc(subject.getName());
151-
while (subjectTriples.hasNext()) {
152-
Triple subjectTriple = subjectTriples.next();
153-
Binding newBind = BindingFactory.binding(binding, subjectVar, subjectTriple.getSubject());
154-
QueryIterator queryIter = objectUnbound(newBind, subjectTriple.getSubject(), predicate, object, execCxt);
155-
queryIterConcat.add(queryIter);
156-
}
137+
ExtendedIterator<Binding> iterator = subjectTriples
138+
.mapWith(Triple::getSubject)
139+
.mapWith(node -> BindingFactory.binding(binding, subjectVar, node));
140+
141+
QueryIter queryIter = QueryIter.flatMap(
142+
QueryIterPlainWrapper.create(iterator, execCxt),
143+
b -> objectUnbound(b, b.get(subjectVar), predicate, object, execCxt),
144+
execCxt);
157145

158-
return queryIterConcat;
146+
return queryIter;
159147
}
160148

161149
}

jena-geosparql/src/main/java/org/apache/jena/geosparql/geo/topological/GenericPropertyFunction.java

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
import org.apache.jena.geosparql.geof.topological.GenericFilterFunction;
2525
import org.apache.jena.geosparql.implementation.GeometryWrapper;
2626
import org.apache.jena.geosparql.implementation.index.QueryRewriteIndex;
27-
import org.apache.jena.geosparql.implementation.vocabulary.Geo;
28-
import org.apache.jena.geosparql.implementation.vocabulary.SpatialExtension;
2927
import org.apache.jena.geosparql.spatial.SpatialIndex;
3028
import org.apache.jena.geosparql.spatial.SpatialIndexException;
3129
import org.apache.jena.geosparql.spatial.index.v2.SpatialIndexLib;
@@ -47,7 +45,6 @@
4745
import org.apache.jena.sparql.util.FmtUtils;
4846
import org.apache.jena.system.G;
4947
import org.apache.jena.util.iterator.ExtendedIterator;
50-
import org.apache.jena.vocabulary.RDF;
5148
import org.locationtech.jts.geom.Envelope;
5249
import org.opengis.geometry.MismatchedDimensionException;
5350
import org.opengis.referencing.operation.TransformException;
@@ -144,14 +141,11 @@ private QueryIterator oneBound(Binding binding, Node subject, Node predicate, No
144141
isSubjectBound = false;
145142
}
146143

147-
if (!(boundNode.isLiteral() ||
148-
graph.contains(boundNode, RDF.type.asNode(), Geo.SPATIAL_OBJECT_NODE) ||
149-
graph.contains(boundNode, RDF.type.asNode(), Geo.FEATURE_NODE) ||
150-
graph.contains(boundNode, RDF.type.asNode(), Geo.GEOMETRY_NODE))) {
151-
if (!graph.contains(boundNode, SpatialExtension.GEO_LAT_NODE, null)) {
152-
//Bound node is not a Feature or a Geometry or has Geo predicates so exit.
153-
return QueryIterNullIterator.create(execCxt);
154-
}
144+
// XXX Should probably check specifically for geometry literal.
145+
// If only the object is bound then all subjects will be scanned.
146+
// Therefore, if the object can't match in the first place this operation will be needlessly expensive.
147+
if (!(boundNode.isLiteral() || SpatialObjectAccess.isSpatialObject(graph, boundNode))) {
148+
return QueryIterNullIterator.create(execCxt);
155149
}
156150

157151
boolean isSpatialIndex = SpatialIndexLib.isDefined(execCxt);
@@ -189,18 +183,7 @@ private QueryIterator findAll(Graph graph, Node boundNode, Node unboundNode, Bin
189183
}
190184

191185
private static ExtendedIterator<Triple> findSpatialTriples(Graph graph) {
192-
ExtendedIterator<Triple> spatialTriples;
193-
if (graph.contains(null, RDF.type.asNode(), Geo.SPATIAL_OBJECT_NODE)) {
194-
spatialTriples = graph.find(null, RDF.type.asNode(), Geo.SPATIAL_OBJECT_NODE);
195-
} else if (graph.contains(null, RDF.type.asNode(), Geo.FEATURE_NODE) || graph.contains(null, RDF.type.asNode(), Geo.GEOMETRY_NODE)) {
196-
ExtendedIterator<Triple> featureTriples = graph.find(null, RDF.type.asNode(), Geo.FEATURE_NODE);
197-
ExtendedIterator<Triple> geometryTriples = graph.find(null, RDF.type.asNode(), Geo.GEOMETRY_NODE);
198-
spatialTriples = featureTriples.andThen(geometryTriples);
199-
} else {
200-
//Check for Geo Predicate Features in the Graph if no GeometryLiterals found.
201-
spatialTriples = graph.find(null, SpatialExtension.GEO_LAT_NODE, null);
202-
}
203-
return spatialTriples;
186+
return SpatialObjectAccess.findSpatialTriplesByProperties(graph, null);
204187
}
205188

206189
private QueryIterator findIndex(Graph graph, Node boundNode, Node unboundNode, Binding binding, boolean isSubjectBound, Node predicate, ExecutionContext execCxt) throws ExprEvalException {
@@ -272,7 +255,8 @@ private QueryIterator findByFeature(Graph graph, Binding binding, Binding featur
272255
}
273256

274257
// Also test all Geometry of the Features. All, some or one Geometry may have matched.
275-
ExtendedIterator<Node> featureGeometries = G.iterSP(graph, featureNode, Geo.HAS_GEOMETRY_NODE);
258+
// ExtendedIterator<Node> featureGeometries = G.iterSP(graph, featureNode, Geo.HAS_GEOMETRY_NODE);
259+
ExtendedIterator<Node> featureGeometries = SpatialObjectAccess.findFeatureTriplesByGeometryProperties(graph, featureNode).mapWith(Triple::getObject);
276260
QueryIterator geometriesQueryIterator = QueryIterPlainWrapper.create(
277261
Iter.map(
278262
Iter.filter( // omit asserted

0 commit comments

Comments
 (0)