Quadkey Int64 Encoding
Standard Quadkeys
Map tiles subdivide into 4 at each zoom level. Assigning digits 0–3 to each subdivision gives a string representation of tile position.
Level 1: 4 tiles (0, 1, 2, 3)
Level 2: 16 tiles (00, 01, 02, 03, 10, 11, ..., 33)
Level 3: 64 tiles (000, 001, ..., 333)
...
Level z: 4^z tiles
Each digit represents a position in a 2×2 split: 0=top-left, 1=top-right, 2=bottom-left, 3=bottom-right.
Each digit's bit encoding:
digit = ((y >> i) & 1) << 1 | ((x >> i) & 1)
digit 0: x=0, y=0 (top-left)
digit 1: x=1, y=0 (top-right)
digit 2: x=0, y=1 (bottom-left)
digit 3: x=1, y=1 (bottom-right)
String Limitation
Converting quadkey strings to integers for fast sorting/comparison introduces ambiguity:
"0" → 0
"00" → 0 ← indistinguishable!
"000" → 0 ← indistinguishable!
"032" → 32
"0032"→ 32 ← indistinguishable!
Different zoom levels produce the same integer.
The 0b11 Prefix Trick
Solution: prepend 3 (binary 11) as a prefix sentinel.
"0" → "30" → binary: 11 00 → int 12
"00" → "300" → binary: 11 00 00 → int 48
"000" → "3000" → binary: 11 00 00 00 → int 192
"032" → "3032" → binary: 11 00 11 10 → int 206
All produce distinct integers.
Why 3?
Each quadkey digit is 0, 1, 2, or 3. 3 is a valid digit, but at the first position it serves only as a prefix sentinel. In binary it's 11, so when scanning 2-bit pairs, encountering 11 means "actual quadkey starts here."
Conversion Functions
z/x/y → int64
def tile_to_quadkey_int64(z, x, y):
quadkey_int64 = 3 # prefix 0b11
for i in reversed(range(z)):
digit = ((y >> i) & 1) << 1 | ((x >> i) & 1)
quadkey_int64 = (quadkey_int64 << 2) | digit
return quadkey_int64
int64 → z/x/y
def quadkey_int64_to_zxy(qint64):
x = y = 0
found_prefix = False
z = 0
for shift in reversed(range(0, 64, 2)):
digit = (qint64 >> shift) & 0b11
if not found_prefix:
if digit == 0b11:
found_prefix = True
continue
x = (x << 1) | (digit & 1)
y = (y << 1) | ((digit >> 1) & 1)
z += 1
return z, x, y
Properties
- Sortable: Sorting int64 quadkeys preserves spatial locality
- Compact: Fixed 64-bit integer instead of variable-length string
- Zoom range: 64 bits − 2 bits (prefix) = 62 bits / 2 bits per level = max zoom 31
- Fast traversal: Parent-child relationships via integer operations
- Parent quadkey = child quadkey >> 2
- Child quadkey = (parent quadkey << 2) | digit