--- /dev/null
+// RTree.java\r
+// Java Spatial Index Library\r
+// Copyright (C) 2002-2005 Infomatiq Limited\r
+// Copyright (C) 2008-2010 aled@sourceforge.net\r
+// \r
+// This library is free software; you can redistribute it and/or\r
+// modify it under the terms of the GNU Lesser General Public\r
+// License as published by the Free Software Foundation; either\r
+// version 2.1 of the License, or (at your option) any later version.\r
+// \r
+// This library is distributed in the hope that it will be useful,\r
+// but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+// Lesser General Public License for more details.\r
+// \r
+// You should have received a copy of the GNU Lesser General Public\r
+// License along with this library; if not, write to the Free Software\r
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
+\r
+package com.infomatiq.jsi.rtree;\r
+\r
+import gnu.trove.TIntArrayList;\r
+import gnu.trove.TIntObjectHashMap;\r
+import gnu.trove.TIntProcedure;\r
+import gnu.trove.TIntStack;\r
+\r
+import java.util.Properties;\r
+\r
+import com.infomatiq.jsi.Point;\r
+import com.infomatiq.jsi.Rectangle;\r
+import com.infomatiq.jsi.PriorityQueue;\r
+import com.infomatiq.jsi.SpatialIndex;\r
+\r
+/**\r
+ * Stub replacement for org.apache.log4j.Logger to prevent dependency on log4j.\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ */\r
+class Logger {\r
+ String name;\r
+\r
+ public Logger(String name) {\r
+ this.name = name;\r
+ }\r
+\r
+ public static Logger getLogger(String name) {\r
+ return new Logger(name);\r
+ }\r
+\r
+ public void warn(String string) {\r
+ //System.out.println(name + ": WARN " + string);\r
+ }\r
+\r
+ public boolean isDebugEnabled() {\r
+ return false;\r
+ }\r
+\r
+ public void debug(String string) {\r
+ //System.out.println(name + ": DEBUG " + string);\r
+ }\r
+\r
+ public void error(String string) {\r
+ System.out.println(name + ": ERROR " + string);\r
+ }\r
+}\r
+\r
+/**\r
+ * <p>This is a lightweight RTree implementation, specifically designed \r
+ * for the following features (in order of importance): \r
+ * <ul>\r
+ * <li>Fast intersection query performance. To achieve this, the RTree \r
+ * uses only main memory to store entries. Obviously this will only improve\r
+ * performance if there is enough physical memory to avoid paging.</li>\r
+ * <li>Low memory requirements.</li>\r
+ * <li>Fast add performance.</li>\r
+ * </ul></p> \r
+ * \r
+ * <p>The main reason for the high speed of this RTree implementation is the \r
+ * avoidance of the creation of unnecessary objects, mainly achieved by using\r
+ * primitive collections from the trove4j library.</p>\r
+ * \r
+ * @author aled@sourceforge.net\r
+ * @version 1.0b8\r
+ */\r
+public class RTree implements SpatialIndex {\r
+ private static final Logger log = Logger.getLogger(RTree.class.getName());\r
+ private static final Logger deleteLog = Logger.getLogger(RTree.class.getName() + "-delete");\r
+ \r
+ private static final String version = "1.0b8";\r
+ \r
+ // parameters of the tree\r
+ private final static int DEFAULT_MAX_NODE_ENTRIES = 10;\r
+ int maxNodeEntries;\r
+ int minNodeEntries;\r
+ \r
+ // map of nodeId -> node object\r
+ // TODO eliminate this map - it should not be needed. Nodes\r
+ // can be found by traversing the tree.\r
+ private TIntObjectHashMap<Node> nodeMap = new TIntObjectHashMap<Node>();\r
+ \r
+ // internal consistency checking - set to true if debugging tree corruption\r
+ private final static boolean INTERNAL_CONSISTENCY_CHECKING = false;\r
+ \r
+ // used to mark the status of entries during a node split\r
+ private final static int ENTRY_STATUS_ASSIGNED = 0;\r
+ private final static int ENTRY_STATUS_UNASSIGNED = 1; \r
+ private byte[] entryStatus = null;\r
+ private byte[] initialEntryStatus = null;\r
+ \r
+ // stacks used to store nodeId and entry index of each node \r
+ // from the root down to the leaf. Enables fast lookup\r
+ // of nodes when a split is propagated up the tree.\r
+ private TIntStack parents = new TIntStack();\r
+ private TIntStack parentsEntry = new TIntStack();\r
+ \r
+ // initialisation\r
+ private int treeHeight = 1; // leaves are always level 1\r
+ private int rootNodeId = 0;\r
+ private int size = 0;\r
+ \r
+ // Enables creation of new nodes\r
+ private int highestUsedNodeId = rootNodeId; \r
+ \r
+ // Deleted node objects are retained in the nodeMap, \r
+ // so that they can be reused. Store the IDs of nodes\r
+ // which can be reused.\r
+ private TIntStack deletedNodeIds = new TIntStack();\r
+ \r
+ // List of nearest rectangles. Use a member variable to\r
+ // avoid recreating the object each time nearest() is called.\r
+ private TIntArrayList nearestIds = new TIntArrayList();\r
+ private TIntArrayList savedValues = new TIntArrayList();\r
+ private float savedPriority = 0;\r
+\r
+ // List of nearestN rectangles\r
+ private SortedList nearestNIds = new SortedList();\r
+ \r
+ // List of nearestN rectanges, used in the alternative nearestN implementation.\r
+ private PriorityQueue distanceQueue = \r
+ new PriorityQueue(PriorityQueue.SORT_ORDER_ASCENDING);\r
+ \r
+ /**\r
+ * Constructor. Use init() method to initialize parameters of the RTree.\r
+ */\r
+ public RTree() { \r
+ return; // NOP \r
+ }\r
+ \r
+ //-------------------------------------------------------------------------\r
+ // public implementation of SpatialIndex interface:\r
+ // init(Properties)\r
+ // add(Rectangle, int)\r
+ // delete(Rectangle, int)\r
+ // nearest(Point, TIntProcedure, float)\r
+ // intersects(Rectangle, TIntProcedure)\r
+ // contains(Rectangle, TIntProcedure)\r
+ // size()\r
+ //-------------------------------------------------------------------------\r
+ /**\r
+ * <p>Initialize implementation dependent properties of the RTree.\r
+ * Currently implemented properties are:\r
+ * <ul>\r
+ * <li>MaxNodeEntries</li> This specifies the maximum number of entries\r
+ * in a node. The default value is 10, which is used if the property is\r
+ * not specified, or is less than 2.\r
+ * <li>MinNodeEntries</li> This specifies the minimum number of entries\r
+ * in a node. The default value is half of the MaxNodeEntries value (rounded\r
+ * down), which is used if the property is not specified or is less than 1.\r
+ * </ul></p>\r
+ * \r
+ * @see com.infomatiq.jsi.SpatialIndex#init(Properties)\r
+ */\r
+ public void init(Properties props) {\r
+ if (props == null) {\r
+ // use sensible defaults if null is passed in.\r
+ maxNodeEntries = 50;\r
+ minNodeEntries = 20;\r
+ } else {\r
+ maxNodeEntries = Integer.parseInt(props.getProperty("MaxNodeEntries", "0"));\r
+ minNodeEntries = Integer.parseInt(props.getProperty("MinNodeEntries", "0"));\r
+ \r
+ // Obviously a node with less than 2 entries cannot be split.\r
+ // The node splitting algorithm will work with only 2 entries\r
+ // per node, but will be inefficient.\r
+ if (maxNodeEntries < 2) { \r
+ log.warn("Invalid MaxNodeEntries = " + maxNodeEntries + " Resetting to default value of " + DEFAULT_MAX_NODE_ENTRIES);\r
+ maxNodeEntries = DEFAULT_MAX_NODE_ENTRIES;\r
+ }\r
+ \r
+ // The MinNodeEntries must be less than or equal to (int) (MaxNodeEntries / 2)\r
+ if (minNodeEntries < 1 || minNodeEntries > maxNodeEntries / 2) {\r
+ log.warn("MinNodeEntries must be between 1 and MaxNodeEntries / 2");\r
+ minNodeEntries = maxNodeEntries / 2;\r
+ }\r
+ }\r
+ \r
+ entryStatus = new byte[maxNodeEntries]; \r
+ initialEntryStatus = new byte[maxNodeEntries];\r
+ \r
+ for (int i = 0; i < maxNodeEntries; i++) {\r
+ initialEntryStatus[i] = ENTRY_STATUS_UNASSIGNED;\r
+ }\r
+ \r
+ Node root = new Node(rootNodeId, 1, maxNodeEntries);\r
+ nodeMap.put(rootNodeId, root);\r
+ \r
+ log.debug("init() " + " MaxNodeEntries = " + maxNodeEntries + ", MinNodeEntries = " + minNodeEntries);\r
+ }\r
+ \r
+ /**\r
+ * @see com.infomatiq.jsi.SpatialIndex#add(Rectangle, int)\r
+ */\r
+ public void add(Rectangle r, int id) {\r
+ if (log.isDebugEnabled()) {\r
+ log.debug("Adding rectangle " + r + ", id " + id);\r
+ }\r
+ \r
+ add(r.minX, r.minY, r.maxX, r.maxY, id, 1); \r
+ \r
+ size++;\r
+ \r
+ if (INTERNAL_CONSISTENCY_CHECKING) {\r
+ checkConsistency();\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Adds a new entry at a specified level in the tree\r
+ */\r
+ private void add(float minX, float minY, float maxX, float maxY, int id, int level) {\r
+ // I1 [Find position for new record] Invoke ChooseLeaf to select a \r
+ // leaf node L in which to place r\r
+ Node n = chooseNode(minX, minY, maxX, maxY, level);\r
+ Node newLeaf = null;\r
+ \r
+ // I2 [Add record to leaf node] If L has room for another entry, \r
+ // install E. Otherwise invoke SplitNode to obtain L and LL containing\r
+ // E and all the old entries of L\r
+ if (n.entryCount < maxNodeEntries) {\r
+ n.addEntry(minX, minY, maxX, maxY, id);\r
+ } else {\r
+ newLeaf = splitNode(n, minX, minY, maxX, maxY, id); \r
+ }\r
+ \r
+ // I3 [Propagate changes upwards] Invoke AdjustTree on L, also passing LL\r
+ // if a split was performed\r
+ Node newNode = adjustTree(n, newLeaf); \r
+\r
+ // I4 [Grow tree taller] If node split propagation caused the root to \r
+ // split, create a new root whose children are the two resulting nodes.\r
+ if (newNode != null) {\r
+ int oldRootNodeId = rootNodeId;\r
+ Node oldRoot = getNode(oldRootNodeId);\r
+ \r
+ rootNodeId = getNextNodeId();\r
+ treeHeight++;\r
+ Node root = new Node(rootNodeId, treeHeight, maxNodeEntries);\r
+ root.addEntry(newNode.mbrMinX, newNode.mbrMinY, newNode.mbrMaxX, newNode.mbrMaxY, newNode.nodeId);\r
+ root.addEntry(oldRoot.mbrMinX, oldRoot.mbrMinY, oldRoot.mbrMaxX, oldRoot.mbrMaxY, oldRoot.nodeId);\r
+ nodeMap.put(rootNodeId, root);\r
+ } \r
+ } \r
+ \r
+ /**\r
+ * @see com.infomatiq.jsi.SpatialIndex#delete(Rectangle, int)\r
+ */\r
+ public boolean delete(Rectangle r, int id) {\r
+ // FindLeaf algorithm inlined here. Note the "official" algorithm \r
+ // searches all overlapping entries. This seems inefficient to me, \r
+ // as an entry is only worth searching if it contains (NOT overlaps)\r
+ // the rectangle we are searching for.\r
+ //\r
+ // Also the algorithm has been changed so that it is not recursive.\r
+ \r
+ // FL1 [Search subtrees] If root is not a leaf, check each entry \r
+ // to determine if it contains r. For each entry found, invoke\r
+ // findLeaf on the node pointed to by the entry, until r is found or\r
+ // all entries have been checked.\r
+ parents.reset();\r
+ parents.push(rootNodeId);\r
+ \r
+ parentsEntry.reset();\r
+ parentsEntry.push(-1);\r
+ Node n = null;\r
+ int foundIndex = -1; // index of entry to be deleted in leaf\r
+ \r
+ while (foundIndex == -1 && parents.size() > 0) {\r
+ n = getNode(parents.peek());\r
+ int startIndex = parentsEntry.peek() + 1;\r
+ \r
+ if (!n.isLeaf()) {\r
+ deleteLog.debug("searching node " + n.nodeId + ", from index " + startIndex);\r
+ boolean contains = false;\r
+ for (int i = startIndex; i < n.entryCount; i++) {\r
+ if (Rectangle.contains(n.entriesMinX[i], n.entriesMinY[i], n.entriesMaxX[i], n.entriesMaxY[i],\r
+ r.minX, r.minY, r.maxX, r.maxY)) { \r
+ parents.push(n.ids[i]);\r
+ parentsEntry.pop();\r
+ parentsEntry.push(i); // this becomes the start index when the child has been searched\r
+ parentsEntry.push(-1);\r
+ contains = true;\r
+ break; // ie go to next iteration of while()\r
+ }\r
+ }\r
+ if (contains) {\r
+ continue;\r
+ }\r
+ } else {\r
+ foundIndex = n.findEntry(r.minX, r.minY, r.maxX, r.maxY, id); \r
+ }\r
+ \r
+ parents.pop();\r
+ parentsEntry.pop();\r
+ } // while not found\r
+ \r
+ if (foundIndex != -1) {\r
+ n.deleteEntry(foundIndex);\r
+ condenseTree(n);\r
+ size--;\r
+ }\r
+ \r
+ // shrink the tree if possible (i.e. if root node has exactly one entry,and that \r
+ // entry is not a leaf node, delete the root (it's entry becomes the new root)\r
+ Node root = getNode(rootNodeId);\r
+ while (root.entryCount == 1 && treeHeight > 1)\r
+ {\r
+ deletedNodeIds.push(rootNodeId);\r
+ root.entryCount = 0;\r
+ rootNodeId = root.ids[0];\r
+ treeHeight--;\r
+ root = getNode(rootNodeId);\r
+ }\r
+ \r
+ // if the tree is now empty, then set the MBR of the root node back to it's original state\r
+ // (this is only needed when the tree is empty, as this is the only state where an empty node\r
+ // is not eliminated)\r
+ if (size == 0) {\r
+ root.mbrMinX = Float.MAX_VALUE;\r
+ root.mbrMinY = Float.MAX_VALUE;\r
+ root.mbrMaxX = -Float.MAX_VALUE;\r
+ root.mbrMaxY = -Float.MAX_VALUE;\r
+ }\r
+\r
+ if (INTERNAL_CONSISTENCY_CHECKING) {\r
+ checkConsistency();\r
+ }\r
+ \r
+ return (foundIndex != -1);\r
+ }\r
+ \r
+ /**\r
+ * @see com.infomatiq.jsi.SpatialIndex#nearest(Point, TIntProcedure, float)\r
+ */\r
+ public void nearest(Point p, TIntProcedure v, float furthestDistance) {\r
+ Node rootNode = getNode(rootNodeId);\r
+ \r
+ float furthestDistanceSq = furthestDistance * furthestDistance;\r
+ nearest(p, rootNode, furthestDistanceSq);\r
+ \r
+ nearestIds.forEach(v);\r
+ nearestIds.reset();\r
+ }\r
+ \r
+ private void createNearestNDistanceQueue(Point p, int count, float furthestDistance) {\r
+ distanceQueue.reset();\r
+ distanceQueue.setSortOrder(PriorityQueue.SORT_ORDER_DESCENDING);\r
+ \r
+ // return immediately if given an invalid "count" parameter\r
+ if (count <= 0) {\r
+ return;\r
+ } \r
+ \r
+ parents.reset();\r
+ parents.push(rootNodeId);\r
+ \r
+ parentsEntry.reset();\r
+ parentsEntry.push(-1);\r
+ \r
+ // TODO: possible shortcut here - could test for intersection with the \r
+ // MBR of the root node. If no intersection, return immediately.\r
+ \r
+ float furthestDistanceSq = furthestDistance * furthestDistance;\r
+ \r
+ while (parents.size() > 0) {\r
+ Node n = getNode(parents.peek());\r
+ int startIndex = parentsEntry.peek() + 1;\r
+ \r
+ if (!n.isLeaf()) {\r
+ // go through every entry in the index node to check\r
+ // if it could contain an entry closer than the farthest entry\r
+ // currently stored.\r
+ boolean near = false;\r
+ for (int i = startIndex; i < n.entryCount; i++) {\r
+ if (Rectangle.distanceSq(n.entriesMinX[i], n.entriesMinY[i], \r
+ n.entriesMaxX[i], n.entriesMaxY[i], \r
+ p.x, p.y) <= furthestDistanceSq) {\r
+ parents.push(n.ids[i]);\r
+ parentsEntry.pop();\r
+ parentsEntry.push(i); // this becomes the start index when the child has been searched\r
+ parentsEntry.push(-1);\r
+ near = true;\r
+ break; // ie go to next iteration of while()\r
+ }\r
+ }\r
+ if (near) {\r
+ continue;\r
+ }\r
+ } else {\r
+ // go through every entry in the leaf to check if \r
+ // it is currently one of the nearest N entries.\r
+ for (int i = 0; i < n.entryCount; i++) {\r
+ float entryDistanceSq = Rectangle.distanceSq(n.entriesMinX[i], n.entriesMinY[i],\r
+ n.entriesMaxX[i], n.entriesMaxY[i],\r
+ p.x, p.y);\r
+ int entryId = n.ids[i];\r
+ \r
+ if (entryDistanceSq <= furthestDistanceSq) {\r
+ distanceQueue.insert(entryId, entryDistanceSq);\r
+ \r
+ while (distanceQueue.size() > count) {\r
+ // normal case - we can simply remove the lowest priority (highest distance) entry\r
+ int value = distanceQueue.getValue();\r
+ float distanceSq = distanceQueue.getPriority();\r
+ distanceQueue.pop();\r
+ \r
+ // rare case - multiple items of the same priority (distance)\r
+ if (distanceSq == distanceQueue.getPriority()) {\r
+ savedValues.add(value);\r
+ savedPriority = distanceSq;\r
+ } else {\r
+ savedValues.reset();\r
+ }\r
+ }\r
+ \r
+ // if the saved values have the same distance as the\r
+ // next one in the tree, add them back in.\r
+ if (savedValues.size() > 0 && savedPriority == distanceQueue.getPriority()) {\r
+ for (int svi = 0; svi < savedValues.size(); svi++) {\r
+ distanceQueue.insert(savedValues.get(svi), savedPriority);\r
+ }\r
+ savedValues.reset();\r
+ }\r
+ \r
+ // narrow the search, if we have already found N items\r
+ if (distanceQueue.getPriority() < furthestDistanceSq && distanceQueue.size() >= count) {\r
+ furthestDistanceSq = distanceQueue.getPriority(); \r
+ }\r
+ } \r
+ } \r
+ }\r
+ parents.pop();\r
+ parentsEntry.pop(); \r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @see com.infomatiq.jsi.SpatialIndex#nearestNUnsorted(Point, TIntProcedure, int, float)\r
+ */\r
+ public void nearestNUnsorted(Point p, TIntProcedure v, int count, float furthestDistance) {\r
+ // This implementation is designed to give good performance\r
+ // where\r
+ // o N is high (100+)\r
+ // o The results do not need to be sorted by distance.\r
+ // \r
+ // Uses a priority queue as the underlying data structure. \r
+ // \r
+ // The behaviour of this algorithm has been carefully designed to\r
+ // return exactly the same items as the the original version (nearestN_orig), in particular,\r
+ // more than N items will be returned if items N and N+x have the\r
+ // same priority. \r
+ createNearestNDistanceQueue(p, count, furthestDistance);\r
+ \r
+ while (distanceQueue.size() > 0) {\r
+ v.execute(distanceQueue.getValue());\r
+ distanceQueue.pop();\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * @see com.infomatiq.jsi.SpatialIndex#nearestN(Point, TIntProcedure, int, float)\r
+ */\r
+ public void nearestN(Point p, TIntProcedure v, int count, float furthestDistance) {\r
+ createNearestNDistanceQueue(p, count, furthestDistance);\r
+ \r
+ distanceQueue.setSortOrder(PriorityQueue.SORT_ORDER_ASCENDING);\r
+ \r
+ while (distanceQueue.size() > 0) {\r
+ v.execute(distanceQueue.getValue());\r
+ distanceQueue.pop();\r
+ } \r
+ }\r
+ \r
+ /**\r
+ * @see com.infomatiq.jsi.SpatialIndex#nearestN(Point, TIntProcedure, int, float)\r
+ * @deprecated Use new NearestN or NearestNUnsorted instead.\r
+ * \r
+ * This implementation of nearestN is only suitable for small values of N (ie less than 10).\r
+ */ \r
+ public void nearestN_orig(Point p, TIntProcedure v, int count, float furthestDistance) {\r
+ // return immediately if given an invalid "count" parameter\r
+ if (count <= 0) {\r
+ return;\r
+ }\r
+ \r
+ parents.reset();\r
+ parents.push(rootNodeId);\r
+ \r
+ parentsEntry.reset();\r
+ parentsEntry.push(-1);\r
+ \r
+ nearestNIds.init(count);\r
+ \r
+ // TODO: possible shortcut here - could test for intersection with the \r
+ // MBR of the root node. If no intersection, return immediately.\r
+ \r
+ float furthestDistanceSq = furthestDistance * furthestDistance;\r
+ \r
+ while (parents.size() > 0) {\r
+ Node n = getNode(parents.peek());\r
+ int startIndex = parentsEntry.peek() + 1;\r
+ \r
+ if (!n.isLeaf()) {\r
+ // go through every entry in the index node to check\r
+ // if it could contain an entry closer than the farthest entry\r
+ // currently stored.\r
+ boolean near = false;\r
+ for (int i = startIndex; i < n.entryCount; i++) {\r
+ if (Rectangle.distanceSq(n.entriesMinX[i], n.entriesMinY[i], \r
+ n.entriesMaxX[i], n.entriesMaxY[i], \r
+ p.x, p.y) <= furthestDistanceSq) {\r
+ parents.push(n.ids[i]);\r
+ parentsEntry.pop();\r
+ parentsEntry.push(i); // this becomes the start index when the child has been searched\r
+ parentsEntry.push(-1);\r
+ near = true;\r
+ break; // ie go to next iteration of while()\r
+ }\r
+ }\r
+ if (near) {\r
+ continue;\r
+ }\r
+ } else {\r
+ // go through every entry in the leaf to check if \r
+ // it is currently one of the nearest N entries.\r
+ for (int i = 0; i < n.entryCount; i++) {\r
+ float entryDistanceSq = Rectangle.distanceSq(n.entriesMinX[i], n.entriesMinY[i],\r
+ n.entriesMaxX[i], n.entriesMaxY[i],\r
+ p.x, p.y);\r
+ int entryId = n.ids[i];\r
+ \r
+ if (entryDistanceSq <= furthestDistanceSq) {\r
+ // add the new entry to the tree. Note that the higher the distance, the lower the priority\r
+ nearestNIds.add(entryId, -entryDistanceSq);\r
+ \r
+ float tempFurthestDistanceSq = -nearestNIds.getLowestPriority();\r
+ if (tempFurthestDistanceSq < furthestDistanceSq) {\r
+ furthestDistanceSq = tempFurthestDistanceSq; \r
+ }\r
+ } \r
+ } \r
+ }\r
+ parents.pop();\r
+ parentsEntry.pop(); \r
+ }\r
+ \r
+ nearestNIds.forEachId(v);\r
+ }\r
+ \r
+ /**\r
+ * @see com.infomatiq.jsi.SpatialIndex#intersects(Rectangle, TIntProcedure)\r
+ */\r
+ public void intersects(Rectangle r, TIntProcedure v) {\r
+ Node rootNode = getNode(rootNodeId);\r
+ intersects(r, v, rootNode);\r
+ }\r
+\r
+ /**\r
+ * @see com.infomatiq.jsi.SpatialIndex#contains(Rectangle, TIntProcedure)\r
+ */\r
+ public void contains(Rectangle r, TIntProcedure v) {\r
+ // find all rectangles in the tree that are contained by the passed rectangle\r
+ // written to be non-recursive (should model other searches on this?)\r
+ \r
+ parents.reset();\r
+ parents.push(rootNodeId);\r
+ \r
+ parentsEntry.reset();\r
+ parentsEntry.push(-1);\r
+ \r
+ // TODO: possible shortcut here - could test for intersection with the \r
+ // MBR of the root node. If no intersection, return immediately.\r
+ \r
+ while (parents.size() > 0) {\r
+ Node n = getNode(parents.peek());\r
+ int startIndex = parentsEntry.peek() + 1;\r
+ \r
+ if (!n.isLeaf()) {\r
+ // go through every entry in the index node to check\r
+ // if it intersects the passed rectangle. If so, it \r
+ // could contain entries that are contained.\r
+ boolean intersects = false;\r
+ for (int i = startIndex; i < n.entryCount; i++) {\r
+ if (Rectangle.intersects(r.minX, r.minY, r.maxX, r.maxY, \r
+ n.entriesMinX[i], n.entriesMinY[i], n.entriesMaxX[i], n.entriesMaxY[i])) {\r
+ parents.push(n.ids[i]);\r
+ parentsEntry.pop();\r
+ parentsEntry.push(i); // this becomes the start index when the child has been searched\r
+ parentsEntry.push(-1);\r
+ intersects = true;\r
+ break; // ie go to next iteration of while()\r
+ }\r
+ }\r
+ if (intersects) {\r
+ continue;\r
+ }\r
+ } else {\r
+ // go through every entry in the leaf to check if \r
+ // it is contained by the passed rectangle\r
+ for (int i = 0; i < n.entryCount; i++) {\r
+ if (Rectangle.contains(r.minX, r.minY, r.maxX, r.maxY, \r
+ n.entriesMinX[i], n.entriesMinY[i], n.entriesMaxX[i], n.entriesMaxY[i])) {\r
+ if (!v.execute(n.ids[i])) {\r
+ return;\r
+ }\r
+ } \r
+ } \r
+ }\r
+ parents.pop();\r
+ parentsEntry.pop(); \r
+ }\r
+ }\r
+\r
+ /**\r
+ * @see com.infomatiq.jsi.SpatialIndex#size()\r
+ */\r
+ public int size() {\r
+ return size;\r
+ }\r
+\r
+ /**\r
+ * @see com.infomatiq.jsi.SpatialIndex#getBounds()\r
+ */\r
+ public Rectangle getBounds() {\r
+ Rectangle bounds = null;\r
+ \r
+ Node n = getNode(getRootNodeId());\r
+ if (n != null && n.entryCount > 0) {\r
+ bounds = new Rectangle();\r
+ bounds.minX = n.mbrMinX;\r
+ bounds.minY = n.mbrMinY;\r
+ bounds.maxX = n.mbrMaxX;\r
+ bounds.maxY = n.mbrMaxY;\r
+ }\r
+ return bounds;\r
+ }\r
+ \r
+ /**\r
+ * @see com.infomatiq.jsi.SpatialIndex#getVersion()\r
+ */\r
+ public String getVersion() {\r
+ return "RTree-" + version;\r
+ }\r
+ //-------------------------------------------------------------------------\r
+ // end of SpatialIndex methods\r
+ //-------------------------------------------------------------------------\r
+ \r
+ /**\r
+ * Get the next available node ID. Reuse deleted node IDs if\r
+ * possible\r
+ */\r
+ private int getNextNodeId() {\r
+ int nextNodeId = 0;\r
+ if (deletedNodeIds.size() > 0) {\r
+ nextNodeId = deletedNodeIds.pop();\r
+ } else {\r
+ nextNodeId = 1 + highestUsedNodeId++;\r
+ }\r
+ return nextNodeId;\r
+ }\r
+\r
+ /**\r
+ * Get a node object, given the ID of the node.\r
+ */\r
+ public Node getNode(int id) {\r
+ return nodeMap.get(id);\r
+ }\r
+\r
+ /**\r
+ * Get the highest used node ID\r
+ */ \r
+ public int getHighestUsedNodeId() {\r
+ return highestUsedNodeId;\r
+ }\r
+\r
+ /**\r
+ * Get the root node ID\r
+ */\r
+ public int getRootNodeId() {\r
+ return rootNodeId; \r
+ }\r
+ \r
+ /**\r
+ * Split a node. Algorithm is taken pretty much verbatim from\r
+ * Guttman's original paper.\r
+ * \r
+ * @return new node object.\r
+ */\r
+ private Node splitNode(Node n, float newRectMinX, float newRectMinY, float newRectMaxX, float newRectMaxY, int newId) {\r
+ // [Pick first entry for each group] Apply algorithm pickSeeds to \r
+ // choose two entries to be the first elements of the groups. Assign\r
+ // each to a group.\r
+ \r
+ // debug code\r
+ float initialArea = 0;\r
+ if (log.isDebugEnabled()) {\r
+ float unionMinX = Math.min(n.mbrMinX, newRectMinX);\r
+ float unionMinY = Math.min(n.mbrMinY, newRectMinY);\r
+ float unionMaxX = Math.max(n.mbrMaxX, newRectMaxX);\r
+ float unionMaxY = Math.max(n.mbrMaxY, newRectMaxY);\r
+ \r
+ initialArea = (unionMaxX - unionMinX) * (unionMaxY - unionMinY);\r
+ }\r
+ \r
+ System.arraycopy(initialEntryStatus, 0, entryStatus, 0, maxNodeEntries);\r
+ \r
+ Node newNode = null;\r
+ newNode = new Node(getNextNodeId(), n.level, maxNodeEntries);\r
+ nodeMap.put(newNode.nodeId, newNode);\r
+ \r
+ pickSeeds(n, newRectMinX, newRectMinY, newRectMaxX, newRectMaxY, newId, newNode); // this also sets the entryCount to 1\r
+ \r
+ // [Check if done] If all entries have been assigned, stop. If one\r
+ // group has so few entries that all the rest must be assigned to it in \r
+ // order for it to have the minimum number m, assign them and stop. \r
+ while (n.entryCount + newNode.entryCount < maxNodeEntries + 1) {\r
+ if (maxNodeEntries + 1 - newNode.entryCount == minNodeEntries) {\r
+ // assign all remaining entries to original node\r
+ for (int i = 0; i < maxNodeEntries; i++) {\r
+ if (entryStatus[i] == ENTRY_STATUS_UNASSIGNED) {\r
+ entryStatus[i] = ENTRY_STATUS_ASSIGNED;\r
+ \r
+ if (n.entriesMinX[i] < n.mbrMinX) n.mbrMinX = n.entriesMinX[i];\r
+ if (n.entriesMinY[i] < n.mbrMinY) n.mbrMinY = n.entriesMinY[i];\r
+ if (n.entriesMaxX[i] > n.mbrMaxX) n.mbrMaxX = n.entriesMaxX[i];\r
+ if (n.entriesMaxY[i] > n.mbrMaxY) n.mbrMaxY = n.entriesMaxY[i];\r
+ \r
+ n.entryCount++;\r
+ }\r
+ }\r
+ break;\r
+ } \r
+ if (maxNodeEntries + 1 - n.entryCount == minNodeEntries) {\r
+ // assign all remaining entries to new node\r
+ for (int i = 0; i < maxNodeEntries; i++) {\r
+ if (entryStatus[i] == ENTRY_STATUS_UNASSIGNED) {\r
+ entryStatus[i] = ENTRY_STATUS_ASSIGNED;\r
+ newNode.addEntry(n.entriesMinX[i], n.entriesMinY[i], n.entriesMaxX[i], n.entriesMaxY[i], n.ids[i]);\r
+ n.ids[i] = -1; // an id of -1 indicates the entry is not in use\r
+ }\r
+ }\r
+ break;\r
+ }\r
+ \r
+ // [Select entry to assign] Invoke algorithm pickNext to choose the\r
+ // next entry to assign. Add it to the group whose covering rectangle \r
+ // will have to be enlarged least to accommodate it. Resolve ties\r
+ // by adding the entry to the group with smaller area, then to the \r
+ // the one with fewer entries, then to either. Repeat from S2\r
+ pickNext(n, newNode); \r
+ }\r
+ \r
+ n.reorganize(this);\r
+ \r
+ // check that the MBR stored for each node is correct.\r
+ if (INTERNAL_CONSISTENCY_CHECKING) {\r
+ Rectangle nMBR = new Rectangle(n.mbrMinX, n.mbrMinY, n.mbrMaxX, n.mbrMaxY);\r
+ if (!nMBR.equals(calculateMBR(n))) {\r
+ log.error("Error: splitNode old node MBR wrong");\r
+ }\r
+ Rectangle newNodeMBR = new Rectangle(newNode.mbrMinX, newNode.mbrMinY, newNode.mbrMaxX, newNode.mbrMaxY);\r
+ if (!newNodeMBR.equals(calculateMBR(newNode))) {\r
+ log.error("Error: splitNode new node MBR wrong");\r
+ }\r
+ }\r
+ \r
+ // debug code\r
+ if (log.isDebugEnabled()) {\r
+ float newArea = Rectangle.area(n.mbrMinX, n.mbrMinY, n.mbrMaxX, n.mbrMaxY) + \r
+ Rectangle.area(newNode.mbrMinX, newNode.mbrMinY, newNode.mbrMaxX, newNode.mbrMaxY);\r
+ float percentageIncrease = (100 * (newArea - initialArea)) / initialArea;\r
+ log.debug("Node " + n.nodeId + " split. New area increased by " + percentageIncrease + "%"); \r
+ }\r
+ \r
+ return newNode;\r
+ }\r
+ \r
+ /**\r
+ * Pick the seeds used to split a node.\r
+ * Select two entries to be the first elements of the groups\r
+ */\r
+ private void pickSeeds(Node n, float newRectMinX, float newRectMinY, float newRectMaxX, float newRectMaxY, int newId, Node newNode) {\r
+ // Find extreme rectangles along all dimension. Along each dimension,\r
+ // find the entry whose rectangle has the highest low side, and the one \r
+ // with the lowest high side. Record the separation.\r
+ float maxNormalizedSeparation = -1; // initialize to -1 so that even overlapping rectangles will be considered for the seeds\r
+ int highestLowIndex = -1;\r
+ int lowestHighIndex = -1;\r
+ \r
+ // for the purposes of picking seeds, take the MBR of the node to include\r
+ // the new rectangle aswell.\r
+ if (newRectMinX < n.mbrMinX) n.mbrMinX = newRectMinX;\r
+ if (newRectMinY < n.mbrMinY) n.mbrMinY = newRectMinY;\r
+ if (newRectMaxX > n.mbrMaxX) n.mbrMaxX = newRectMaxX;\r
+ if (newRectMaxY > n.mbrMaxY) n.mbrMaxY = newRectMaxY;\r
+ \r
+ float mbrLenX = n.mbrMaxX - n.mbrMinX;\r
+ float mbrLenY = n.mbrMaxY - n.mbrMinY;\r
+ \r
+ if (log.isDebugEnabled()) {\r
+ log.debug("pickSeeds(): NodeId = " + n.nodeId);\r
+ }\r
+ \r
+ float tempHighestLow = newRectMinX;\r
+ int tempHighestLowIndex = -1; // -1 indicates the new rectangle is the seed\r
+ \r
+ float tempLowestHigh = newRectMaxX;\r
+ int tempLowestHighIndex = -1; // -1 indicates the new rectangle is the seed \r
+ \r
+ for (int i = 0; i < n.entryCount; i++) {\r
+ float tempLow = n.entriesMinX[i];\r
+ if (tempLow >= tempHighestLow) {\r
+ tempHighestLow = tempLow;\r
+ tempHighestLowIndex = i;\r
+ } else { // ensure that the same index cannot be both lowestHigh and highestLow\r
+ float tempHigh = n.entriesMaxX[i];\r
+ if (tempHigh <= tempLowestHigh) {\r
+ tempLowestHigh = tempHigh;\r
+ tempLowestHighIndex = i;\r
+ }\r
+ }\r
+ \r
+ // PS2 [Adjust for shape of the rectangle cluster] Normalize the separations\r
+ // by dividing by the widths of the entire set along the corresponding\r
+ // dimension\r
+ float normalizedSeparation = mbrLenX == 0 ? 1 : (tempHighestLow - tempLowestHigh) / mbrLenX;\r
+ if (normalizedSeparation > 1 || normalizedSeparation < -1) {\r
+ log.error("Invalid normalized separation X");\r
+ }\r
+ \r
+ if (log.isDebugEnabled()) {\r
+ log.debug("Entry " + i + ", dimension X: HighestLow = " + tempHighestLow + \r
+ " (index " + tempHighestLowIndex + ")" + ", LowestHigh = " +\r
+ tempLowestHigh + " (index " + tempLowestHighIndex + ", NormalizedSeparation = " + normalizedSeparation);\r
+ }\r
+ \r
+ // PS3 [Select the most extreme pair] Choose the pair with the greatest\r
+ // normalized separation along any dimension.\r
+ // Note that if negative it means the rectangles overlapped. However still include\r
+ // overlapping rectangles if that is the only choice available.\r
+ if (normalizedSeparation >= maxNormalizedSeparation) {\r
+ highestLowIndex = tempHighestLowIndex;\r
+ lowestHighIndex = tempLowestHighIndex;\r
+ maxNormalizedSeparation = normalizedSeparation;\r
+ }\r
+ }\r
+ \r
+ // Repeat for the Y dimension\r
+ tempHighestLow = newRectMinY;\r
+ tempHighestLowIndex = -1; // -1 indicates the new rectangle is the seed\r
+ \r
+ tempLowestHigh = newRectMaxY;\r
+ tempLowestHighIndex = -1; // -1 indicates the new rectangle is the seed \r
+ \r
+ for (int i = 0; i < n.entryCount; i++) {\r
+ float tempLow = n.entriesMinY[i];\r
+ if (tempLow >= tempHighestLow) {\r
+ tempHighestLow = tempLow;\r
+ tempHighestLowIndex = i;\r
+ } else { // ensure that the same index cannot be both lowestHigh and highestLow\r
+ float tempHigh = n.entriesMaxY[i];\r
+ if (tempHigh <= tempLowestHigh) {\r
+ tempLowestHigh = tempHigh;\r
+ tempLowestHighIndex = i;\r
+ }\r
+ }\r
+ \r
+ // PS2 [Adjust for shape of the rectangle cluster] Normalize the separations\r
+ // by dividing by the widths of the entire set along the corresponding\r
+ // dimension\r
+ float normalizedSeparation = mbrLenY == 0 ? 1 : (tempHighestLow - tempLowestHigh) / mbrLenY;\r
+ if (normalizedSeparation > 1 || normalizedSeparation < -1) {\r
+ log.error("Invalid normalized separation Y");\r
+ }\r
+ \r
+ if (log.isDebugEnabled()) {\r
+ log.debug("Entry " + i + ", dimension Y: HighestLow = " + tempHighestLow + \r
+ " (index " + tempHighestLowIndex + ")" + ", LowestHigh = " +\r
+ tempLowestHigh + " (index " + tempLowestHighIndex + ", NormalizedSeparation = " + normalizedSeparation);\r
+ }\r
+ \r
+ // PS3 [Select the most extreme pair] Choose the pair with the greatest\r
+ // normalized separation along any dimension.\r
+ // Note that if negative it means the rectangles overlapped. However still include\r
+ // overlapping rectangles if that is the only choice available.\r
+ if (normalizedSeparation >= maxNormalizedSeparation) {\r
+ highestLowIndex = tempHighestLowIndex;\r
+ lowestHighIndex = tempLowestHighIndex;\r
+ maxNormalizedSeparation = normalizedSeparation;\r
+ }\r
+ }\r
+ \r
+ // At this point it is possible that the new rectangle is both highestLow and lowestHigh.\r
+ // This can happen if all rectangles in the node overlap the new rectangle.\r
+ // Resolve this by declaring that the highestLowIndex is the lowest Y and,\r
+ // the lowestHighIndex is the largest X (but always a different rectangle)\r
+ if (highestLowIndex == lowestHighIndex) { \r
+ highestLowIndex = -1;\r
+ float tempMinY = newRectMinY;\r
+ lowestHighIndex = 0;\r
+ float tempMaxX = n.entriesMaxX[0];\r
+ \r
+ for (int i = 1; i < n.entryCount; i++) {\r
+ if (n.entriesMinY[i] < tempMinY) {\r
+ tempMinY = n.entriesMinY[i];\r
+ highestLowIndex = i;\r
+ }\r
+ else if (n.entriesMaxX[i] > tempMaxX) {\r
+ tempMaxX = n.entriesMaxX[i];\r
+ lowestHighIndex = i;\r
+ }\r
+ }\r
+ }\r
+ \r
+ // highestLowIndex is the seed for the new node.\r
+ if (highestLowIndex == -1) {\r
+ newNode.addEntry(newRectMinX, newRectMinY, newRectMaxX, newRectMaxY, newId);\r
+ } else {\r
+ newNode.addEntry(n.entriesMinX[highestLowIndex], n.entriesMinY[highestLowIndex], \r
+ n.entriesMaxX[highestLowIndex], n.entriesMaxY[highestLowIndex], \r
+ n.ids[highestLowIndex]);\r
+ n.ids[highestLowIndex] = -1;\r
+ \r
+ // move the new rectangle into the space vacated by the seed for the new node\r
+ n.entriesMinX[highestLowIndex] = newRectMinX;\r
+ n.entriesMinY[highestLowIndex] = newRectMinY;\r
+ n.entriesMaxX[highestLowIndex] = newRectMaxX;\r
+ n.entriesMaxY[highestLowIndex] = newRectMaxY;\r
+ \r
+ n.ids[highestLowIndex] = newId;\r
+ }\r
+ \r
+ // lowestHighIndex is the seed for the original node. \r
+ if (lowestHighIndex == -1) {\r
+ lowestHighIndex = highestLowIndex;\r
+ }\r
+ \r
+ entryStatus[lowestHighIndex] = ENTRY_STATUS_ASSIGNED;\r
+ n.entryCount = 1;\r
+ n.mbrMinX = n.entriesMinX[lowestHighIndex];\r
+ n.mbrMinY = n.entriesMinY[lowestHighIndex];\r
+ n.mbrMaxX = n.entriesMaxX[lowestHighIndex];\r
+ n.mbrMaxY = n.entriesMaxY[lowestHighIndex];\r
+ }\r
+\r
+ /** \r
+ * Pick the next entry to be assigned to a group during a node split.\r
+ * \r
+ * [Determine cost of putting each entry in each group] For each \r
+ * entry not yet in a group, calculate the area increase required\r
+ * in the covering rectangles of each group \r
+ */\r
+ private int pickNext(Node n, Node newNode) {\r
+ float maxDifference = Float.NEGATIVE_INFINITY;\r
+ int next = 0;\r
+ int nextGroup = 0;\r
+ \r
+ maxDifference = Float.NEGATIVE_INFINITY;\r
+ \r
+ if (log.isDebugEnabled()) {\r
+ log.debug("pickNext()");\r
+ }\r
+ \r
+ for (int i = 0; i < maxNodeEntries; i++) {\r
+ if (entryStatus[i] == ENTRY_STATUS_UNASSIGNED) {\r
+ \r
+ if (n.ids[i] == -1) {\r
+ log.error("Error: Node " + n.nodeId + ", entry " + i + " is null");\r
+ }\r
+ \r
+ float nIncrease = Rectangle.enlargement(n.mbrMinX, n.mbrMinY, n.mbrMaxX, n.mbrMaxY, \r
+ n.entriesMinX[i], n.entriesMinY[i], n.entriesMaxX[i], n.entriesMaxY[i]);\r
+ float newNodeIncrease = Rectangle.enlargement(newNode.mbrMinX, newNode.mbrMinY, newNode.mbrMaxX, newNode.mbrMaxY,\r
+ n.entriesMinX[i], n.entriesMinY[i], n.entriesMaxX[i], n.entriesMaxY[i]);\r
+\r
+ float difference = Math.abs(nIncrease - newNodeIncrease);\r
+ \r
+ if (difference > maxDifference) {\r
+ next = i;\r
+ \r
+ if (nIncrease < newNodeIncrease) {\r
+ nextGroup = 0; \r
+ } else if (newNodeIncrease < nIncrease) {\r
+ nextGroup = 1;\r
+ } else if (Rectangle.area(n.mbrMinX, n.mbrMinY, n.mbrMaxX, n.mbrMaxY) < Rectangle.area(newNode.mbrMinX, newNode.mbrMinY, newNode.mbrMaxX, newNode.mbrMaxY)) {\r
+ nextGroup = 0;\r
+ } else if (Rectangle.area(newNode.mbrMinX, newNode.mbrMinY, newNode.mbrMaxX, newNode.mbrMaxY) < Rectangle.area(n.mbrMinX, n.mbrMinY, n.mbrMaxX, n.mbrMaxY)) {\r
+ nextGroup = 1;\r
+ } else if (newNode.entryCount < maxNodeEntries / 2) {\r
+ nextGroup = 0;\r
+ } else {\r
+ nextGroup = 1;\r
+ }\r
+ maxDifference = difference; \r
+ }\r
+ if (log.isDebugEnabled()) {\r
+ log.debug("Entry " + i + " group0 increase = " + nIncrease + ", group1 increase = " + newNodeIncrease +\r
+ ", diff = " + difference + ", MaxDiff = " + maxDifference + " (entry " + next + ")");\r
+ }\r
+ }\r
+ }\r
+ \r
+ entryStatus[next] = ENTRY_STATUS_ASSIGNED;\r
+ \r
+ if (nextGroup == 0) {\r
+ if (n.entriesMinX[next] < n.mbrMinX) n.mbrMinX = n.entriesMinX[next];\r
+ if (n.entriesMinY[next] < n.mbrMinY) n.mbrMinY = n.entriesMinY[next];\r
+ if (n.entriesMaxX[next] > n.mbrMaxX) n.mbrMaxX = n.entriesMaxX[next];\r
+ if (n.entriesMaxY[next] > n.mbrMaxY) n.mbrMaxY = n.entriesMaxY[next];\r
+ n.entryCount++;\r
+ } else {\r
+ // move to new node.\r
+ newNode.addEntry(n.entriesMinX[next], n.entriesMinY[next], n.entriesMaxX[next], n.entriesMaxY[next], n.ids[next]);\r
+ n.ids[next] = -1;\r
+ }\r
+ \r
+ return next; \r
+ }\r
+\r
+ /**\r
+ * Recursively searches the tree for the nearest entry. Other queries\r
+ * call execute() on an IntProcedure when a matching entry is found; \r
+ * however nearest() must store the entry Ids as it searches the tree,\r
+ * in case a nearer entry is found.\r
+ * Uses the member variable nearestIds to store the nearest\r
+ * entry IDs.\r
+ * \r
+ * TODO rewrite this to be non-recursive?\r
+ */\r
+ private float nearest(Point p, Node n, float furthestDistanceSq) {\r
+ for (int i = 0; i < n.entryCount; i++) {\r
+ float tempDistanceSq = Rectangle.distanceSq(n.entriesMinX[i], n.entriesMinY[i], n.entriesMaxX[i], n.entriesMaxY[i], p.x, p.y);\r
+ if (n.isLeaf()) { // for leaves, the distance is an actual nearest distance \r
+ if (tempDistanceSq < furthestDistanceSq) {\r
+ furthestDistanceSq = tempDistanceSq;\r
+ nearestIds.reset();\r
+ }\r
+ if (tempDistanceSq <= furthestDistanceSq) {\r
+ nearestIds.add(n.ids[i]);\r
+ } \r
+ } else { // for index nodes, only go into them if they potentially could have\r
+ // a rectangle nearer than actualNearest\r
+ if (tempDistanceSq <= furthestDistanceSq) {\r
+ // search the child node\r
+ furthestDistanceSq = nearest(p, getNode(n.ids[i]), furthestDistanceSq);\r
+ }\r
+ }\r
+ }\r
+ return furthestDistanceSq;\r
+ }\r
+ \r
+ /** \r
+ * Recursively searches the tree for all intersecting entries.\r
+ * Immediately calls execute() on the passed IntProcedure when \r
+ * a matching entry is found.\r
+ * \r
+ * TODO rewrite this to be non-recursive? Make sure it\r
+ * doesn't slow it down.\r
+ */\r
+ private boolean intersects(Rectangle r, TIntProcedure v, Node n) {\r
+ for (int i = 0; i < n.entryCount; i++) {\r
+ if (Rectangle.intersects(r.minX, r.minY, r.maxX, r.maxY, n.entriesMinX[i], n.entriesMinY[i], n.entriesMaxX[i], n.entriesMaxY[i])) {\r
+ if (n.isLeaf()) {\r
+ if (!v.execute(n.ids[i])) {\r
+ return false;\r
+ }\r
+ } else {\r
+ Node childNode = getNode(n.ids[i]);\r
+ if (!intersects(r, v, childNode)) {\r
+ return false;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Used by delete(). Ensures that all nodes from the passed node\r
+ * up to the root have the minimum number of entries.\r
+ * \r
+ * Note that the parent and parentEntry stacks are expected to\r
+ * contain the nodeIds of all parents up to the root.\r
+ */\r
+ private void condenseTree(Node l) {\r
+ // CT1 [Initialize] Set n=l. Set the list of eliminated\r
+ // nodes to be empty.\r
+ Node n = l;\r
+ Node parent = null;\r
+ int parentEntry = 0;\r
+ \r
+ TIntStack eliminatedNodeIds = new TIntStack();\r
+ \r
+ // CT2 [Find parent entry] If N is the root, go to CT6. Otherwise \r
+ // let P be the parent of N, and let En be N's entry in P \r
+ while (n.level != treeHeight) {\r
+ parent = getNode(parents.pop());\r
+ parentEntry = parentsEntry.pop();\r
+ \r
+ // CT3 [Eliminiate under-full node] If N has too few entries,\r
+ // delete En from P and add N to the list of eliminated nodes\r
+ if (n.entryCount < minNodeEntries) {\r
+ parent.deleteEntry(parentEntry);\r
+ eliminatedNodeIds.push(n.nodeId);\r
+ } else {\r
+ // CT4 [Adjust covering rectangle] If N has not been eliminated,\r
+ // adjust EnI to tightly contain all entries in N\r
+ if (n.mbrMinX != parent.entriesMinX[parentEntry] ||\r
+ n.mbrMinY != parent.entriesMinY[parentEntry] ||\r
+ n.mbrMaxX != parent.entriesMaxX[parentEntry] ||\r
+ n.mbrMaxY != parent.entriesMaxY[parentEntry]) {\r
+ float deletedMinX = parent.entriesMinX[parentEntry];\r
+ float deletedMinY = parent.entriesMinY[parentEntry];\r
+ float deletedMaxX = parent.entriesMaxX[parentEntry];\r
+ float deletedMaxY = parent.entriesMaxY[parentEntry];\r
+ parent.entriesMinX[parentEntry] = n.mbrMinX;\r
+ parent.entriesMinY[parentEntry] = n.mbrMinY;\r
+ parent.entriesMaxX[parentEntry] = n.mbrMaxX;\r
+ parent.entriesMaxY[parentEntry] = n.mbrMaxY;\r
+ parent.recalculateMBRIfInfluencedBy(deletedMinX, deletedMinY, deletedMaxX, deletedMaxY);\r
+ }\r
+ }\r
+ // CT5 [Move up one level in tree] Set N=P and repeat from CT2\r
+ n = parent;\r
+ }\r
+ \r
+ // CT6 [Reinsert orphaned entries] Reinsert all entries of nodes in set Q.\r
+ // Entries from eliminated leaf nodes are reinserted in tree leaves as in \r
+ // Insert(), but entries from higher level nodes must be placed higher in \r
+ // the tree, so that leaves of their dependent subtrees will be on the same\r
+ // level as leaves of the main tree\r
+ while (eliminatedNodeIds.size() > 0) {\r
+ Node e = getNode(eliminatedNodeIds.pop());\r
+ for (int j = 0; j < e.entryCount; j++) {\r
+ add(e.entriesMinX[j], e.entriesMinY[j], e.entriesMaxX[j], e.entriesMaxY[j], e.ids[j], e.level); \r
+ e.ids[j] = -1;\r
+ }\r
+ e.entryCount = 0;\r
+ deletedNodeIds.push(e.nodeId);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Used by add(). Chooses a leaf to add the rectangle to.\r
+ */\r
+ private Node chooseNode(float minX, float minY, float maxX, float maxY, int level) {\r
+ // CL1 [Initialize] Set N to be the root node\r
+ Node n = getNode(rootNodeId);\r
+ parents.reset();\r
+ parentsEntry.reset();\r
+ \r
+ // CL2 [Leaf check] If N is a leaf, return N\r
+ while (true) {\r
+ if (n == null) {\r
+ log.error("Could not get root node (" + rootNodeId + ")"); \r
+ }\r
+ \r
+ if (n.level == level) {\r
+ return n;\r
+ }\r
+ \r
+ // CL3 [Choose subtree] If N is not at the desired level, let F be the entry in N \r
+ // whose rectangle FI needs least enlargement to include EI. Resolve\r
+ // ties by choosing the entry with the rectangle of smaller area.\r
+ float leastEnlargement = Rectangle.enlargement(n.entriesMinX[0], n.entriesMinY[0], n.entriesMaxX[0], n.entriesMaxY[0],\r
+ minX, minY, maxX, maxY);\r
+ int index = 0; // index of rectangle in subtree\r
+ for (int i = 1; i < n.entryCount; i++) {\r
+ float tempMinX = n.entriesMinX[i];\r
+ float tempMinY = n.entriesMinY[i];\r
+ float tempMaxX = n.entriesMaxX[i];\r
+ float tempMaxY = n.entriesMaxY[i];\r
+ float tempEnlargement = Rectangle.enlargement(tempMinX, tempMinY, tempMaxX, tempMaxY, \r
+ minX, minY, maxX, maxY);\r
+ if ((tempEnlargement < leastEnlargement) ||\r
+ ((tempEnlargement == leastEnlargement) && \r
+ (Rectangle.area(tempMinX, tempMinY, tempMaxX, tempMaxY) < \r
+ Rectangle.area(n.entriesMinX[index], n.entriesMinY[index], n.entriesMaxX[index], n.entriesMaxY[index])))) {\r
+ index = i;\r
+ leastEnlargement = tempEnlargement;\r
+ }\r
+ }\r
+ \r
+ parents.push(n.nodeId);\r
+ parentsEntry.push(index);\r
+ \r
+ // CL4 [Descend until a leaf is reached] Set N to be the child node \r
+ // pointed to by Fp and repeat from CL2\r
+ n = getNode(n.ids[index]);\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Ascend from a leaf node L to the root, adjusting covering rectangles and\r
+ * propagating node splits as necessary.\r
+ */\r
+ private Node adjustTree(Node n, Node nn) {\r
+ // AT1 [Initialize] Set N=L. If L was split previously, set NN to be \r
+ // the resulting second node.\r
+ \r
+ // AT2 [Check if done] If N is the root, stop\r
+ while (n.level != treeHeight) {\r
+ \r
+ // AT3 [Adjust covering rectangle in parent entry] Let P be the parent \r
+ // node of N, and let En be N's entry in P. Adjust EnI so that it tightly\r
+ // encloses all entry rectangles in N.\r
+ Node parent = getNode(parents.pop());\r
+ int entry = parentsEntry.pop(); \r
+ \r
+ if (parent.ids[entry] != n.nodeId) {\r
+ log.error("Error: entry " + entry + " in node " + \r
+ parent.nodeId + " should point to node " + \r
+ n.nodeId + "; actually points to node " + parent.ids[entry]);\r
+ }\r
+ \r
+ if (parent.entriesMinX[entry] != n.mbrMinX ||\r
+ parent.entriesMinY[entry] != n.mbrMinY ||\r
+ parent.entriesMaxX[entry] != n.mbrMaxX ||\r
+ parent.entriesMaxY[entry] != n.mbrMaxY) {\r
+ \r
+ parent.entriesMinX[entry] = n.mbrMinX;\r
+ parent.entriesMinY[entry] = n.mbrMinY;\r
+ parent.entriesMaxX[entry] = n.mbrMaxX;\r
+ parent.entriesMaxY[entry] = n.mbrMaxY;\r
+\r
+ parent.recalculateMBR();\r
+ }\r
+ \r
+ // AT4 [Propagate node split upward] If N has a partner NN resulting from \r
+ // an earlier split, create a new entry Enn with Ennp pointing to NN and \r
+ // Enni enclosing all rectangles in NN. Add Enn to P if there is room. \r
+ // Otherwise, invoke splitNode to produce P and PP containing Enn and\r
+ // all P's old entries.\r
+ Node newNode = null;\r
+ if (nn != null) {\r
+ if (parent.entryCount < maxNodeEntries) {\r
+ parent.addEntry(nn.mbrMinX, nn.mbrMinY, nn.mbrMaxX, nn.mbrMaxY, nn.nodeId);\r
+ } else {\r
+ newNode = splitNode(parent, nn.mbrMinX, nn.mbrMinY, nn.mbrMaxX, nn.mbrMaxY, nn.nodeId);\r
+ }\r
+ }\r
+ \r
+ // AT5 [Move up to next level] Set N = P and set NN = PP if a split \r
+ // occurred. Repeat from AT2\r
+ n = parent;\r
+ nn = newNode;\r
+ \r
+ parent = null;\r
+ newNode = null;\r
+ }\r
+ \r
+ return nn;\r
+ }\r
+ \r
+ \r
+ /**\r
+ * Check the consistency of the tree.\r
+ * \r
+ * @return false if an inconsistency is detected, true otherwise.\r
+ */\r
+ public boolean checkConsistency() {\r
+ return checkConsistency(rootNodeId, treeHeight, null);\r
+ }\r
+ \r
+ private boolean checkConsistency(int nodeId, int expectedLevel, Rectangle expectedMBR) {\r
+ // go through the tree, and check that the internal data structures of \r
+ // the tree are not corrupted. \r
+ Node n = getNode(nodeId);\r
+ \r
+ if (n == null) {\r
+ log.error("Error: Could not read node " + nodeId);\r
+ return false;\r
+ }\r
+ \r
+ // if tree is empty, then there should be exactly one node, at level 1\r
+ // TODO: also check the MBR is as for a new node\r
+ if (nodeId == rootNodeId && size() == 0) {\r
+ if (n.level != 1) {\r
+ log.error("Error: tree is empty but root node is not at level 1");\r
+ return false;\r
+ }\r
+ }\r
+ \r
+ if (n.level != expectedLevel) {\r
+ log.error("Error: Node " + nodeId + ", expected level " + expectedLevel + ", actual level " + n.level);\r
+ return false;\r
+ }\r
+ \r
+ Rectangle calculatedMBR = calculateMBR(n);\r
+ Rectangle actualMBR = new Rectangle();\r
+ actualMBR.minX = n.mbrMinX;\r
+ actualMBR.minY = n.mbrMinY;\r
+ actualMBR.maxX = n.mbrMaxX;\r
+ actualMBR.maxY = n.mbrMaxY;\r
+ if (!actualMBR.equals(calculatedMBR)) {\r
+ log.error("Error: Node " + nodeId + ", calculated MBR does not equal stored MBR");\r
+ if (actualMBR.minX != n.mbrMinX) log.error(" actualMinX=" + actualMBR.minX + ", calc=" + calculatedMBR.minX);\r
+ if (actualMBR.minY != n.mbrMinY) log.error(" actualMinY=" + actualMBR.minY + ", calc=" + calculatedMBR.minY);\r
+ if (actualMBR.maxX != n.mbrMaxX) log.error(" actualMaxX=" + actualMBR.maxX + ", calc=" + calculatedMBR.maxX);\r
+ if (actualMBR.maxY != n.mbrMaxY) log.error(" actualMaxY=" + actualMBR.maxY + ", calc=" + calculatedMBR.maxY);\r
+ return false;\r
+ }\r
+ \r
+ if (expectedMBR != null && !actualMBR.equals(expectedMBR)) {\r
+ log.error("Error: Node " + nodeId + ", expected MBR (from parent) does not equal stored MBR");\r
+ return false;\r
+ }\r
+ \r
+ // Check for corruption where a parent entry is the same object as the child MBR\r
+ if (expectedMBR != null && actualMBR.sameObject(expectedMBR)) {\r
+ log.error("Error: Node " + nodeId + " MBR using same rectangle object as parent's entry");\r
+ return false;\r
+ }\r
+ \r
+ for (int i = 0; i < n.entryCount; i++) {\r
+ if (n.ids[i] == -1) {\r
+ log.error("Error: Node " + nodeId + ", Entry " + i + " is null");\r
+ return false;\r
+ } \r
+ \r
+ if (n.level > 1) { // if not a leaf\r
+ if (!checkConsistency(n.ids[i], n.level - 1, new Rectangle(n.entriesMinX[i], n.entriesMinY[i], n.entriesMaxX[i], n.entriesMaxY[i]))) {\r
+ return false;\r
+ }\r
+ } \r
+ }\r
+ return true;\r
+ }\r
+ \r
+ /**\r
+ * Given a node object, calculate the node MBR from it's entries.\r
+ * Used in consistency checking\r
+ */\r
+ private Rectangle calculateMBR(Node n) {\r
+ Rectangle mbr = new Rectangle();\r
+ \r
+ for (int i = 0; i < n.entryCount; i++) {\r
+ if (n.entriesMinX[i] < mbr.minX) mbr.minX = n.entriesMinX[i];\r
+ if (n.entriesMinY[i] < mbr.minY) mbr.minY = n.entriesMinY[i];\r
+ if (n.entriesMaxX[i] > mbr.maxX) mbr.maxX = n.entriesMaxX[i];\r
+ if (n.entriesMaxY[i] > mbr.maxY) mbr.maxY = n.entriesMaxY[i];\r
+ }\r
+ return mbr; \r
+ }\r
+}\r