Range Queries¶
A Yoro code is not only a label for a place — it is a sortable key. That makes
"what is near me" answerable with a plain BETWEEN on an ordinary B-tree index,
with no spatial extension, no R-tree, and no PostGIS.
Why it works¶
Two properties line up:
- The alphabet is sorted.
23456789ABCDEFGHJKMNPQRSTVWXYis in increasing value order, so for bodies of equal length, lexicographic order on the text equals numeric order on the Hilbert index. - The Hilbert curve is contiguous. Any aligned
s × sblock of cells occupies one unbroken run ofs²indices.
So a rectangle on the ground becomes a handful of index intervals — and, thanks to (1), a handful of string intervals your database already knows how to seek.
ranges()¶
from yoro import ranges
for code_min, code_max in ranges(12.60, 12.66, -8.03, -7.97, precision=14, domain="ML"):
cur.execute(
"SELECT * FROM pois WHERE code BETWEEN ? AND ? ORDER BY code",
(code_min, code_max),
)
Both bounds are inclusive. The intervals are disjoint and sorted.
Compared to cells_in_bounds()¶
cells_in_bounds() enumerates every cell and refuses past
max_cells, because the count grows with the area. ranges() returns the
intervals containing those cells, so its output stays small however large the
box. On one Bamako neighbourhood:
| Precision | cells_in_bounds() |
ranges() |
|---|---|---|
| ~12 | 270 cells | 18 intervals |
| ~14 | refuses (3886 > 2000) | 49 intervals |
Use cells_in_bounds() when you want to draw the grid; use ranges() when
you want to query it.
Capping the interval count¶
Each interval costs one index seek. When you would rather do fewer seeks and
filter afterwards, max_ranges merges the closest intervals:
The result then covers some cells outside the box — merging spans the gaps — so filter on exact coordinates afterwards if precision matters. Nothing inside the box is ever dropped.
Truncated codes: prefix_range()¶
Dropping characters off the end of a code has two readings, and they are not the same region:
- (a) the index interval
[d · 29ᵗ, (d+1) · 29ᵗ)— a connected segment of the curve, exactly whatLIKE 'prefix%'matches; - (b) decoding the prefix as a shorter code — a cell at the canonical
precision of
k − t, whose edges do not line up with the finer grid.
prefix_range() implements (a), and clips to the valid index range so it never
spans codes decode() would reject:
from yoro import prefix_range
lo, hi = prefix_range("ML-4V7D", full_length=6)
cur.execute("SELECT * FROM places WHERE code BETWEEN ? AND ?", (lo, hi))
Prefer this over LIKE 'ML-4V7D%': it uses the same index as every other query
and cannot accidentally match a code of a different length.
What this does not give you¶
Codes sort by Hilbert index, and a curve visits a plane in one dimension — so two cells that are neighbours on the ground can sit far apart in code order. That is why a box needs several intervals rather than one, and why the count grows with how badly the box straddles the curve's larger blocks. It is a locality heuristic that pays off, not a promise of contiguity.
The intervals also describe cells, not distances. ranges() gives you the
candidates; if you need "within 500 m", filter the rows it returns.