code wiki / _hdl_build / nx_geo_s2.nx
nx_geo_s2.nx source
↩ module page · 95 lines · 3316 B
1// nx_geo_s2.nx -- LIB: GEO-005 S2-CLASS CELL INDEX via the HILBERT space-filling curve. The real
2// exceed over GEO-002's geohash (which interleaves lon/lat bits = the Z-ORDER / Morton curve).
3//
4// THE EXCEED ANGLE (measured, not asserted): a Hilbert curve is CONTINUOUS -- consecutive 1D cell
5// indices are ALWAYS 4-adjacent in 2D (Manhattan step exactly 1) -- so a 1D range scan never "tears"
6// across distant 2D regions. The Z-order curve that geohash uses TEARS: its consecutive step jumps by
7// up to the grid width N at every major quadrant boundary. Google's S2 chose Hilbert for exactly this
8// locality; here it is sovereign + integer. (cell index quality is what makes range queries / tile
9// coherence good; this is the measurable weakness of plain geohash.)
10//
11// Pure integer bit arithmetic (bit-tests via /,% to avoid the geohash const-index landmine),
12// deterministic. (x,y) are integer cell coords in [0, 2^k); a real lon/lat first quantizes to that
13// grid (GEO-002 already does the microdeg bisection). license_tier: ORIGINAL
14import "nx_syscalls.nx"
15
16func geo_s2_pow2(k: i64) -> i64 { var n: i64 = 1; var t: i64 = 0; while t < k { n = n * 2; t = t + 1 } return n }
17
18// (x,y) -> Hilbert distance d on the order-k curve (grid n=2^k). Standard rotate-and-fold algorithm.
19func geo_hilbert_xy2d(k: i64, x: i64, y: i64) -> i64 {
20 let n: i64 = geo_s2_pow2(k)
21 var d: i64 = 0
22 var xx: i64 = x
23 var yy: i64 = y
24 var s: i64 = n / 2
25 while s > 0 {
26 var rx: i64 = 0
27 if ((xx / s) % 2) == 1 { rx = 1 }
28 var ry: i64 = 0
29 if ((yy / s) % 2) == 1 { ry = 1 }
30 // q = (3*rx) XOR ry, computed without a bitwise xor.
31 var q: i64 = 3 * rx
32 if ry == 1 { if rx == 0 { q = 1 } else { q = 2 } }
33 d = d + s * s * q
34 // rotate the quadrant
35 if ry == 0 {
36 if rx == 1 {
37 xx = n - 1 - xx
38 yy = n - 1 - yy
39 }
40 let tmp: i64 = xx
41 xx = yy
42 yy = tmp
43 }
44 s = s / 2
45 }
46 return d
47}
48
49// inverse: Hilbert distance d -> (x,y), written to out2[0]=x, out2[1]=y.
50func geo_hilbert_d2xy(k: i64, d: i64, out2: *i64) -> i64 {
51 let n: i64 = geo_s2_pow2(k)
52 var rx: i64 = 0
53 var ry: i64 = 0
54 var s: i64 = 1
55 var t: i64 = d
56 var xx: i64 = 0
57 var yy: i64 = 0
58 while s < n {
59 rx = (t / 2) % 2
60 ry = ((t % 2) + rx) % 2 // = (t XOR rx) AND 1
61 if ry == 0 {
62 if rx == 1 {
63 xx = s - 1 - xx
64 yy = s - 1 - yy
65 }
66 let tmp: i64 = xx
67 xx = yy
68 yy = tmp
69 }
70 xx = xx + s * rx
71 yy = yy + s * ry
72 t = t / 4
73 s = s * 2
74 }
75 out2[0] = xx
76 out2[1] = yy
77 return 0
78}
79
80// (x,y) -> Z-order / Morton distance (the geohash-family curve) on the order-k grid, for comparison.
81func geo_zorder_xy2d(k: i64, x: i64, y: i64) -> i64 {
82 var d: i64 = 0
83 var i: i64 = 0
84 var bitval: i64 = 1 // 4^i = position weight of bit i of x; y's bit sits one above (bitval*2)
85 var xs: i64 = x
86 var ys: i64 = y
87 while i < k {
88 d = d + (xs % 2) * bitval + (ys % 2) * bitval * 2
89 xs = xs / 2
90 ys = ys / 2
91 bitval = bitval * 4
92 i = i + 1
93 }
94 return d
95}