Skip to content

Commit 68a0859

Browse files
authored
Add Shortcut for spatial.intersects procedure (neo4j-contrib#412)
Implemented a shortcut for the `spatial.intersects` procedure to improve performance. The update bypasses the full geometry intersection check when the requested polygon is a rectangle (without hole) and the target geometry is entirely contained within the search window. This optimization reduces unnecessary computations, enhancing the efficiency of spatial intersection operations.
1 parent c543e01 commit 68a0859

File tree

4 files changed

+58
-7
lines changed

4 files changed

+58
-7
lines changed

src/main/java/org/neo4j/gis/spatial/filter/SearchIntersectWindow.java

+30
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@
1919
*/
2020
package org.neo4j.gis.spatial.filter;
2121

22+
import org.locationtech.jts.algorithm.Orientation;
2223
import org.locationtech.jts.geom.Geometry;
2324
import org.neo4j.gis.spatial.Layer;
2425
import org.neo4j.gis.spatial.Utilities;
2526
import org.neo4j.gis.spatial.index.Envelope;
2627
import org.neo4j.gis.spatial.rtree.filter.AbstractSearchEnvelopeIntersection;
2728
import org.neo4j.graphdb.Node;
29+
import org.neo4j.graphdb.Transaction;
30+
2831

2932
/**
3033
* Find geometries that intersect with the specified search window.
@@ -35,6 +38,7 @@ public class SearchIntersectWindow extends AbstractSearchEnvelopeIntersection {
3538

3639
private final Layer layer;
3740
private final Geometry windowGeom;
41+
private final boolean isBBox;
3842

3943
public SearchIntersectWindow(Layer layer, Envelope envelope) {
4044
this(layer, Utilities.fromNeo4jToJts(envelope));
@@ -44,6 +48,32 @@ public SearchIntersectWindow(Layer layer, org.locationtech.jts.geom.Envelope oth
4448
super(layer.getGeometryEncoder(), Utilities.fromJtsToNeo4j(other));
4549
this.layer = layer;
4650
this.windowGeom = layer.getGeometryFactory().toGeometry(other);
51+
this.isBBox = this.windowGeom.isRectangle()
52+
// not a hole
53+
&& !Orientation.isCCW(windowGeom.getCoordinates());
54+
}
55+
56+
@Override
57+
public EnvelopFilterResult needsToVisitExtended(org.neo4j.gis.spatial.rtree.Envelope indexNodeEnvelope) {
58+
if (isBBox && referenceEnvelope.contains(indexNodeEnvelope)) {
59+
return EnvelopFilterResult.INCLUDE_ALL;
60+
}
61+
if (indexNodeEnvelope.intersects(referenceEnvelope)) {
62+
return EnvelopFilterResult.FILTER;
63+
}
64+
return EnvelopFilterResult.EXCLUDE_ALL;
65+
}
66+
67+
@Override
68+
public boolean geometryMatches(Transaction tx, Node geomNode) {
69+
var geomEnvelope = decoder.decodeEnvelope(geomNode);
70+
if (isBBox && referenceEnvelope.contains(geomEnvelope)) {
71+
return true;
72+
}
73+
if (geomEnvelope.intersects(referenceEnvelope)) {
74+
return onEnvelopeIntersection(geomNode, geomEnvelope);
75+
}
76+
return false;
4777
}
4878

4979
@Override

src/main/java/org/neo4j/gis/spatial/rtree/RTreeIndex.java

+21-5
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@
4242
import org.neo4j.graphdb.Relationship;
4343
import org.neo4j.graphdb.RelationshipType;
4444
import org.neo4j.graphdb.Transaction;
45+
import org.neo4j.graphdb.impl.StandardExpander;
46+
import org.neo4j.graphdb.traversal.BranchState;
4547
import org.neo4j.graphdb.traversal.Evaluation;
46-
import org.neo4j.graphdb.traversal.Evaluator;
4748
import org.neo4j.graphdb.traversal.Evaluators;
49+
import org.neo4j.graphdb.traversal.PathEvaluator;
4850
import org.neo4j.graphdb.traversal.TraversalDescription;
4951
import org.neo4j.graphdb.traversal.Traverser;
5052
import org.neo4j.kernel.impl.traversal.MonoDirectionalTraversalDescription;
@@ -754,7 +756,7 @@ public Iterable<Node> getAllIndexedNodes(Transaction tx) {
754756
return new IndexNodeToGeometryNodeIterable(getAllIndexInternalNodes(tx));
755757
}
756758

757-
private class SearchEvaluator implements Evaluator {
759+
private class SearchEvaluator extends PathEvaluator.Adapter<SearchFilter.EnvelopFilterResult> {
758760

759761
private final SearchFilter filter;
760762
private final Transaction tx;
@@ -765,14 +767,22 @@ public SearchEvaluator(Transaction tx, SearchFilter filter) {
765767
}
766768

767769
@Override
768-
public Evaluation evaluate(Path path) {
770+
public Evaluation evaluate(Path path, BranchState<SearchFilter.EnvelopFilterResult> state) {
769771
Relationship rel = path.lastRelationship();
770772
Node node = path.endNode();
771773
if (rel == null) {
772774
return Evaluation.EXCLUDE_AND_CONTINUE;
773775
}
774776
if (rel.isType(RTreeRelationshipTypes.RTREE_CHILD)) {
775-
boolean shouldContinue = filter.needsToVisit(getIndexNodeEnvelope(node));
777+
boolean shouldContinue;
778+
if (state.getState() == SearchFilter.EnvelopFilterResult.INCLUDE_ALL) {
779+
shouldContinue = true;
780+
} else {
781+
SearchFilter.EnvelopFilterResult envelopFilterResult = filter.needsToVisitExtended(
782+
getIndexNodeEnvelope(node));
783+
state.setState(envelopFilterResult);
784+
shouldContinue = envelopFilterResult != SearchFilter.EnvelopFilterResult.EXCLUDE_ALL;
785+
}
776786
if (shouldContinue) {
777787
monitor.matchedTreeNode(path.length(), node);
778788
}
@@ -782,7 +792,12 @@ public Evaluation evaluate(Path path) {
782792
Evaluation.EXCLUDE_AND_PRUNE;
783793
}
784794
if (rel.isType(RTreeRelationshipTypes.RTREE_REFERENCE)) {
785-
boolean found = filter.geometryMatches(tx, node);
795+
boolean found;
796+
if (state.getState() == SearchFilter.EnvelopFilterResult.INCLUDE_ALL) {
797+
found = true;
798+
} else {
799+
found = filter.geometryMatches(tx, node);
800+
}
786801
monitor.addCase(found ? "Geometry Matches" : "Geometry Does NOT Match");
787802
if (found) {
788803
monitor.setHeight(path.length());
@@ -801,6 +816,7 @@ public SearchResults searchIndex(Transaction tx, SearchFilter filter) {
801816
MonoDirectionalTraversalDescription traversal = new MonoDirectionalTraversalDescription();
802817
TraversalDescription td = traversal
803818
.depthFirst()
819+
.expand(StandardExpander.DEFAULT, path -> SearchFilter.EnvelopFilterResult.FILTER)
804820
.relationships(RTreeRelationshipTypes.RTREE_CHILD, Direction.OUTGOING)
805821
.relationships(RTreeRelationshipTypes.RTREE_REFERENCE, Direction.OUTGOING)
806822
.evaluator(searchEvaluator);

src/main/java/org/neo4j/gis/spatial/rtree/filter/AbstractSearchEnvelopeIntersection.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,11 @@ public boolean needsToVisit(Envelope indexNodeEnvelope) {
4444
}
4545

4646
@Override
47-
public final boolean geometryMatches(Transaction tx, Node geomNode) {
47+
public boolean geometryMatches(Transaction tx, Node geomNode) {
4848
Envelope geomEnvelope = decoder.decodeEnvelope(geomNode);
4949
if (geomEnvelope.intersects(referenceEnvelope)) {
5050
return onEnvelopeIntersection(geomNode, geomEnvelope);
5151
}
52-
5352
return false;
5453
}
5554

src/main/java/org/neo4j/gis/spatial/rtree/filter/SearchFilter.java

+6
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,14 @@
2525

2626
public interface SearchFilter {
2727

28+
enum EnvelopFilterResult {
29+
INCLUDE_ALL, EXCLUDE_ALL, FILTER
30+
}
2831
boolean needsToVisit(Envelope envelope);
2932

33+
default EnvelopFilterResult needsToVisitExtended(Envelope envelope) {
34+
return needsToVisit(envelope) ? EnvelopFilterResult.FILTER : EnvelopFilterResult.EXCLUDE_ALL;
35+
}
3036
boolean geometryMatches(Transaction tx, Node geomNode);
3137

3238
}

0 commit comments

Comments
 (0)