nx_geo_wasm.nx source
↩ module page · 63 lines · 2952 B
1// nx_geo_wasm.nx -- GEO-024h: sovereign Web-Mercator projection compiled to WASM for the Nishi Maps
2// browser client. WASM-TARGET rules (per nx_pets_wasm): NO syscalls, fixed offsets, top-level funcs
3// auto-export, NO forward references (nxc2 resolves top-to-bottom). Pipeline: nxc2 --target wat -> wat
4// -> wasm (wabt last-mile / nx_wat_compiler sovereign) -> browser calls these exports to reproject on
5// pan/zoom = true infinite zoom, geo math stays Nishi-authored + Nishi-compiled.
6//
7// STEP A: nm_merc_x (exact integer longitude axis, no trig). STEP B: nm_merc_y (the Mercator latitude
8// axis) via nm_tan -- a LOCALS-ONLY CORDIC (no sys_mmap, so it crosses into wasm AND runs native).
9// license_tier: ORIGINAL
10import "fx.nx" // mmap-free helpers only: fx_normalize_angle, fx_cordic_atan, fx_mul, fx_log2, consts
11
12const NM_LN2: i64 = 45426 // ln(2) in Q16.16 (log2 -> ln)
13
14// canary: proves the nx->wat->wasm round-trip carries integer args/returns correctly.
15func nm_add(a: i64, b: i64) -> i64 { return a + b }
16
17// world width in pixels at slippy zoom z = 256 * 2^z.
18func nm_worldpx(z: i64) -> i64 { var n: i64 = 256; var t: i64 = 0; while t < z { n = n * 2; t = t + 1 } return n }
19
20// longitude (microdegrees) -> Web-Mercator world-pixel X at zoom z. EXACT integer (the linear axis).
21func nm_merc_x(lon: i64, z: i64) -> i64 {
22 let w: i64 = nm_worldpx(z)
23 return (lon + 180000000) * w / 360000000
24}
25
26// WASM-FRIENDLY tan(theta) in Q16.16 -- a LOCALS-ONLY CORDIC (no sys_mmap, no scratch pointers, so it
27// compiles to clean wasm AND runs native). Same algorithm as fx.nx fx_sin_cos but returns tan=sin/cos
28// directly (x=cos, y=sin kept in locals). This is the port the WASM capstone needed.
29func nm_tan(theta_raw: i64) -> i64 {
30 let theta: i64 = fx_normalize_angle(theta_raw)
31 var t: i64 = theta
32 var flip_cos: i64 = 0
33 var flip_sin: i64 = 0
34 if t > FX_HALF_PI { t = FX_PI - t; flip_cos = 1 }
35 if t < (0 - FX_HALF_PI) { t = (0 - FX_PI) - t; flip_sin = 1; flip_cos = 1 }
36 var x: i64 = FX_CORDIC_K
37 var y: i64 = 0
38 var zz: i64 = t
39 var i: i64 = 0
40 while i < 16 {
41 let atan_i: i64 = fx_cordic_atan(i)
42 let dx: i64 = y >> i
43 let dy: i64 = x >> i
44 if zz >= 0 { x = x - dx; y = y + dy; zz = zz - atan_i } else { x = x + dx; y = y - dy; zz = zz + atan_i }
45 i = i + 1
46 }
47 if flip_sin == 1 { y = 0 - y }
48 if flip_cos == 1 { x = 0 - x }
49 if x == 0 { return 0 }
50 return y * FX_ONE / x
51}
52
53// latitude (microdegrees) -> Web-Mercator world-pixel Y at zoom z = (1 - ln(tan(pi/4+lat/2))/pi)/2 * worldpx.
54func nm_merc_y(lat: i64, z: i64) -> i64 {
55 let w: i64 = nm_worldpx(z)
56 let phi: i64 = lat * FX_TWO_PI / 360000000
57 let arg: i64 = FX_PI / 4 + phi / 2
58 var tanv: i64 = nm_tan(arg)
59 if tanv <= 0 { tanv = 1 }
60 let psi: i64 = fx_mul(fx_log2(tanv), NM_LN2)
61 let pop: i64 = psi * FX_ONE / FX_PI
62 return ((FX_ONE - pop) / 2) * w / FX_ONE
63}