Skip to content

Commit

Permalink
Add Shortcut for spatial.intersects procedure (#412)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Andy2003 committed May 31, 2024
1 parent c543e01 commit 68a0859
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
*/
package org.neo4j.gis.spatial.filter;

import org.locationtech.jts.algorithm.Orientation;
import org.locationtech.jts.geom.Geometry;
import org.neo4j.gis.spatial.Layer;
import org.neo4j.gis.spatial.Utilities;
import org.neo4j.gis.spatial.index.Envelope;
import org.neo4j.gis.spatial.rtree.filter.AbstractSearchEnvelopeIntersection;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Transaction;


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

private final Layer layer;
private final Geometry windowGeom;
private final boolean isBBox;

public SearchIntersectWindow(Layer layer, Envelope envelope) {
this(layer, Utilities.fromNeo4jToJts(envelope));
Expand All @@ -44,6 +48,32 @@ public SearchIntersectWindow(Layer layer, org.locationtech.jts.geom.Envelope oth
super(layer.getGeometryEncoder(), Utilities.fromJtsToNeo4j(other));
this.layer = layer;
this.windowGeom = layer.getGeometryFactory().toGeometry(other);
this.isBBox = this.windowGeom.isRectangle()
// not a hole
&& !Orientation.isCCW(windowGeom.getCoordinates());
}

@Override
public EnvelopFilterResult needsToVisitExtended(org.neo4j.gis.spatial.rtree.Envelope indexNodeEnvelope) {
if (isBBox && referenceEnvelope.contains(indexNodeEnvelope)) {
return EnvelopFilterResult.INCLUDE_ALL;
}
if (indexNodeEnvelope.intersects(referenceEnvelope)) {
return EnvelopFilterResult.FILTER;
}
return EnvelopFilterResult.EXCLUDE_ALL;
}

@Override
public boolean geometryMatches(Transaction tx, Node geomNode) {
var geomEnvelope = decoder.decodeEnvelope(geomNode);
if (isBBox && referenceEnvelope.contains(geomEnvelope)) {
return true;
}
if (geomEnvelope.intersects(referenceEnvelope)) {
return onEnvelopeIntersection(geomNode, geomEnvelope);
}
return false;
}

@Override
Expand Down
26 changes: 21 additions & 5 deletions src/main/java/org/neo4j/gis/spatial/rtree/RTreeIndex.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.impl.StandardExpander;
import org.neo4j.graphdb.traversal.BranchState;
import org.neo4j.graphdb.traversal.Evaluation;
import org.neo4j.graphdb.traversal.Evaluator;
import org.neo4j.graphdb.traversal.Evaluators;
import org.neo4j.graphdb.traversal.PathEvaluator;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.graphdb.traversal.Traverser;
import org.neo4j.kernel.impl.traversal.MonoDirectionalTraversalDescription;
Expand Down Expand Up @@ -754,7 +756,7 @@ public Iterable<Node> getAllIndexedNodes(Transaction tx) {
return new IndexNodeToGeometryNodeIterable(getAllIndexInternalNodes(tx));
}

private class SearchEvaluator implements Evaluator {
private class SearchEvaluator extends PathEvaluator.Adapter<SearchFilter.EnvelopFilterResult> {

private final SearchFilter filter;
private final Transaction tx;
Expand All @@ -765,14 +767,22 @@ public SearchEvaluator(Transaction tx, SearchFilter filter) {
}

@Override
public Evaluation evaluate(Path path) {
public Evaluation evaluate(Path path, BranchState<SearchFilter.EnvelopFilterResult> state) {
Relationship rel = path.lastRelationship();
Node node = path.endNode();
if (rel == null) {
return Evaluation.EXCLUDE_AND_CONTINUE;
}
if (rel.isType(RTreeRelationshipTypes.RTREE_CHILD)) {
boolean shouldContinue = filter.needsToVisit(getIndexNodeEnvelope(node));
boolean shouldContinue;
if (state.getState() == SearchFilter.EnvelopFilterResult.INCLUDE_ALL) {
shouldContinue = true;
} else {
SearchFilter.EnvelopFilterResult envelopFilterResult = filter.needsToVisitExtended(
getIndexNodeEnvelope(node));
state.setState(envelopFilterResult);
shouldContinue = envelopFilterResult != SearchFilter.EnvelopFilterResult.EXCLUDE_ALL;
}
if (shouldContinue) {
monitor.matchedTreeNode(path.length(), node);
}
Expand All @@ -782,7 +792,12 @@ public Evaluation evaluate(Path path) {
Evaluation.EXCLUDE_AND_PRUNE;
}
if (rel.isType(RTreeRelationshipTypes.RTREE_REFERENCE)) {
boolean found = filter.geometryMatches(tx, node);
boolean found;
if (state.getState() == SearchFilter.EnvelopFilterResult.INCLUDE_ALL) {
found = true;
} else {
found = filter.geometryMatches(tx, node);
}
monitor.addCase(found ? "Geometry Matches" : "Geometry Does NOT Match");
if (found) {
monitor.setHeight(path.length());
Expand All @@ -801,6 +816,7 @@ public SearchResults searchIndex(Transaction tx, SearchFilter filter) {
MonoDirectionalTraversalDescription traversal = new MonoDirectionalTraversalDescription();
TraversalDescription td = traversal
.depthFirst()
.expand(StandardExpander.DEFAULT, path -> SearchFilter.EnvelopFilterResult.FILTER)
.relationships(RTreeRelationshipTypes.RTREE_CHILD, Direction.OUTGOING)
.relationships(RTreeRelationshipTypes.RTREE_REFERENCE, Direction.OUTGOING)
.evaluator(searchEvaluator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,11 @@ public boolean needsToVisit(Envelope indexNodeEnvelope) {
}

@Override
public final boolean geometryMatches(Transaction tx, Node geomNode) {
public boolean geometryMatches(Transaction tx, Node geomNode) {
Envelope geomEnvelope = decoder.decodeEnvelope(geomNode);
if (geomEnvelope.intersects(referenceEnvelope)) {
return onEnvelopeIntersection(geomNode, geomEnvelope);
}

return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,14 @@

public interface SearchFilter {

enum EnvelopFilterResult {
INCLUDE_ALL, EXCLUDE_ALL, FILTER
}
boolean needsToVisit(Envelope envelope);

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

}

0 comments on commit 68a0859

Please sign in to comment.