|
| 1 | +# Index Arithmetic |
| 2 | + |
| 3 | +One of the most powerful features of hierarchical index types (Z3, Z7, ZORDER) is that **standard arithmetic and bitwise operations on the raw index values correspond to spatial and hierarchical operations on the grid**. No special API methods are needed — just plain BigInt math. |
| 4 | + |
| 5 | +This page shows practical recipes for each index type. |
| 6 | + |
| 7 | +## Interactive Demo |
| 8 | + |
| 9 | +Try changing the aperture to see different index types in action. Click a cell, then inspect the arithmetic panel below. |
| 10 | + |
| 11 | +<ClientOnly> |
| 12 | + <DggsAddressTypesDemo /> |
| 13 | +</ClientOnly> |
| 14 | + |
| 15 | +## Setup |
| 16 | + |
| 17 | +All examples below assume this setup: |
| 18 | + |
| 19 | +```typescript |
| 20 | +import { Webdggrid } from 'webdggrid'; |
| 21 | + |
| 22 | +const dggs = await Webdggrid.load(); |
| 23 | +``` |
| 24 | + |
| 25 | +## Z3 Arithmetic (Aperture 3) |
| 26 | + |
| 27 | +Z3 encodes each cell as a base-3 number where each digit (0, 1, 2) represents which of the 3 children was chosen at that resolution level. |
| 28 | + |
| 29 | +```typescript |
| 30 | +dggs.setDggs({ |
| 31 | + poleCoordinates: { lat: 0, lng: 0 }, |
| 32 | + azimuth: 0, |
| 33 | + aperture: 3, |
| 34 | + topology: 'HEXAGON', |
| 35 | + projection: 'ISEA', |
| 36 | +}, 5); |
| 37 | + |
| 38 | +const cellId = 50n; |
| 39 | +const z3 = dggs.sequenceNumToZ3(cellId, 5); |
| 40 | +``` |
| 41 | + |
| 42 | +### Find Parent — Integer Division |
| 43 | + |
| 44 | +Drop the last base-3 digit by dividing by 3. This is equivalent to `sequenceNumParent()`. |
| 45 | + |
| 46 | +```typescript |
| 47 | +const parentZ3 = z3 / 3n; |
| 48 | +const parentSeq = dggs.z3ToSequenceNum(parentZ3, 4); |
| 49 | + |
| 50 | +// Verify: identical to the API |
| 51 | +const parentApi = dggs.sequenceNumParent([cellId], 5)[0]; |
| 52 | +console.log(parentSeq === parentApi); // true |
| 53 | +``` |
| 54 | + |
| 55 | +### Find Children — Multiply and Append |
| 56 | + |
| 57 | +Multiply by 3 and add digits 0, 1, 2. This is equivalent to `sequenceNumChildren()`. |
| 58 | + |
| 59 | +```typescript |
| 60 | +const children = [0n, 1n, 2n].map(digit => { |
| 61 | + const childZ3 = z3 * 3n + digit; |
| 62 | + return dggs.z3ToSequenceNum(childZ3, 6); |
| 63 | +}); |
| 64 | + |
| 65 | +// Verify: identical to the API |
| 66 | +const childrenApi = dggs.sequenceNumChildren([cellId], 5)[0]; |
| 67 | +console.log(children.every((c, i) => c === childrenApi[i])); // true |
| 68 | +``` |
| 69 | + |
| 70 | +### Extract Resolution Level — Modulo |
| 71 | + |
| 72 | +Read which child was chosen at the finest level: |
| 73 | + |
| 74 | +```typescript |
| 75 | +const lastDigit = z3 % 3n; |
| 76 | +console.log(lastDigit); // 0n, 1n, or 2n — the child index at this resolution |
| 77 | +``` |
| 78 | + |
| 79 | +### Walk the Hierarchy — Digit Extraction |
| 80 | + |
| 81 | +Extract the full hierarchical path from coarsest to finest: |
| 82 | + |
| 83 | +```typescript |
| 84 | +function extractDigits(z3Value, resolution) { |
| 85 | + const digits = []; |
| 86 | + let v = z3Value; |
| 87 | + for (let i = 0; i < resolution; i++) { |
| 88 | + digits.unshift(Number(v % 3n)); |
| 89 | + v = v / 3n; |
| 90 | + } |
| 91 | + return digits; |
| 92 | +} |
| 93 | + |
| 94 | +const path = extractDigits(z3, 5); |
| 95 | +console.log(path); // e.g. [1, 0, 2, 1, 0] — the path from root to cell |
| 96 | +``` |
| 97 | + |
| 98 | +### Check if Cell is Ancestor |
| 99 | + |
| 100 | +Cell A is an ancestor of cell B if B's Z3 value starts with A's digits: |
| 101 | + |
| 102 | +```typescript |
| 103 | +function isAncestor(ancestorZ3, descendantZ3, resAncestor, resDescendant) { |
| 104 | + const shift = BigInt(resDescendant - resAncestor); |
| 105 | + const base = 3n; |
| 106 | + return descendantZ3 / (base ** shift) === ancestorZ3; |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +## Z7 Arithmetic (Aperture 7) |
| 111 | + |
| 112 | +Z7 works identically to Z3 but in base 7. Each parent has exactly 7 children (digits 0–6). |
| 113 | + |
| 114 | +```typescript |
| 115 | +dggs.setDggs({ |
| 116 | + poleCoordinates: { lat: 0, lng: 0 }, |
| 117 | + azimuth: 0, |
| 118 | + aperture: 7, |
| 119 | + topology: 'HEXAGON', |
| 120 | + projection: 'ISEA', |
| 121 | +}, 5); |
| 122 | + |
| 123 | +const cellId = 100n; |
| 124 | +const z7 = dggs.sequenceNumToZ7(cellId, 5); |
| 125 | +``` |
| 126 | + |
| 127 | +### Find Parent |
| 128 | + |
| 129 | +```typescript |
| 130 | +const parentZ7 = z7 / 7n; |
| 131 | +const parentSeq = dggs.z7ToSequenceNum(parentZ7, 4); |
| 132 | +``` |
| 133 | + |
| 134 | +### Find Children |
| 135 | + |
| 136 | +```typescript |
| 137 | +const children = [0n, 1n, 2n, 3n, 4n, 5n, 6n].map(digit => { |
| 138 | + const childZ7 = z7 * 7n + digit; |
| 139 | + return dggs.z7ToSequenceNum(childZ7, 6); |
| 140 | +}); |
| 141 | +``` |
| 142 | + |
| 143 | +### Extract Digits |
| 144 | + |
| 145 | +```typescript |
| 146 | +function extractZ7Digits(z7Value, resolution) { |
| 147 | + const digits = []; |
| 148 | + let v = z7Value; |
| 149 | + for (let i = 0; i < resolution; i++) { |
| 150 | + digits.unshift(Number(v % 7n)); |
| 151 | + v = v / 7n; |
| 152 | + } |
| 153 | + return digits; |
| 154 | +} |
| 155 | + |
| 156 | +const path = extractZ7Digits(z7, 5); |
| 157 | +// e.g. [3, 1, 5, 0, 2] — 7-ary hierarchical path |
| 158 | +``` |
| 159 | + |
| 160 | +### Sibling Enumeration |
| 161 | + |
| 162 | +Find all cells that share the same parent (siblings): |
| 163 | + |
| 164 | +```typescript |
| 165 | +const parentZ7 = z7 / 7n; |
| 166 | +const siblings = [0n, 1n, 2n, 3n, 4n, 5n, 6n].map(digit => { |
| 167 | + return dggs.z7ToSequenceNum(parentZ7 * 7n + digit, 5); |
| 168 | +}); |
| 169 | +// siblings contains all 7 children of the same parent, including the original cell |
| 170 | +``` |
| 171 | + |
| 172 | +## ZORDER Arithmetic (Aperture 3 & 4) |
| 173 | + |
| 174 | +ZORDER (Morton codes) interleave coordinate bits to create a Z-shaped space-filling curve. Unlike Z3/Z7, ZORDER doesn't directly encode parent-child relationships, but it provides **spatial locality** — cells that are close in space have close ZORDER values. |
| 175 | + |
| 176 | +```typescript |
| 177 | +dggs.setDggs({ |
| 178 | + poleCoordinates: { lat: 0, lng: 0 }, |
| 179 | + azimuth: 0, |
| 180 | + aperture: 4, |
| 181 | + topology: 'HEXAGON', |
| 182 | + projection: 'ISEA', |
| 183 | +}, 5); |
| 184 | + |
| 185 | +const cellId = 100n; |
| 186 | +const zorder = dggs.sequenceNumToZOrder(cellId, 5); |
| 187 | +``` |
| 188 | + |
| 189 | +### Spatial Range Queries |
| 190 | + |
| 191 | +Since spatially close cells have similar ZORDER values, you can do range queries: |
| 192 | + |
| 193 | +```typescript |
| 194 | +// Find all cells in a ZORDER range |
| 195 | +function cellsInRange(zMin, zMax, resolution) { |
| 196 | + const cells = []; |
| 197 | + // In practice, you'd query a database with: WHERE zorder BETWEEN zMin AND zMax |
| 198 | + // This is much faster than checking geographic bounds for each cell |
| 199 | + return cells; |
| 200 | +} |
| 201 | +``` |
| 202 | + |
| 203 | +### Common Ancestor Detection |
| 204 | + |
| 205 | +Two cells that share a longer ZORDER hex prefix are closer in the spatial hierarchy: |
| 206 | + |
| 207 | +```typescript |
| 208 | +function sharedPrefixLength(z1, z2) { |
| 209 | + const h1 = z1.toString(16).padStart(16, '0'); |
| 210 | + const h2 = z2.toString(16).padStart(16, '0'); |
| 211 | + let common = 0; |
| 212 | + for (let i = 0; i < h1.length; i++) { |
| 213 | + if (h1[i] === h2[i]) common++; |
| 214 | + else break; |
| 215 | + } |
| 216 | + return common; |
| 217 | +} |
| 218 | + |
| 219 | +const neighborZorder = dggs.sequenceNumToZOrder(neighborId, 5); |
| 220 | +const prefix = sharedPrefixLength(zorder, neighborZorder); |
| 221 | +// Higher prefix length = closer spatial relationship |
| 222 | +``` |
| 223 | + |
| 224 | +### Database Indexing Pattern |
| 225 | + |
| 226 | +ZORDER is ideal for spatial indexing in databases: |
| 227 | + |
| 228 | +```typescript |
| 229 | +// Store cells with ZORDER index |
| 230 | +const cells = cellIds.map(id => ({ |
| 231 | + seqnum: id, |
| 232 | + zorder: dggs.sequenceNumToZOrder(id, 5), |
| 233 | + // ... other data |
| 234 | +})); |
| 235 | + |
| 236 | +// Query: "find all cells near this point" |
| 237 | +const targetZorder = dggs.sequenceNumToZOrder(targetCell, 5); |
| 238 | +// SQL: SELECT * FROM cells WHERE zorder BETWEEN ? AND ? ORDER BY zorder |
| 239 | +// The range bounds can be computed from the target's ZORDER +/- a window |
| 240 | +``` |
| 241 | + |
| 242 | +## Comparison: When to Use Which |
| 243 | + |
| 244 | +| Operation | Z3/Z7 | ZORDER | SEQNUM | |
| 245 | +|-----------|-------|--------|--------| |
| 246 | +| Find parent | `z / base` | N/A | `sequenceNumParent()` | |
| 247 | +| Find children | `z * base + digit` | N/A | `sequenceNumChildren()` | |
| 248 | +| Check ancestry | `descendant / base^n === ancestor` | Prefix comparison | N/A | |
| 249 | +| Range query | N/A | `BETWEEN zMin AND zMax` | N/A | |
| 250 | +| Spatial proximity | Compare digit prefixes | Compare values | N/A | |
| 251 | +| Database index | Good for hierarchy | Good for spatial | Just an ID | |
| 252 | +| Sibling enumeration | Vary last digit | N/A | Via parent + children | |
| 253 | + |
| 254 | +## Key Takeaway |
| 255 | + |
| 256 | +**SEQNUM** is just a flat ID — arithmetic on it is meaningless. |
| 257 | + |
| 258 | +**Z3/Z7** encode the hierarchical path, so division and multiplication navigate up and down the tree. |
| 259 | + |
| 260 | +**ZORDER** encodes spatial position, so numerical proximity equals spatial proximity — perfect for range queries and database indexing. |
| 261 | + |
| 262 | +All operations are plain BigInt math. The API's `sequenceNumToZ3()` / `z3ToSequenceNum()` (and Z7/ZORDER equivalents) are the only bridge you need. |
| 263 | + |
| 264 | +## API Reference |
| 265 | + |
| 266 | +- [`sequenceNumToZ3()`](api/classes/Webdggrid.md#sequencenumtoz3) / [`z3ToSequenceNum()`](api/classes/Webdggrid.md#z3tosequencenum) |
| 267 | +- [`sequenceNumToZ7()`](api/classes/Webdggrid.md#sequencenumtoz7) / [`z7ToSequenceNum()`](api/classes/Webdggrid.md#z7tosequencenum) |
| 268 | +- [`sequenceNumToZOrder()`](api/classes/Webdggrid.md#sequencenumtozorder) / [`zOrderToSequenceNum()`](api/classes/Webdggrid.md#zordertosequencenum) |
0 commit comments