/// <summary> /// Used by add(). Chooses a leaf to add the rectangle to. /// </summary> private Node <T> chooseNode(Rectangle r, int level) { // CL1 [Initialize] Set N to be the root node Node <T> n = getNode(rootNodeId); parents.Clear(); parentsEntry.Clear(); // CL2 [Leaf check] If N is a leaf, return N while (true) { if (n == null) { Debug.WriteLine($"Could not get root Node<T> ({rootNodeId})"); } if (n.level == level) { return(n); } // CL3 [Choose subtree] If N is not at the desired level, let F be the entry in N // whose rectangle FI needs least enlargement to include EI. Resolve // ties by choosing the entry with the rectangle of smaller area. float leastEnlargement = n.getEntry(0).enlargement(r); int index = 0; // index of rectangle in subtree for (int i = 1; i < n.entryCount; i++) { Rectangle tempRectangle = n.getEntry(i); float tempEnlargement = tempRectangle.enlargement(r); if ((tempEnlargement < leastEnlargement) || ((tempEnlargement == leastEnlargement) && (tempRectangle.area() < n.getEntry(index).area()))) { index = i; leastEnlargement = tempEnlargement; } } parents.Push(n.nodeId); parentsEntry.Push(index); // CL4 [Descend until a leaf is reached] Set N to be the child Node<T> // pointed to by Fp and repeat from CL2 n = getNode(n.ids[index]); } }
/// <summary> /// Split a node. Algorithm is taken pretty much verbatim from /// Guttman's original paper. /// </summary> /// <param name="n"></param> /// <param name="newRect"></param> /// <param name="newId"></param> /// <returns>return new Node<T> object.</returns> private Node <T> splitNode(Node <T> 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 #if DEBUG float initialArea = 0; Rectangle union = n.mbr.union(newRect); initialArea = union.area(); #endif System.Array.Copy(initialEntryStatus, 0, entryStatus, 0, maxNodeEntries); Node <T> newNode = null; newNode = new Node <T>(getNextNodeId(), n.level, maxNodeEntries); nodeMap.Add(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<T> is correct. if (INTERNAL_CONSISTENCY_CHECKING) { if (!n.mbr.Equals(calculateMBR(n))) { Debug.WriteLine("Error: splitNode old Node<T> MBR wrong"); } if (!newNode.mbr.Equals(calculateMBR(newNode))) { Debug.WriteLine("Error: splitNode new Node<T> MBR wrong"); } } // debug code #if DEBUG float newArea = n.mbr.area() + newNode.mbr.area(); float percentageIncrease = (100 * (newArea - initialArea)) / initialArea; Debug.WriteLine($"Node { n.nodeId} split. New area increased by {percentageIncrease}%"); #endif return(newNode); }