R-Trees
An R-Tree is a dynamic height-balanced tree structure designed to index multi-dimensional spatial objects. Unlike KD-trees or Octrees, the bounding regions in an R-Tree can overlap.
RTreeNode:
1class RTreeNode:2 def __init__(self, is_leaf=False):3 self.is_leaf = is_leaf4 self.mbr = MinimumBoundingRectangle() # MBR enclosing all children5 self.children = [] # List of RTreeNodes or spatial objects67 def get_enclosing_mbr(self) -> 'MinimumBoundingRectangle':8 if not self.children:9 return MinimumBoundingRectangle()10 union_mbr = self.children[0].mbr.clone()11 for child in self.children[1:]:12 union_mbr.expand_to_include(child.mbr)13 return union_mbr
R-Trees insert elements dynamically. To insert a new object, the algorithm recursively traverses down by picking the child bounding box that requires the least area enlargement.
ChooseLeaf(node, new_object):
1def choose_leaf(node, new_object) -> RTreeNode:2 if node.is_leaf:3 return node45 best_child = None6 min_enlargement = float('inf')78 for child in node.children:9 # Calculate area expansion if child MBR expands to enclose new_object10 area_before = child.mbr.area()11 area_after = child.mbr.union(new_object.mbr).area()12 enlargement = area_after - area_before1314 if enlargement < min_enlargement:15 min_enlargement = enlargement16 best_child = child1718 return choose_leaf(best_child, new_object)
R-Tree search processes overlaps recursively. If the query region intersects a node's Minimum Bounding Rectangle, we descend; otherwise, the entire branch is pruned.
SearchRTree(node, query_rect):
1def search_rtree(node, query_rect, results):2 # Base Case: check if node's MBR intersects the query rectangle3 if not node.mbr.intersects(query_rect):4 return # Prune this branch immediately56 if node.is_leaf:7 for obj in node.children:8 if obj.mbr.intersects(query_rect):9 results.append(obj)10 else:11 for child in node.children:12 search_rtree(child, query_rect, results)
Deletion in R-Trees handles node underflow (fewer than $m$ entries) differently than B-Trees. Instead of adjacent borrowing, underflowed R-Tree nodes are deleted entirely, and their remaining elements are re-inserted into the tree from the root. This forces global reconstruction, keeping bounding boxes tightly bound.
CondenseTree(node, reinsert_list):
1def delete_rtree(node, entry, reinsert_list=None) -> bool:2 if reinsert_list is None:3 reinsert_list = []45 # 1. Find leaf and remove element6 if node.is_leaf:7 if entry in node.children:8 node.children.remove(entry)9 return True10 return False1112 # 2. Traverse down child nodes13 for child in node.children:14 if overlaps(child.mbr, entry.mbr):15 if delete_rtree(child, entry, reinsert_list):16 # Handle underflow: node drops below minimum capacity m17 if len(child.children) < m:18 node.children.remove(child)19 reinsert_list.extend(child.get_all_elements())20 return True2122 return False