code wiki / (root) / nx_geo_wasm.nx

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}