diff --git a/src/main/java/org/neo4j/gis/spatial/filter/SearchIntersectWindow.java b/src/main/java/org/neo4j/gis/spatial/filter/SearchIntersectWindow.java index 123bb93f..235b86e5 100644 --- a/src/main/java/org/neo4j/gis/spatial/filter/SearchIntersectWindow.java +++ b/src/main/java/org/neo4j/gis/spatial/filter/SearchIntersectWindow.java @@ -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. @@ -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)); @@ -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 diff --git a/src/main/java/org/neo4j/gis/spatial/rtree/RTreeIndex.java b/src/main/java/org/neo4j/gis/spatial/rtree/RTreeIndex.java index a3e49856..11b357d7 100644 --- a/src/main/java/org/neo4j/gis/spatial/rtree/RTreeIndex.java +++ b/src/main/java/org/neo4j/gis/spatial/rtree/RTreeIndex.java @@ -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; @@ -754,7 +756,7 @@ public Iterable getAllIndexedNodes(Transaction tx) { return new IndexNodeToGeometryNodeIterable(getAllIndexInternalNodes(tx)); } - private class SearchEvaluator implements Evaluator { + private class SearchEvaluator extends PathEvaluator.Adapter { private final SearchFilter filter; private final Transaction tx; @@ -765,14 +767,22 @@ public SearchEvaluator(Transaction tx, SearchFilter filter) { } @Override - public Evaluation evaluate(Path path) { + public Evaluation evaluate(Path path, BranchState 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); } @@ -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()); @@ -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); diff --git a/src/main/java/org/neo4j/gis/spatial/rtree/filter/AbstractSearchEnvelopeIntersection.java b/src/main/java/org/neo4j/gis/spatial/rtree/filter/AbstractSearchEnvelopeIntersection.java index e77bdb64..4cfc7e3a 100644 --- a/src/main/java/org/neo4j/gis/spatial/rtree/filter/AbstractSearchEnvelopeIntersection.java +++ b/src/main/java/org/neo4j/gis/spatial/rtree/filter/AbstractSearchEnvelopeIntersection.java @@ -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; } diff --git a/src/main/java/org/neo4j/gis/spatial/rtree/filter/SearchFilter.java b/src/main/java/org/neo4j/gis/spatial/rtree/filter/SearchFilter.java index 173e56bf..c8c5df0d 100644 --- a/src/main/java/org/neo4j/gis/spatial/rtree/filter/SearchFilter.java +++ b/src/main/java/org/neo4j/gis/spatial/rtree/filter/SearchFilter.java @@ -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); }