diff --git a/src/main/java/org/neo4j/collections/rtree/RTreeIndex.java b/src/main/java/org/neo4j/collections/rtree/RTreeIndex.java index 2c3f6df..c12fca1 100644 --- a/src/main/java/org/neo4j/collections/rtree/RTreeIndex.java +++ b/src/main/java/org/neo4j/collections/rtree/RTreeIndex.java @@ -26,7 +26,6 @@ import org.neo4j.collections.rtree.filter.SearchFilter; import org.neo4j.collections.rtree.filter.SearchResults; import org.neo4j.collections.rtree.search.Search; -import org.neo4j.collections.rtree.search.SearchAll; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; @@ -244,8 +243,8 @@ public Iterable getAllIndexedNodes() { } private class SearchEvaluator implements ReturnableEvaluator, StopEvaluator { + private SearchFilter filter; - private TraversalPosition lastPosition; private boolean isReturnableNode; private boolean isStopNode; @@ -254,21 +253,18 @@ public SearchEvaluator(SearchFilter filter) { } void checkPosition(TraversalPosition position) { - if (!position.equals(lastPosition)) { - Relationship rel = position.lastRelationshipTraversed(); - Node node = position.currentNode(); - if (rel == null) { - isStopNode = false; - isReturnableNode = false; - } else if (rel.getType().equals(RTreeRelationshipTypes.RTREE_CHILD)) { - isStopNode = filter.needsToVisit(getIndexNodeEnvelope(node)); - isReturnableNode = false; - } else { - isReturnableNode = filter.geometryMatches(node); - isStopNode = !isReturnableNode; - } + Relationship rel = position.lastRelationshipTraversed(); + Node node = position.currentNode(); + if (rel == null) { + isStopNode = false; + isReturnableNode = false; + } else if (rel.getType().equals(RTreeRelationshipTypes.RTREE_CHILD)) { + isReturnableNode = false; + isStopNode = !filter.needsToVisit(getIndexNodeEnvelope(node)); + } else if (rel.getType().equals(RTreeRelationshipTypes.RTREE_REFERENCE)) { + isReturnableNode = filter.geometryMatches(node); + isStopNode = true; } - lastPosition = position; } @Override @@ -316,6 +312,14 @@ public Node getIndexRoot() { // Private methods + private Envelope getChildNodeEnvelope(Node child, RelationshipType relType) { + if (relType == RTreeRelationshipTypes.RTREE_REFERENCE) { + return getLeafNodeEnvelope(child); + } else { + return getIndexNodeEnvelope(child); + } + } + /** * The leaf nodes belong to the domain model, and as such need to use the * layers domain-specific GeometryEncoder for decoding the envelope. @@ -332,15 +336,18 @@ private Envelope getLeafNodeEnvelope(Node geomNode) { protected Envelope getIndexNodeEnvelope(Node indexNode) { if (indexNode == null) indexNode = getIndexRoot(); if (!indexNode.hasProperty(PROP_BBOX)) { - System.err.println("node[" + indexNode + "] has no bounding box property '" + PROP_BBOX + "'"); + // this is ok after an index node split return null; } - return bboxToEnvelope((double[]) indexNode.getProperty(PROP_BBOX)); + + double[] bbox = (double[]) indexNode.getProperty(PROP_BBOX); + // Envelope parameters: xmin, xmax, ymin, ymax + return new Envelope(bbox[0], bbox[2], bbox[1], bbox[3]); } private void visitInTx(SpatialIndexVisitor visitor, Long indexNodeId) { Node indexNode = database.getNodeById(indexNodeId); - if(!visitor.needsToVisit(getIndexNodeEnvelope(indexNode))) return; + if (!visitor.needsToVisit(getIndexNodeEnvelope(indexNode))) return; if (indexNode.hasRelationship(RTreeRelationshipTypes.RTREE_CHILD, Direction.OUTGOING)) { // Node is not a leaf @@ -493,7 +500,7 @@ private Node chooseIndexNodeWithSmallestArea(List indexNodes) { double smallestArea = -1; for (Node indexNode : indexNodes) { - double area = getArea(indexNode); + double area = getArea(getIndexNodeEnvelope(indexNode)); if (result == null || area < smallestArea) { result = indexNode; smallestArea = area; @@ -559,9 +566,9 @@ private Node quadraticSplit(Node indexNode, RelationshipType relationshipType) { Node seed2 = null; double worst = Double.NEGATIVE_INFINITY; for (Node e : entries) { - Envelope eEnvelope = getLeafNodeEnvelope(e); + Envelope eEnvelope = getChildNodeEnvelope(e, relationshipType); for (Node e1 : entries) { - Envelope e1Envelope = getLeafNodeEnvelope(e1); + Envelope e1Envelope = getChildNodeEnvelope(e1, relationshipType); double deadSpace = getArea(createEnvelope(eEnvelope, e1Envelope)) - getArea(eEnvelope) - getArea(e1Envelope); if (deadSpace > worst) { worst = deadSpace; @@ -573,11 +580,11 @@ private Node quadraticSplit(Node indexNode, RelationshipType relationshipType) { List group1 = new ArrayList(); group1.add(seed1); - Envelope group1envelope = getLeafNodeEnvelope(seed1); + Envelope group1envelope = getChildNodeEnvelope(seed1, relationshipType); List group2 = new ArrayList(); group2.add(seed2); - Envelope group2envelope = getLeafNodeEnvelope(seed2); + Envelope group2envelope = getChildNodeEnvelope(seed2, relationshipType); entries.remove(seed1); entries.remove(seed2); @@ -588,7 +595,7 @@ private Node quadraticSplit(Node indexNode, RelationshipType relationshipType) { Node bestEntry = null; double expansionMin = Double.POSITIVE_INFINITY; for (Node e : entries) { - Envelope nodeEnvelope = getLeafNodeEnvelope(e); + Envelope nodeEnvelope = getChildNodeEnvelope(e, relationshipType); double expansion1 = getArea(createEnvelope(nodeEnvelope, group1envelope)) - getArea(group1envelope); double expansion2 = getArea(createEnvelope(nodeEnvelope, group2envelope)) - getArea(group2envelope); @@ -618,7 +625,7 @@ private Node quadraticSplit(Node indexNode, RelationshipType relationshipType) { // insert the best candidate entry in the best group bestGroup.add(bestEntry); - bestGroupEnvelope.expandToInclude(getLeafNodeEnvelope(bestEntry)); + bestGroupEnvelope.expandToInclude(getChildNodeEnvelope(bestEntry, relationshipType)); entries.remove(bestEntry); } @@ -648,22 +655,11 @@ private void createNewRoot(Node oldRoot, Node newIndexNode) { layerNode.createRelationshipTo(newRoot, RTreeRelationshipTypes.RTREE_ROOT); } - private double[] envelopeToBBox(Envelope bounds) { - return new double[]{ bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY() }; - } - - protected Envelope bboxToEnvelope(double[] bbox) { - // Envelope parameters: xmin, xmax, ymin, ymax - return new Envelope(bbox[0], bbox[2], bbox[1], bbox[3]); - } - private boolean addChild(Node parent, RelationshipType type, Node newChild) { - double[] childBBox = null; - if (type == RTreeRelationshipTypes.RTREE_REFERENCE) { - childBBox = envelopeToBBox(envelopeDecoder.decodeEnvelope(newChild)); - } else { - childBBox = (double[]) newChild.getProperty(PROP_BBOX); - } + Envelope childEnvelope = getChildNodeEnvelope(newChild, type); + double[] childBBox = new double[] { + childEnvelope.getMinX(), childEnvelope.getMinY(), + childEnvelope.getMaxX(), childEnvelope.getMaxY() }; parent.createRelationshipTo(newChild, type); return expandParentBoundingBoxAfterNewChild(parent, childBBox); } @@ -696,9 +692,9 @@ private boolean adjustParentBoundingBox(Node indexNode, RelationshipType relatio Node childNode = iterator.next().getEndNode(); if (bbox == null) { - bbox = new Envelope(getLeafNodeEnvelope(childNode)); + bbox = new Envelope(getChildNodeEnvelope(childNode, relationshipType)); } else { - bbox.expandToInclude(getLeafNodeEnvelope(childNode)); + bbox.expandToInclude(getChildNodeEnvelope(childNode, relationshipType)); } } @@ -770,10 +766,6 @@ private Node getIndexNodeParent(Node indexNode) { else return relationship.getStartNode(); } - private double getArea(Node node) { - return getArea(getLeafNodeEnvelope(node)); - } - private double getArea(Envelope e) { return e.getWidth() * e.getHeight(); } diff --git a/src/main/java/org/neo4j/collections/rtree/filter/AbstractSearchEnvelopeIntersection.java b/src/main/java/org/neo4j/collections/rtree/filter/AbstractSearchEnvelopeIntersection.java index aa6428d..f6f160b 100644 --- a/src/main/java/org/neo4j/collections/rtree/filter/AbstractSearchEnvelopeIntersection.java +++ b/src/main/java/org/neo4j/collections/rtree/filter/AbstractSearchEnvelopeIntersection.java @@ -24,6 +24,7 @@ import org.neo4j.graphdb.Node; public abstract class AbstractSearchEnvelopeIntersection implements SearchFilter { + protected EnvelopeDecoder decoder; protected Envelope referenceEnvelope; @@ -43,6 +44,7 @@ public final boolean geometryMatches(Node geomNode) { if (geomEnvelope.intersects(referenceEnvelope)) { return onEnvelopeIntersection(geomNode, geomEnvelope); } + return false; } diff --git a/src/main/java/org/neo4j/collections/rtree/filter/SearchFilter.java b/src/main/java/org/neo4j/collections/rtree/filter/SearchFilter.java index 8300a8e..7a5c2d9 100644 --- a/src/main/java/org/neo4j/collections/rtree/filter/SearchFilter.java +++ b/src/main/java/org/neo4j/collections/rtree/filter/SearchFilter.java @@ -23,6 +23,9 @@ import org.neo4j.graphdb.Node; public interface SearchFilter { + boolean needsToVisit(Envelope envelope); - boolean geometryMatches(Node geomNode); + + boolean geometryMatches(Node geomNode); + } \ No newline at end of file diff --git a/src/test/java/org/neo4j/collections/rtree/TestSearch.java b/src/test/java/org/neo4j/collections/rtree/TestSearch.java index 4f7c4a3..3c248d4 100644 --- a/src/test/java/org/neo4j/collections/rtree/TestSearch.java +++ b/src/test/java/org/neo4j/collections/rtree/TestSearch.java @@ -67,22 +67,14 @@ public void myFirstTest() { Envelope expectedBbox = new Envelope(0, 25, 0, 32); assertEnvelopeEquals(bbox, expectedBbox); -// Search search = new SearchInvalidEnvelopes(index.getEnvelopeDecoder()); -// index.executeSearch(search); -// assertEquals(1, search.getResults().size()); - - Search search = new SearchEqualEnvelopes(index.getEnvelopeDecoder(), - new Envelope(0, 2, 0, 3)); + Search search = new SearchEqualEnvelopes(index.getEnvelopeDecoder(), new Envelope(0, 2, 0, 3)); index.executeSearch(search); assertEquals(1, search.getResults().size()); - search = new SearchCoveredByEnvelope(index.getEnvelopeDecoder(), - new Envelope(9, 15, -1, 3)); + search = new SearchCoveredByEnvelope(index.getEnvelopeDecoder(), new Envelope(9, 15, -1, 3)); index.executeSearch(search); assertEquals(3, search.getResults().size()); - // TODO test index.removeAll(deleteGeomNodes, monitor) - index.clear(new NullListener()); assertEquals(0, index.count()); diff --git a/src/test/java/org/neo4j/collections/rtree/TestSearchFilter.java b/src/test/java/org/neo4j/collections/rtree/TestSearchFilter.java index 650a33a..8552134 100644 --- a/src/test/java/org/neo4j/collections/rtree/TestSearchFilter.java +++ b/src/test/java/org/neo4j/collections/rtree/TestSearchFilter.java @@ -21,24 +21,23 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import org.junit.Test; -import org.neo4j.collections.rtree.filter.SearchFilter; import org.neo4j.collections.rtree.filter.SearchCoveredByEnvelope; import org.neo4j.collections.rtree.filter.SearchEqualEnvelopes; +import org.neo4j.collections.rtree.filter.SearchFilter; +import org.neo4j.collections.rtree.filter.SearchResults; import org.neo4j.graphdb.Node; + public class TestSearchFilter extends SpatialTestCase { @Test public void searchIndexWithFilter() { - RTreeIndex index = new RTreeIndex(graphDb(), graphDb().getReferenceNode(), + RTreeIndex index = new RTreeIndex(graphDb(), graphDb().getReferenceNode(), new EnvelopeDecoderFromDoubleArray("bbox")); - assertTrue(index.isEmpty()); - assertEquals(0, index.count()); - + // equal bbox test index.add(createGeomNode(0, 0, 2, 3)); index.add(createGeomNode(10, 0)); @@ -46,36 +45,30 @@ public void searchIndexWithFilter() { index.add(createGeomNode(14, 2)); index.add(createGeomNode(25, 32)); - Node geomNode = createGeomNode(11, 1); - index.add(geomNode); - assertFalse(index.isEmpty()); - assertEquals(6, index.count()); - - assertTrue(index.isNodeIndexed(geomNode.getId())); - index.remove(geomNode.getId(), false); - assertFalse(index.isNodeIndexed(geomNode.getId())); - assertEquals(5, index.count()); - Envelope bbox = index.getBoundingBox(); - Envelope expectedBbox = new Envelope(0, 25, 0, 32); - assertEnvelopeEquals(bbox, expectedBbox); - - SearchFilter filter = new SearchEqualEnvelopes(index.getEnvelopeDecoder(), - new Envelope(0, 2, 0, 3)); - assertEquals(1, index.searchIndex(filter).count()); + SearchFilter filter = new SearchEqualEnvelopes(index.getEnvelopeDecoder(), new Envelope(0, 2, 0, 3)); + SearchResults results = index.searchIndex(filter); - filter = new SearchCoveredByEnvelope(index.getEnvelopeDecoder(), - new Envelope(9, 15, -1, 3)); - assertEquals(3, index.searchIndex(filter).count()); + int count = 0; + for (Node node : results) { + System.out.println("found node: " + node.getId()); + count++; + } - // TODO test index.removeAll(deleteGeomNodes, monitor) - - index.clear(new NullListener()); - assertEquals(0, index.count()); + assertEquals(1, count); + + filter = new SearchCoveredByEnvelope(index.getEnvelopeDecoder(), new Envelope(9, 15, -1, 3)); + results = index.searchIndex(filter); + + count = 0; + for (Node node : results) { + System.out.println("found node: " + node.getId()); + count++; + } - debugIndexTree(index, graphDb().getReferenceNode()); + assertEquals(3, count); } } \ No newline at end of file