diff --git a/src/com/newbrightidea/util/RTree.java b/src/com/newbrightidea/util/RTree.java index cb91bb0..8c26d27 100644 --- a/src/com/newbrightidea/util/RTree.java +++ b/src/com/newbrightidea/util/RTree.java @@ -8,6 +8,11 @@ import java.util.Map; import java.util.Queue; import java.util.Set; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; /** * Implementation of an arbitrary-dimension RTree. Based on R-Trees: A Dynamic @@ -20,11 +25,18 @@ * * @param the type of entry to store in this RTree. */ -public class RTree { - public enum SeedPicker {LINEAR, QUADRATIC} +public class RTree +{ + public enum SeedPicker + { + LINEAR, + QUADRATIC + } private final int maxEntries; + private final int minEntries; + private final int numDims; private final float[] pointDims; @@ -40,9 +52,10 @@ public enum SeedPicker {LINEAR, QUADRATIC} * * @param maxEntries maximum number of entries per node * @param minEntries minimum number of entries per node (except for the root node) - * @param numDims the number of dimensions of the RTree. + * @param numDims the number of dimensions of the RTree. */ - public RTree(int maxEntries, int minEntries, int numDims, SeedPicker seedPicker) { + public RTree(int maxEntries, int minEntries, int numDims, SeedPicker seedPicker) + { assert (minEntries <= (maxEntries / 2)); this.numDims = numDims; this.maxEntries = maxEntries; @@ -52,14 +65,17 @@ public RTree(int maxEntries, int minEntries, int numDims, SeedPicker seedPicker) root = buildRoot(true); } - public RTree(int maxEntries, int minEntries, int numDims) { + public RTree(int maxEntries, int minEntries, int numDims) + { this(maxEntries, minEntries, numDims, SeedPicker.LINEAR); } - private Node buildRoot(boolean asLeaf) { + private Node buildRoot(boolean asLeaf) + { float[] initCoords = new float[numDims]; float[] initDimensions = new float[numDims]; - for (int i = 0; i < this.numDims; i++) { + for (int i = 0; i < this.numDims; i++) + { initCoords[i] = (float) Math.sqrt(Float.MAX_VALUE); initDimensions[i] = -2.0f * (float) Math.sqrt(Float.MAX_VALUE); } @@ -70,87 +86,96 @@ private Node buildRoot(boolean asLeaf) { * Builds a new RTree using default parameters: maximum 50 entries per node * minimum 2 entries per node 2 dimensions */ - public RTree() { + public RTree() + { this(50, 2, 2, SeedPicker.LINEAR); } /** * @return the maximum number of entries per node */ - public int getMaxEntries() { + public int getMaxEntries() + { return maxEntries; } /** * @return the minimum number of entries per node for all nodes except the - * root. + * root. */ - public int getMinEntries() { + public int getMinEntries() + { return minEntries; } /** * @return the number of dimensions of the tree */ - public int getNumDims() { + public int getNumDims() + { return numDims; } /** * @return the number of items in this tree. */ - public int size() { + public int size() + { return size; } + /** + * Searches the RTree for objects overlapping with the given rectangle and + * returns a Java 8 Stream<T> instance for processing + * results. + * + * @param coords the corner of the rectangle that is the lower bound of every + * dimension (eg. the top-left corner) + * @param dimensions the dimensions of the rectangle. + * @return a Java 8 Stream<T> of objects whose rectangles + * overlap with the given rectangle. + */ + public Stream stream(float[] coords, float[] dimensions) + { + assert (coords.length == numDims); + assert (dimensions.length == numDims); + + return StreamSupport.stream(new TreeSearchSpliterator(coords, dimensions), false); + } + /** * Searches the RTree for objects overlapping with the given rectangle. * - * @param coords the corner of the rectangle that is the lower bound of every - * dimension (eg. the top-left corner) + * @param coords the corner of the rectangle that is the lower bound of every + * dimension (eg. the top-left corner) * @param dimensions the dimensions of the rectangle. * @return a list of objects whose rectangles overlap with the given - * rectangle. + * rectangle. */ - public List search(float[] coords, float[] dimensions) { + public List search(float[] coords, float[] dimensions) + { assert (coords.length == numDims); assert (dimensions.length == numDims); - LinkedList results = new LinkedList(); - search(coords, dimensions, root, results); - return results; - } - private void search(float[] coords, float[] dimensions, Node n, - LinkedList results) { - if (n.leaf) { - for (Node e : n.children) { - if (isOverlap(coords, dimensions, e.coords, e.dimensions)) { - results.add(((Entry) e).entry); - } - } - } else { - for (Node c : n.children) { - if (isOverlap(coords, dimensions, c.coords, c.dimensions)) { - search(coords, dimensions, c, results); - } - } - } + return stream(coords, dimensions).collect(Collectors.toList()); } /** * Deletes the entry associated with the given rectangle from the RTree * - * @param coords the corner of the rectangle that is the lower bound in every - * dimension + * @param coords the corner of the rectangle that is the lower bound in every + * dimension * @param dimensions the dimensions of the rectangle - * @param entry the entry to delete + * @param entry the entry to delete * @return true iff the entry was deleted from the RTree. */ - public boolean delete(float[] coords, float[] dimensions, T entry) { + public boolean delete(float[] coords, float[] dimensions, T entry) + { assert (coords.length == numDims); assert (dimensions.length == numDims); Node l = findLeaf(root, coords, dimensions, entry); - if (l == null) { + if (l == null) + { System.out.println("WTF?"); findLeaf(root, coords, dimensions, entry); } @@ -158,42 +183,56 @@ public boolean delete(float[] coords, float[] dimensions, T entry) { assert (l.leaf) : "Entry is not found at leaf?!?"; ListIterator li = l.children.listIterator(); T removed = null; - while (li.hasNext()) { + while (li.hasNext()) + { @SuppressWarnings("unchecked") Entry e = (Entry) li.next(); - if (e.entry.equals(entry)) { + if (e.entry.equals(entry)) + { removed = e.entry; li.remove(); break; } } - if (removed != null) { + if (removed != null) + { condenseTree(l); size--; } - if (size == 0) { + if (size == 0) + { root = buildRoot(true); } return (removed != null); } - public boolean delete(float[] coords, T entry) { + public boolean delete(float[] coords, T entry) + { return delete(coords, pointDims, entry); } - private Node findLeaf(Node n, float[] coords, float[] dimensions, T entry) { - if (n.leaf) { - for (Node c : n.children) { - if (((Entry) c).entry.equals(entry)) { + private Node findLeaf(Node n, float[] coords, float[] dimensions, T entry) + { + if (n.leaf) + { + for (Node c : n.children) + { + if (((Entry) c).entry.equals(entry)) + { return n; } } return null; - } else { - for (Node c : n.children) { - if (isOverlap(c.coords, c.dimensions, coords, dimensions)) { + } + else + { + for (Node c : n.children) + { + if (isOverlap(c.coords, c.dimensions, coords, dimensions)) + { Node result = findLeaf(c, coords, dimensions, entry); - if (result != null) { + if (result != null) + { return result; } } @@ -202,38 +241,55 @@ private Node findLeaf(Node n, float[] coords, float[] dimensions, T entry) { } } - private void condenseTree(Node n) { + private void condenseTree(Node n) + { Set q = new HashSet(); - while (n != root) { - if (n.leaf && (n.children.size() < minEntries)) { + while (n != root) + { + if (n.leaf && (n.children.size() < minEntries)) + { q.addAll(n.children); n.parent.children.remove(n); - } else if (!n.leaf && (n.children.size() < minEntries)) { + } + else if (!n.leaf && (n.children.size() < minEntries)) + { // probably a more efficient way to do this... LinkedList toVisit = new LinkedList(n.children); - while (!toVisit.isEmpty()) { + while (!toVisit.isEmpty()) + { Node c = toVisit.pop(); - if (c.leaf) { + if (c.leaf) + { q.addAll(c.children); - } else { + } + else + { toVisit.addAll(c.children); } } n.parent.children.remove(n); - } else { + } + else + { tighten(n); } n = n.parent; } - if (root.children.size() == 0) { + if (root.children.size() == 0) + { root = buildRoot(true); - } else if ((root.children.size() == 1) && (!root.leaf)) { + } + else if ((root.children.size() == 1) && (!root.leaf)) + { root = root.children.get(0); root.parent = null; - } else { + } + else + { tighten(root); } - for (Node ne : q) { + for (Node ne : q) + { @SuppressWarnings("unchecked") Entry e = (Entry) ne; insert(e.coords, e.dimensions, e.entry); @@ -244,7 +300,8 @@ private void condenseTree(Node n) { /** * Empties the RTree */ - public void clear() { + public void clear() + { root = buildRoot(true); // let the GC take care of the rest. } @@ -253,12 +310,13 @@ public void clear() { * Inserts the given entry into the RTree, associated with the given * rectangle. * - * @param coords the corner of the rectangle that is the lower bound in every - * dimension + * @param coords the corner of the rectangle that is the lower bound in every + * dimension * @param dimensions the dimensions of the rectangle - * @param entry the entry to insert + * @param entry the entry to insert */ - public void insert(float[] coords, float[] dimensions, T entry) { + public void insert(float[] coords, float[] dimensions, T entry) + { assert (coords.length == numDims); assert (dimensions.length == numDims); Entry e = new Entry(coords, dimensions, entry); @@ -266,10 +324,13 @@ public void insert(float[] coords, float[] dimensions, T entry) { l.children.add(e); size++; e.parent = l; - if (l.children.size() > maxEntries) { + if (l.children.size() > maxEntries) + { Node[] splits = splitNode(l); adjustTree(splits[0], splits[1]); - } else { + } + else + { adjustTree(l, null); } } @@ -280,40 +341,51 @@ public void insert(float[] coords, float[] dimensions, T entry) { * @param coords * @param entry */ - public void insert(float[] coords, T entry) { + public void insert(float[] coords, T entry) + { insert(coords, pointDims, entry); } - public void visit(NodeVisitor nv) { + public void visit(NodeVisitor nv) + { float[] coordBuf = new float[numDims]; float[] dimBuf = new float[numDims]; Queue toVisit = new LinkedList<>(); - Map nodeDepths = new HashMap<>(); + Map nodeDepths = new HashMap<>(); nodeDepths.put(root, 0); toVisit.add(root); - while (!toVisit.isEmpty()) { + while (!toVisit.isEmpty()) + { Node currentNode = toVisit.remove(); - if (currentNode.parent != null) { + if (currentNode.parent != null) + { nodeDepths.put(currentNode, nodeDepths.get(currentNode.parent) + 1); } System.arraycopy(currentNode.coords, 0, coordBuf, 0, numDims); System.arraycopy(currentNode.dimensions, 0, dimBuf, 0, numDims); - if (currentNode instanceof RTree.Entry) { - nv.visit(nodeDepths.get(currentNode), coordBuf, dimBuf, ((Entry)currentNode).entry); - } else { + if (currentNode instanceof RTree.Entry) + { + nv.visit(nodeDepths.get(currentNode), coordBuf, dimBuf, ((Entry) currentNode).entry); + } + else + { nv.visit(nodeDepths.get(currentNode), coordBuf, dimBuf, null); } toVisit.addAll(currentNode.children); } } - public interface NodeVisitor { + public interface NodeVisitor + { public void visit(int depth, float[] coords, float[] dimensions, V value); } - private void adjustTree(Node n, Node nn) { - if (n == root) { - if (nn != null) { + private void adjustTree(Node n, Node nn) + { + if (n == root) + { + if (nn != null) + { // build new root and add children. root = buildRoot(false); root.children.add(n); @@ -325,28 +397,32 @@ private void adjustTree(Node n, Node nn) { return; } tighten(n); - if (nn != null) { + if (nn != null) + { tighten(nn); - if (n.parent.children.size() > maxEntries) { + if (n.parent.children.size() > maxEntries) + { Node[] splits = splitNode(n.parent); adjustTree(splits[0], splits[1]); } } - if (n.parent != null) { + if (n.parent != null) + { adjustTree(n.parent, null); } } - private Node[] splitNode(Node n) { + private Node[] splitNode(Node n) + { // TODO: this class probably calls "tighten" a little too often. // For instance the call at the end of the "while (!cc.isEmpty())" loop // could be modified and inlined because it's only adjusting for the addition - // of a single node. Left as-is for now for readability. + // of a single node. Left as-is for now for readability. @SuppressWarnings("unchecked") - Node[] nn = new RTree.Node[] - {n, new Node(n.coords, n.dimensions, n.leaf)}; + Node[] nn = new RTree.Node[] { n, new Node(n.coords, n.dimensions, n.leaf) }; nn[1].parent = n.parent; - if (nn[1].parent != null) { + if (nn[1].parent != null) + { nn[1].parent.children.add(nn[1]); } LinkedList cc = new LinkedList(n.children); @@ -355,15 +431,19 @@ private Node[] splitNode(Node n) { nn[0].children.add(ss[0]); nn[1].children.add(ss[1]); tighten(nn); - while (!cc.isEmpty()) { + while (!cc.isEmpty()) + { if ((nn[0].children.size() >= minEntries) - && (nn[1].children.size() + cc.size() == minEntries)) { + && (nn[1].children.size() + cc.size() == minEntries)) + { nn[1].children.addAll(cc); cc.clear(); tighten(nn); // Not sure this is required. return nn; - } else if ((nn[1].children.size() >= minEntries) - && (nn[0].children.size() + cc.size() == minEntries)) { + } + else if ((nn[1].children.size() >= minEntries) + && (nn[0].children.size() + cc.size() == minEntries)) + { nn[0].children.addAll(cc); cc.clear(); tighten(nn); // Not sure this is required. @@ -373,23 +453,38 @@ private Node[] splitNode(Node n) { Node preferred; float e0 = getRequiredExpansion(nn[0].coords, nn[0].dimensions, c); float e1 = getRequiredExpansion(nn[1].coords, nn[1].dimensions, c); - if (e0 < e1) { + if (e0 < e1) + { preferred = nn[0]; - } else if (e0 > e1) { + } + else if (e0 > e1) + { preferred = nn[1]; - } else { + } + else + { float a0 = getArea(nn[0].dimensions); float a1 = getArea(nn[1].dimensions); - if (a0 < a1) { + if (a0 < a1) + { preferred = nn[0]; - } else if (e0 > a1) { + } + else if (e0 > a1) + { preferred = nn[1]; - } else { - if (nn[0].children.size() < nn[1].children.size()) { + } + else + { + if (nn[0].children.size() < nn[1].children.size()) + { preferred = nn[0]; - } else if (nn[0].children.size() > nn[1].children.size()) { + } + else if (nn[0].children.size() > nn[1].children.size()) + { preferred = nn[1]; - } else { + } + else + { preferred = nn[(int) Math.round(Math.random())]; } } @@ -401,23 +496,29 @@ private Node[] splitNode(Node n) { } // Implementation of Quadratic PickSeeds - private RTree.Node[] qPickSeeds(LinkedList nn) { + private RTree.Node[] qPickSeeds(LinkedList nn) + { @SuppressWarnings("unchecked") RTree.Node[] bestPair = new RTree.Node[2]; float maxWaste = -1.0f * Float.MAX_VALUE; - for (Node n1 : nn) { - for (Node n2 : nn) { - if (n1 == n2) continue; + for (Node n1 : nn) + { + for (Node n2 : nn) + { + if (n1 == n2) + continue; float n1a = getArea(n1.dimensions); float n2a = getArea(n2.dimensions); float ja = 1.0f; - for (int i = 0; i < numDims; i++) { + for (int i = 0; i < numDims; i++) + { float jc0 = Math.min(n1.coords[i], n2.coords[i]); float jc1 = Math.max(n1.coords[i] + n1.dimensions[i], n2.coords[i] + n2.dimensions[i]); ja *= (jc1 - jc0); } float waste = ja - n1a - n2a; - if (waste > maxWaste) { + if (waste > maxWaste) + { maxWaste = waste; bestPair[0] = n1; bestPair[1] = n2; @@ -435,14 +536,17 @@ private RTree.Node[] qPickSeeds(LinkedList nn) { * @param cc the children to be divided between the new nodes, one item will be removed from this list. * @param nn the candidate nodes for the children to be added to. */ - private Node qPickNext(LinkedList cc, Node[] nn) { + private Node qPickNext(LinkedList cc, Node[] nn) + { float maxDiff = -1.0f * Float.MAX_VALUE; Node nextC = null; - for (Node c : cc) { + for (Node c : cc) + { float n0Exp = getRequiredExpansion(nn[0].coords, nn[0].dimensions, c); float n1Exp = getRequiredExpansion(nn[1].coords, nn[1].dimensions, c); float diff = Math.abs(n1Exp - n0Exp); - if (diff > maxDiff) { + if (diff > maxDiff) + { maxDiff = diff; nextC = c; } @@ -453,34 +557,41 @@ private Node qPickNext(LinkedList cc, Node[] nn) { } // Implementation of LinearPickSeeds - private RTree.Node[] lPickSeeds(LinkedList nn) { + private RTree.Node[] lPickSeeds(LinkedList nn) + { @SuppressWarnings("unchecked") RTree.Node[] bestPair = new RTree.Node[2]; boolean foundBestPair = false; float bestSep = 0.0f; - for (int i = 0; i < numDims; i++) { + for (int i = 0; i < numDims; i++) + { float dimLb = Float.MAX_VALUE, dimMinUb = Float.MAX_VALUE; float dimUb = -1.0f * Float.MAX_VALUE, dimMaxLb = -1.0f * Float.MAX_VALUE; Node nMaxLb = null, nMinUb = null; - for (Node n : nn) { - if (n.coords[i] < dimLb) { + for (Node n : nn) + { + if (n.coords[i] < dimLb) + { dimLb = n.coords[i]; } - if (n.dimensions[i] + n.coords[i] > dimUb) { + if (n.dimensions[i] + n.coords[i] > dimUb) + { dimUb = n.dimensions[i] + n.coords[i]; } - if (n.coords[i] > dimMaxLb) { + if (n.coords[i] > dimMaxLb) + { dimMaxLb = n.coords[i]; nMaxLb = n; } - if (n.dimensions[i] + n.coords[i] < dimMinUb) { + if (n.dimensions[i] + n.coords[i] < dimMinUb) + { dimMinUb = n.dimensions[i] + n.coords[i]; nMinUb = n; } } - float sep = (nMaxLb == nMinUb) ? -1.0f : - Math.abs((dimMinUb - dimMaxLb) / (dimUb - dimLb)); - if (sep >= bestSep) { + float sep = (nMaxLb == nMinUb) ? -1.0f : Math.abs((dimMinUb - dimMaxLb) / (dimUb - dimLb)); + if (sep >= bestSep) + { bestPair[0] = nMaxLb; bestPair[1] = nMinUb; bestSep = sep; @@ -488,10 +599,11 @@ private RTree.Node[] lPickSeeds(LinkedList nn) { } } // In the degenerate case where all points are the same, the above - // algorithm does not find a best pair. Just pick the first 2 + // algorithm does not find a best pair. Just pick the first 2 // children. - if (!foundBestPair) { - bestPair = new RTree.Node[]{nn.get(0), nn.get(1)}; + if (!foundBestPair) + { + bestPair = new RTree.Node[] { nn.get(0), nn.get(1) }; } nn.remove(bestPair[0]); nn.remove(bestPair[1]); @@ -503,34 +615,42 @@ private RTree.Node[] lPickSeeds(LinkedList nn) { * * @param cc the children to be divided between the new nodes, one item will be removed from this list. */ - private Node lPickNext(LinkedList cc) { + private Node lPickNext(LinkedList cc) + { return cc.pop(); } - private void tighten(Node... nodes) { + private void tighten(Node... nodes) + { assert (nodes.length >= 1) : "Pass some nodes to tighten!"; - for (Node n : nodes) { + for (Node n : nodes) + { assert (n.children.size() > 0) : "tighten() called on empty node!"; float[] minCoords = new float[numDims]; float[] maxCoords = new float[numDims]; - for (int i = 0; i < numDims; i++) { + for (int i = 0; i < numDims; i++) + { minCoords[i] = Float.MAX_VALUE; maxCoords[i] = -1.0f * Float.MAX_VALUE; - for (Node c : n.children) { + for (Node c : n.children) + { // we may have bulk-added a bunch of children to a node (eg. in // splitNode) // so here we just enforce the child->parent relationship. c.parent = n; - if (c.coords[i] < minCoords[i]) { + if (c.coords[i] < minCoords[i]) + { minCoords[i] = c.coords[i]; } - if ((c.coords[i] + c.dimensions[i]) > maxCoords[i]) { + if ((c.coords[i] + c.dimensions[i]) > maxCoords[i]) + { maxCoords[i] = (c.coords[i] + c.dimensions[i]); } } } - for (int i = 0; i < numDims; i++) { + for (int i = 0; i < numDims; i++) + { // Convert max coords to dimensions maxCoords[i] -= minCoords[i]; } @@ -539,25 +659,33 @@ private void tighten(Node... nodes) { } } - private RTree.Node chooseLeaf(RTree.Node n, RTree.Entry e) { - if (n.leaf) { + private RTree.Node chooseLeaf(RTree.Node n, RTree.Entry e) + { + if (n.leaf) + { return n; } float minInc = Float.MAX_VALUE; Node next = null; - for (RTree.Node c : n.children) { + for (RTree.Node c : n.children) + { float inc = getRequiredExpansion(c.coords, c.dimensions, e); - if (inc < minInc) { + if (inc < minInc) + { minInc = inc; next = c; - } else if (inc == minInc) { + } + else if (inc == minInc) + { float curArea = 1.0f; float thisArea = 1.0f; - for (int i = 0; i < c.dimensions.length; i++) { + for (int i = 0; i < c.dimensions.length; i++) + { curArea *= next.dimensions[i]; thisArea *= c.dimensions[i]; } - if (thisArea < curArea) { + if (thisArea < curArea) + { next = c; } } @@ -569,63 +697,88 @@ private RTree.Node chooseLeaf(RTree.Node n, RTree.Entry e) { * Returns the increase in area necessary for the given rectangle to cover the * given entry. */ - private float getRequiredExpansion(float[] coords, float[] dimensions, Node e) { + private float getRequiredExpansion(float[] coords, float[] dimensions, Node e) + { float area = getArea(dimensions); float[] deltas = new float[dimensions.length]; - for (int i = 0; i < deltas.length; i++) { - if (coords[i] + dimensions[i] < e.coords[i] + e.dimensions[i]) { + for (int i = 0; i < deltas.length; i++) + { + if (coords[i] + dimensions[i] < e.coords[i] + e.dimensions[i]) + { deltas[i] = e.coords[i] + e.dimensions[i] - coords[i] - dimensions[i]; - } else if (coords[i] + dimensions[i] > e.coords[i] + e.dimensions[i]) { + } + else if (coords[i] + dimensions[i] > e.coords[i] + e.dimensions[i]) + { deltas[i] = coords[i] - e.coords[i]; } } float expanded = 1.0f; - for (int i = 0; i < dimensions.length; i++) { + for (int i = 0; i < dimensions.length; i++) + { expanded *= dimensions[i] + deltas[i]; } return (expanded - area); } - private float getArea(float[] dimensions) { + private float getArea(float[] dimensions) + { float area = 1.0f; - for (int i = 0; i < dimensions.length; i++) { + for (int i = 0; i < dimensions.length; i++) + { area *= dimensions[i]; } return area; } - private boolean isOverlap(float[] scoords, float[] sdimensions, - float[] coords, float[] dimensions) { + private boolean isOverlap(float[] scoords, + float[] sdimensions, + float[] coords, + float[] dimensions) + { final float FUDGE_FACTOR = 1.001f; - for (int i = 0; i < scoords.length; i++) { + for (int i = 0; i < scoords.length; i++) + { boolean overlapInThisDimension = false; - if (scoords[i] == coords[i]) { + if (scoords[i] == coords[i]) + { overlapInThisDimension = true; - } else if (scoords[i] < coords[i]) { - if (scoords[i] + FUDGE_FACTOR * sdimensions[i] >= coords[i]) { + } + else if (scoords[i] < coords[i]) + { + if (scoords[i] + FUDGE_FACTOR * sdimensions[i] >= coords[i]) + { overlapInThisDimension = true; } - } else if (scoords[i] > coords[i]) { - if (coords[i] + FUDGE_FACTOR * dimensions[i] >= scoords[i]) { + } + else if (scoords[i] > coords[i]) + { + if (coords[i] + FUDGE_FACTOR * dimensions[i] >= scoords[i]) + { overlapInThisDimension = true; } } - if (!overlapInThisDimension) { + if (!overlapInThisDimension) + { return false; } } return true; } - private class Node { + private class Node + { final float[] coords; + final float[] dimensions; + final LinkedList children; + final boolean leaf; Node parent; - private Node(float[] coords, float[] dimensions, boolean leaf) { + private Node(float[] coords, float[] dimensions, boolean leaf) + { this.coords = new float[coords.length]; this.dimensions = new float[dimensions.length]; System.arraycopy(coords, 0, this.coords, 0, coords.length); @@ -636,10 +789,12 @@ private Node(float[] coords, float[] dimensions, boolean leaf) { } - private class Entry extends Node { + private class Entry extends Node + { final T entry; - public Entry(float[] coords, float[] dimensions, T entry) { + public Entry(float[] coords, float[] dimensions, T entry) + { // an entry isn't actually a leaf (its parent is a leaf) // but all the algorithms should stop at the first leaf they encounter, // so this little hack shouldn't be a problem. @@ -647,8 +802,88 @@ public Entry(float[] coords, float[] dimensions, T entry) { this.entry = entry; } - public String toString() { + @Override + public String toString() + { return "Entry: " + entry; } } + + private class TreeSearchSpliterator implements Spliterator + { + private Queue queue = new LinkedList<>(); + + private float[] coords; + + private float[] dimensions; + + private int processedCount = 0; + + public TreeSearchSpliterator(float[] coords, + float[] dimensions) + { + queue.add(root); + this.coords = coords; + this.dimensions = dimensions; + } + + @Override + @SuppressWarnings("unchecked") + public boolean tryAdvance(Consumer action) + { + Node node = queue.poll(); + + while (node != null) + { + if (node instanceof RTree.Entry) + { + ++processedCount; + action.accept(((RTree.Entry) node).entry); + return true; + } + else if (node.leaf) + { + for (Node e : node.children) + { + if (isOverlap(coords, dimensions, e.coords, e.dimensions)) + { + queue.add(e); + } + } + } + else + { + for (Node c : node.children) + { + if (isOverlap(coords, dimensions, c.coords, c.dimensions)) + { + queue.add(c); + } + } + } + + node = queue.poll(); + } + + return false; + } + + @Override + public int characteristics() + { + return SIZED; + } + + @Override + public long estimateSize() + { + return size - processedCount; + } + + @Override + public Spliterator trySplit() + { + return null; + } + } }