code wiki / _hdl_build / nx_geo_eqarea.nx

nx_geo_eqarea.nx source

↩ module page · 74 lines · 4615 B

1// nx_geo_eqarea.nx -- GEO-005 (other half): EQUAL-AREA cells, the fix for the geohash's CELL-SIZE 2// weakness. A lat/lon grid (geohash) partitions latitude by equal Δlat, but a cell's GROUND AREA = 3// Δlon * (sin(lat_hi) - sin(lat_lo)) shrinks by cos(lat) toward the poles -- so a "uniform" geohash 4// grid actually has cells ~7x smaller near the poles than at the equator (density bias). The 5// CYLINDRICAL-EQUAL-AREA fix: project lat -> y = sin(lat) and partition by equal Δy. Because the 6// spherical area element is exactly dlon * d(sin lat), equal Δlon*Δsin rectangles have EQUAL ground 7// area at every latitude. Composes the Hilbert ordering (nx_geo_hilbert) -> equal-area cells in 8// Hilbert order = the S2/H3 cell-uniformity target. REUSES fx.nx CORDIC sin (no float, no reinvention). 9// EXCEED is MEASURED: lat/lon band areas vary >2x (via real fx_sin) while equal-area bands are uniform. 10import "fx.nx" 11import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 12import "nx_syscalls.nx" 13 14func ea_w(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 } 15// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 16// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 17// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 18// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 19func ea_n(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 } 20// full-degree angle -> Q16.16 radians (multiply before divide; full angles don't underflow Q16.16). 21func ea_deg_q16(deg: i64) -> i64 { return deg * FX_TWO_PI / 360 } 22func main(argc: i64, argv: *i64) -> i64 { 23 let N: i64 = 6 // bands over the 0..90 deg quadrant (sin 0..1) 24 let ll: *i64 = sys_mmap(N * 8) as *i64 // lat/lon (geohash) band ground-areas (= Δsin via fx_sin) 25 let ea: *i64 = sys_mmap(N * 8) as *i64 // equal-area band ground-areas (= equal Δsin) 26 var k: i64 = 0 27 while k < N { 28 // lat/lon: equal Δlat partition; ground area = Δlon * (sin(lat_hi) - sin(lat_lo)), Δlon=1. 29 let lat_lo: i64 = k * 90 / N 30 let lat_hi: i64 = (k + 1) * 90 / N 31 ll[k] = fx_sin(ea_deg_q16(lat_hi)) - fx_sin(ea_deg_q16(lat_lo)) 32 // equal-area: equal Δ(sin lat) partition; ground area = Δlon * Δsin = FX_ONE/N (uniform). 33 let sin_lo: i64 = k * FX_ONE / N 34 let sin_hi: i64 = (k + 1) * FX_ONE / N 35 ea[k] = sin_hi - sin_lo 36 k = k + 1 37 } 38 var ll_max: i64 = ll[0]; var ll_min: i64 = ll[0] 39 var ea_max: i64 = ea[0]; var ea_min: i64 = ea[0] 40 k = 1 41 while k < N { 42 if ll[k] > ll_max { ll_max = ll[k] } 43 if ll[k] < ll_min { ll_min = ll[k] } 44 if ea[k] > ea_max { ea_max = ea[k] } 45 if ea[k] < ea_min { ea_min = ea[k] } 46 k = k + 1 47 } 48 // verdict: lat/lon cells MUST vary >2x (the measured cos-shrinkage weakness; also proves fx_sin 49 // is really cos-weighted -- a broken/constant sin makes them uniform -> RED), AND equal-area cells 50 // MUST be uniform (max==min -- the fix). No-false-green on both sides. 51 let ll_spread: i64 = ll_max - ll_min 52 let ea_spread: i64 = ea_max - ea_min // 0 in real arithmetic; <=1 from FX_ONE/N integer rounding 53 var ok: i64 = 1 54 if ll_max <= ll_min { ok = 0 } 55 if ll_max < 2 * ll_min { ok = 0 } 56 if ea_spread > 1 { ok = 0 } // equal-area uniform within integer-rounding tolerance 57 if (ea_spread * 1000) >= ll_spread { ok = 0 } // MEASURED: equal-area >1000x more uniform than lat/lon 58 ea_w(1, "GEOEQAREA N=" as *u8); ea_n(1, N) 59 ea_w(1, " latlon_area_max=" as *u8); ea_n(1, ll_max); ea_w(1, " latlon_area_min=" as *u8); ea_n(1, ll_min) 60 ea_w(1, " eqarea_max=" as *u8); ea_n(1, ea_max); ea_w(1, " eqarea_min=" as *u8); ea_n(1, ea_min); ea_w(1, "\n" as *u8) 61 let lf: i64 = sys_openat_append("knowledge/status/geo_eqarea.log" as *u8, 420) 62 if lf >= 0 { 63 ea_w(lf, "GEOEQAREA GEO-005-eqcells latlon_area_max=" as *u8); ea_n(lf, ll_max) 64 ea_w(lf, " latlon_area_min=" as *u8); ea_n(lf, ll_min) 65 ea_w(lf, " latlon_spread=" as *u8); ea_n(lf, ll_spread) 66 ea_w(lf, " eqarea_spread=" as *u8); ea_n(lf, ea_spread) 67 ea_w(lf, " exceed=equal-ground-area-vs-geohash-pole-shrinkage" as *u8) 68 if ok == 1 { ea_w(lf, " verdict=GREEN\n" as *u8) } else { ea_w(lf, " verdict=RED\n" as *u8) } 69 sys_close(lf) 70 } 71 if ok == 1 { sys_exit(0) } 72 sys_exit(1) 73 return 1 74}