Skip to main content

R-Tree

SpaceTime
AccessLookupInsertionDeletion
O(n)O(log n)O(log n)O(n log n)O(log n)

Definition​

R-Tree is a spatial index structure used in computer science and geographic information systems to efficiently organize and search spatial objects, facilitating operations like finding nearby locations or detecting overlapping regions.

Simplified

R-Tree is like a smart index for digital maps, organizing locations into boxes for efficient searches. It helps quickly find specific places, like parks or restaurants, by guiding you to relevant areas rather than scanning the entire map. In essence, it's an efficient guide for locating things on a digital map

Practice​

AspectPseudo Code
Calculate Bounding Box
_calculate_bounding_box(points):
min_x = min(p[0] for p in points)
min_y = min(p[1] for p in points)
max_x = max(p[0] for p in points)
max_y = max(p[1] for p in points)
return (min_x, min_y, max_x, max_y)
Calculate Expanded Bbox
_calculate_expanded_bbox(old_bbox, point):
min_x, min_y, max_x, max_y = old_bbox
new_min_x = min(min_x, point[0])
new_min_y = min(min_y, point[1])
new_max_x = max(max_x, point[0])
new_max_y = max(max_y, point[1])
return (new_min_x, new_min_y, new_max_x, new_max_y)
Point In Bbox
_point_in_bbox(point, bbox):
return bbox[0] <= point[0] <= bbox[2] and bbox[1] <= point[1] <= bbox[3]
Distance
_distance(point1, point2):
return sqrt((point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2)
Bbox Fully Contained
_bbox_fully_contained(bbox1, bbox2):
return bbox1[0] >= bbox2[0] and bbox1[1] >= bbox2[1] and bbox1[2] <= bbox2[2] and bbox1[3] <= bbox2[3]
Bbox Intersects Query
_bbox_intersects_query(bbox, query_bbox):
return not (bbox[2] < query_bbox[0] or
bbox[0] > query_bbox[2] or
bbox[3] < query_bbox[1] or
bbox[1] > query_bbox[3])
Split
_split(node):
split_point = len(node.children) // 2
new_node = RTreeNode(is_leaf=node.is_leaf)
new_node.children = node.children.split(split_point, node.children.length)
node.children = node.children.split(0, split_point)

node.bounding_box = _calculate_bounding_box(node.children)
new_node.bounding_box = _calculate_bounding_box(new_node.children)

if node == root:
new_root = RTreeNode(is_leaf=False)
new_root.children = [node, new_node]
new_root.bounding_box = _calculate_bounding_box(new_root.children)
root = new_root
Calculate Area Increase
_calculate_area_increase(old_bbox, new_bbox):
old_area = (old_bbox[2] - old_bbox[0]) * (old_bbox[3] - old_bbox[1])
new_area = (new_bbox[2] - new_bbox[0]) * (new_bbox[3] - new_bbox[1])
return new_area - old_area
Insertion
insert(point):
_insert_recursive(root, point)

_insert_recursive(node, point):
if node.is_leaf:
node.children.append(point)
if len(node.children) > max_children:
_split(node)
else:
min_increase = ∞
best_child = ΓΈ

for child in node.children:
child_bbox = _calculate_bounding_box(child)
expanded_bbox = _calculate_expanded_bbox(child_bbox, point)
increase = _calculate_area_increase(child_bbox, expanded_bbox)

if increase < min_increase:
min_increase=increase
best_child=child

_insert_recursive(best_child, point)
Search Recursive
_search_recursive(node, query_bbox, result):
if node.is_leaf:
for point in node.children:
if _point_in_bbox(point, query_bbox):
result.append(point)
else:
for child in node.children:
child_bbox = _calculate_bounding_box(child)
if _bbox_intersects_query(child_bbox, query_bbox):
_search_recursive(child, query_bbox, result)
Nearest Neighbors Recursive
nearest_neighbors_recursive(node, query_point, k, result):
if node.is_leaf:
for point in node.children:
_update_nearest_neighbors(query_point, point, k, result)
else:
sorted_children = sorted(node.children, key=lambda child: _distance(query_point, _calculate_bounding_box(child)))

for child in sorted_children:
child_bbox = _calculate_bounding_box(child)
if _distance(query_point, child_bbox) < result[-1][0]:
_nearest_neighbors_recursive(child, query_point, k, result)

_update_nearest_neighbors(query_point, point, k, result):
distance = _distance(query_point, point)

if len(result) < k:
result.append((distance, point))
result.sort(key=lambda x: x[0], reverse=True)
elif distance < result[0][0]:
result[0] = (distance, point)
result.sort(key=lambda x: x[0], reverse=True)
Delete Recursive
_delete_recursive(node, point):
if node.is_leaf:
node.children = [p for p in node.children if p != point]
else:
for child in node.children:
child_bbox = _calculate_bounding_box(child)
if _point_in_bbox(point, child_bbox):
_delete_recursive(child, point)
break
node.bounding_box = _calculate_bounding_box(node.children)
Update
update(old_point, new_point):
_recursive_update(root, old_point, new_point)

_recursive_update(node, old_point, new_point):
if node.is_leaf:
for i, child in enumerate(node.children):
if child.bounding_box == old_point:
node.children[i].bounding_box = new_point
return
else:
for child in node.children:
if _intersects(child.bounding_box, old_point):
_recursive_update(child, old_point, new_point)

_intersects(box1, box2):
return not (box1[2] < box2[0] or
box1[0] > box2[2] or
box1[3] < box2[1] or
box1[1] > box2[3])
Range Query
range_query(query_bbox):
result = []
_range_query_recursive(root, query_bbox, result)
return result

_range_query_recursive(node, query_bbox, result):
if node.is_leaf:
for point in node.children:
if _point_in_bbox(point, query_bbox):
result.append(point)
else:
for child in node.children:
child_bbox = _calculate_bounding_box(child)
if _bbox_intersects_query(child_bbox, query_bbox):
_range_query_recursive(child, query_bbox, result)
Window Query Recursive
window_query_recursive(node, query_bbox, result):
if node.is_leaf:
for point in node.children:
if _point_in_bbox(point, query_bbox):
result.append(point)
else:
for child in node.children:
child_bbox = _calculate_bounding_box(child)
if _bbox_fully_contained(child_bbox, query_bbox):
window_query_recursive(child, query_bbox, result)
kNN Query
knn_query(query_point, k):
result = []

_knn_search(root, query_point, k)

result.sort_based_on_second_element()
return result.split(0,k)

_knn_search(node, query_point, k):
if node.is_leaf:
for child in node.children:
result.append((child, _distance(query_point, child.bounding_box)))
else:
distances = [(child, _calculate_min_distance(query_point, child.bounding_box)) for child in node.children]
distances.sort_based_on_second_element()

for child, _ in distances[:k]:
knn_search(child, query_point, k)

_calculate_min_distance(point, bounding_box):
min_distance = 0
for i in range(len(point)):
if point[i] < bounding_box[i]:
min_distance += (bounding_box[i] - point[i])**2
elif point[i] > bounding_box[i + 2]:
min_distance += (point[i] - bounding_box[i + 2])**2
return sqrt(min_distance)
Traverse
traverse(visit_func):
recursive_traverse(root, visit_func)

recursive_traverse(node, visit_func):
visit_func(node)
if not node.is_leaf:
for child in node.children:
recursive_traverse(child, visit_func)

print_node_info(node, depth):
indentation = " " * depth
if node.is_leaf:
print(indentation + "Leaf Node Bounding Box:", node.bounding_box)
else:
print(indentation + "Internal Node Bounding Box:", node.bounding_box)