Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

After this page, you should be able to:

Builds on: A PR tree partitions cells; an R-tree groups object bounds instead.

Code languages: Python defines the explanations, figures and self-checks. Use the language guide for native conventions and dependencies. Complete runnable topic examples: Python · JavaScript · Julia.

JavaScript points are arrays compared by same_point; Julia points are value tuples. Julia coordinate/child indices are one-based (x=1, y=2; SW/SE/NW/NE=1/2/3/4), while depth still starts at zero. R-tree orientation uses exact big-integer arithmetic for the supplied integer-coordinate geometry; nearest distances remain floating calculations. General R-tree update code is still conceptual in every language.

Follow the five operations

OperationRead here
Construction and insertionRepresentation below, then least enlargement and splitting
Exact object lookupEndpoint equality after candidate filtering
Range searchMBR candidates and exact intersection
Nearest neighbourBound ordering and exact segment distance
Deletion and costsCondensation, reinsertion and operation summary

A parent bound encloses every descendant object

A line or polygon can cross many fixed partition cells. An R-tree instead groups objects and stores a minimum bounding rectangle (MBR) for each object or child group. Every parent entry must enclose all bounds below that entry. Internal entries refer to child nodes; leaf entries associate object bounds with record identifiers.

An ordinary R-tree is a balanced multiway structure with all leaves at the same depth and configured node-occupancy limits. Its construction and splitting policy tries to keep bounds useful. This reading starts with a given small tree; it does not implement general insertion, balancing or node splitting.

Sibling rectangles may overlap

R-tree sibling bounds are enclosures, not a disjoint partition of the plane. A query can intersect several siblings, requiring more than one branch. Tree balance limits path length but does not guarantee that a query follows only one path.

Two child-group bounding rectangles overlap around a query, so both children must be examined; the corresponding tree groups five line records.

The following original example stores five line segments. It uses a different object/query example from the shared point comparison so that filtering and exact geometry can be distinguished.

Build the bounds of a given two-leaf tree

Python
JavaScript
Julia
segments = {
    "L1": ((0, 0), (10, 10)),
    "L2": ((0, 7), (6, 7)),
    "L3": ((8, 0), (10, 2)),
    "L4": ((3, 5), (3, 9)),
    "L5": ((5, 6), (9, 8)),
}
line_query = (2, 6, 4, 8)

def segment_bounds(segment):
    a, b = segment
    return (min(a[0], b[0]), min(a[1], b[1]),
            max(a[0], b[0]), max(a[1], b[1]))

def enclosing_bounds(boxes):
    boxes = list(boxes)  # requires a nonempty collection
    return (min(b[0] for b in boxes), min(b[1] for b in boxes),
            max(b[2] for b in boxes), max(b[3] for b in boxes))

class RNode:
    def __init__(self, entries, leaf):
        self.entries = list(entries)  # (enclosing_box, child_or_id)
        self.leaf = leaf

line_boxes = {key: segment_bounds(value) for key, value in segments.items()}
group_u = RNode([(line_boxes[key], key) for key in ["L1", "L2"]], True)
group_v = RNode([(line_boxes[key], key) for key in ["L3", "L4", "L5"]], True)
r_root = RNode([
    (enclosing_bounds(box for box, _ in group_u.entries), group_u),
    (enclosing_bounds(box for box, _ in group_v.entries), group_v),
], False)

Group U encloses (0,0,10,10); group V encloses (3,0,10,9). Both intersect query (2,6,4,8), so neither can be skipped. This deliberately overlapping grouping illustrates query behaviour, not an optimal packing claim. Both leaves are at depth one; their occupancies two and three fit an example maximum of three and minimum of two.

A bounding-box hit can be a false positive

L1’s diagonal has box (0,0,10,10), which overlaps the query, but the line itself does not cross the query rectangle: at x between 2 and 4 its y is between 2 and 4, below the query. L2 and L4 do intersect the query.

The diagonal L1 has an overlapping bounding rectangle but misses the query; L2 and L4 actually cross it.

The index produces candidates, followed by a predicate on the actual geometry. For axis-aligned rectangular objects and a rectangle-intersection question, box overlap can itself be exact. For general geometry, enclosure is sufficient to reject a disjoint box but insufficient to confirm intersection.

Reference: Guttman’s original R-tree paper. The PostGIS spatial-index workshop explains the same filter/refine principle in an application setting.

Check your understanding

Predict the answer before opening the explanation.

1. Does diagonal L1 intersect the query (2,6,4,8) just because its box does?

Show explanation

No. Its box (0,0,10,10) overlaps the query, so L1 is a candidate. On L1, y=x; for x in [2,4], y is in [2,4], below [6,8]. Exact refinement rejects L1.

2. The example’s leaves are at the same depth. Can the query choose only one of groups U and V?

Show explanation

No. U’s box (0,0,10,10) and V’s box (3,0,10,9) both meet the query. Equal leaf depth does not make their bounds disjoint or establish a single search path.

Next: Range candidates and exact refinement. The following operation reading reuses that traversal for exact lookup and develops construction, nearest search and deletion.