Skip to content

Commit 1b111bb

Browse files
committed
feat: add Index Arithmetic documentation and link in the configuration
1 parent 4c6b180 commit 1b111bb

File tree

2 files changed

+269
-0
lines changed

2 files changed

+269
-0
lines changed

docs/.vitepress/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export default defineConfig({
4242
{ text: 'Multi-Aperture', link: '/multi-aperture' },
4343
{ text: 'Hierarchical Operations', link: '/hierarchical-operations' },
4444
{ text: 'Hierarchical Address Types', link: '/hierarchical-addresses' },
45+
{ text: 'Index Arithmetic', link: '/index-arithmetic' },
4546
],
4647
},
4748
{

docs/index-arithmetic.md

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
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

Comments
 (0)