/** Adds a new entry at a specified level in the tree */ private void add(Rectangle r, int id, int level) { // I1 [Find position for new record] Invoke ChooseLeaf to select a // leaf node L in which to place r Node n = chooseNode(r, level); Node newLeaf = null; // I2 [Add record to leaf node] If L has room for another entry, // install E. Otherwise invoke SplitNode to obtain L and LL containing // E and all the old entries of L if (n.entryCount < maxNodeEntries) { n.addEntryNoCopy(r, id); } else { newLeaf = splitNode(n, r, id); } // I3 [Propagate changes upwards] Invoke AdjustTree on L, also passing // LL // if a split was performed Node newNode = adjustTree(n, newLeaf); // I4 [Grow tree taller] If node split propagation caused the root to // split, create a new root whose children are the two resulting nodes. if (newNode != null) { int oldRootNodeId = rootNodeId; Node oldRoot = getNode(oldRootNodeId); rootNodeId = getNextNodeId(); treeHeight++; Node root = new Node(rootNodeId, treeHeight, maxNodeEntries); root.addEntry(newNode.mbr, newNode.nodeId); root.addEntry(oldRoot.mbr, oldRoot.nodeId); nodeMap.put(rootNodeId, root); } if (INTERNAL_CONSISTENCY_CHECKING) { checkConsistency(rootNodeId, treeHeight, null); } }
/** * Pick the next entry to be assigned to a group during a node split. * * <p>[Determine cost of putting each entry in each group] For each entry not yet in a group, * calculate the area increase required in the covering rectangles of each group */ private int pickNext(Node n, Node newNode) { float maxDifference = Float.NEGATIVE_INFINITY; int next = 0; int nextGroup = 0; maxDifference = Float.NEGATIVE_INFINITY; if (log.isDebugEnabled()) { log.debug("pickNext()"); } for (int i = 0; i < maxNodeEntries; i++) { if (entryStatus[i] == ENTRY_STATUS_UNASSIGNED) { if (n.entries[i] == null) { log.error("Error: Node " + n.nodeId + ", entry " + i + " is null"); } float nIncrease = n.mbr.enlargement(n.entries[i]); float newNodeIncrease = newNode.mbr.enlargement(n.entries[i]); float difference = Math.abs(nIncrease - newNodeIncrease); if (difference > maxDifference) { next = i; if (nIncrease < newNodeIncrease) { nextGroup = 0; } else if (newNodeIncrease < nIncrease) { nextGroup = 1; } else if (n.mbr.area() < newNode.mbr.area()) { nextGroup = 0; } else if (newNode.mbr.area() < n.mbr.area()) { nextGroup = 1; } else if (newNode.entryCount < maxNodeEntries / 2) { nextGroup = 0; } else { nextGroup = 1; } maxDifference = difference; } if (log.isDebugEnabled()) { log.debug( "Entry " + i + " group0 increase = " + nIncrease + ", group1 increase = " + newNodeIncrease + ", diff = " + difference + ", MaxDiff = " + maxDifference + " (entry " + next + ")"); } } } entryStatus[next] = ENTRY_STATUS_ASSIGNED; if (nextGroup == 0) { n.mbr.add(n.entries[next]); n.entryCount++; } else { // move to new node. newNode.addEntryNoCopy(n.entries[next], n.ids[next]); n.entries[next] = null; } return next; }
/** * Pick the seeds used to split a node. Select two entries to be the first elements of the groups */ private void pickSeeds(Node n, Rectangle newRect, int newId, Node newNode) { // Find extreme rectangles along all dimension. Along each dimension, // find the entry whose rectangle has the highest low side, and the one // with the lowest high side. Record the separation. float maxNormalizedSeparation = 0; int highestLowIndex = 0; int lowestHighIndex = 0; // for the purposes of picking seeds, take the MBR of the node to include // the new rectangle aswell. n.mbr.add(newRect); if (log.isDebugEnabled()) { log.debug("pickSeeds(): NodeId = " + n.nodeId + ", newRect = " + newRect); } for (int d = 0; d < Rectangle.DIMENSIONS; d++) { float tempHighestLow = newRect.min[d]; int tempHighestLowIndex = -1; // -1 indicates the new rectangle is the seed float tempLowestHigh = newRect.max[d]; int tempLowestHighIndex = -1; for (int i = 0; i < n.entryCount; i++) { float tempLow = n.entries[i].min[d]; if (tempLow >= tempHighestLow) { tempHighestLow = tempLow; tempHighestLowIndex = i; } else { // ensure that the same index cannot be both lowestHigh and highestLow float tempHigh = n.entries[i].max[d]; if (tempHigh <= tempLowestHigh) { tempLowestHigh = tempHigh; tempLowestHighIndex = i; } } // PS2 [Adjust for shape of the rectangle cluster] Normalize the separations // by dividing by the widths of the entire set along the corresponding // dimension float normalizedSeparation = (tempHighestLow - tempLowestHigh) / (n.mbr.max[d] - n.mbr.min[d]); if (normalizedSeparation > 1 || normalizedSeparation < -1) { log.error("Invalid normalized separation"); } if (log.isDebugEnabled()) { log.debug( "Entry " + i + ", dimension " + d + ": HighestLow = " + tempHighestLow + " (index " + tempHighestLowIndex + ")" + ", LowestHigh = " + tempLowestHigh + " (index " + tempLowestHighIndex + ", NormalizedSeparation = " + normalizedSeparation); } // PS3 [Select the most extreme pair] Choose the pair with the greatest // normalized separation along any dimension. if (normalizedSeparation > maxNormalizedSeparation) { maxNormalizedSeparation = normalizedSeparation; highestLowIndex = tempHighestLowIndex; lowestHighIndex = tempLowestHighIndex; } } } // highestLowIndex is the seed for the new node. if (highestLowIndex == -1) { newNode.addEntry(newRect, newId); } else { newNode.addEntryNoCopy(n.entries[highestLowIndex], n.ids[highestLowIndex]); n.entries[highestLowIndex] = null; // move the new rectangle into the space vacated by the seed for the new node n.entries[highestLowIndex] = newRect; n.ids[highestLowIndex] = newId; } // lowestHighIndex is the seed for the original node. if (lowestHighIndex == -1) { lowestHighIndex = highestLowIndex; } entryStatus[lowestHighIndex] = ENTRY_STATUS_ASSIGNED; n.entryCount = 1; n.mbr.set(n.entries[lowestHighIndex].min, n.entries[lowestHighIndex].max); }
/** * Split a node. Algorithm is taken pretty much verbatim from Guttman's original paper. * * @return new node object. */ private Node splitNode(Node n, Rectangle newRect, int newId) { // [Pick first entry for each group] Apply algorithm pickSeeds to // choose two entries to be the first elements of the groups. Assign // each to a group. // debug code float initialArea = 0; if (log.isDebugEnabled()) { Rectangle union = n.mbr.union(newRect); initialArea = union.area(); } System.arraycopy(initialEntryStatus, 0, entryStatus, 0, maxNodeEntries); Node newNode = null; newNode = new Node(getNextNodeId(), n.level, maxNodeEntries); nodeMap.put(newNode.nodeId, newNode); pickSeeds(n, newRect, newId, newNode); // this also sets the entryCount to 1 // [Check if done] If all entries have been assigned, stop. If one // group has so few entries that all the rest must be assigned to it in // order for it to have the minimum number m, assign them and stop. while (n.entryCount + newNode.entryCount < maxNodeEntries + 1) { if (maxNodeEntries + 1 - newNode.entryCount == minNodeEntries) { // assign all remaining entries to original node for (int i = 0; i < maxNodeEntries; i++) { if (entryStatus[i] == ENTRY_STATUS_UNASSIGNED) { entryStatus[i] = ENTRY_STATUS_ASSIGNED; n.mbr.add(n.entries[i]); n.entryCount++; } } break; } if (maxNodeEntries + 1 - n.entryCount == minNodeEntries) { // assign all remaining entries to new node for (int i = 0; i < maxNodeEntries; i++) { if (entryStatus[i] == ENTRY_STATUS_UNASSIGNED) { entryStatus[i] = ENTRY_STATUS_ASSIGNED; newNode.addEntryNoCopy(n.entries[i], n.ids[i]); n.entries[i] = null; } } break; } // [Select entry to assign] Invoke algorithm pickNext to choose the // next entry to assign. Add it to the group whose covering rectangle // will have to be enlarged least to accommodate it. Resolve ties // by adding the entry to the group with smaller area, then to the // the one with fewer entries, then to either. Repeat from S2 pickNext(n, newNode); } n.reorganize(this); // check that the MBR stored for each node is correct. if (INTERNAL_CONSISTENCY_CHECKING) { if (!n.mbr.equals(calculateMBR(n))) { log.error("Error: splitNode old node MBR wrong"); } if (!newNode.mbr.equals(calculateMBR(newNode))) { log.error("Error: splitNode new node MBR wrong"); } } // debug code if (log.isDebugEnabled()) { float newArea = n.mbr.area() + newNode.mbr.area(); float percentageIncrease = (100 * (newArea - initialArea)) / initialArea; log.debug("Node " + n.nodeId + " split. New area increased by " + percentageIncrease + "%"); } return newNode; }