code wiki / _hdl_build / nx_geo_match.nx
nx_geo_match.nx source
↩ module page · 65 lines · 3276 B
1// nx_geo_match.nx -- LIB: GEO-023 MAP-MATCHING (snap a noisy GPS fix to a road network).
2//
3// For each road EDGE (a segment A->B) we project the GPS point P onto the segment (closest point),
4// clamped to the endpoints, then pick the edge whose snapped point is nearest P. Projection parameter
5// t = dot(P-A, B-A) / dot(B-A, B-A) clamped to [0,1]; the snapped point is A + t*(B-A).
6//
7// THE EXCEED ANGLE: the projection + nearest-edge choice are INTEGER and deterministic -- the snapped
8// position and the chosen edge are reproducible to the bit (no float jitter that makes a GPS fix flip
9// between two near-equidistant roads). Overflow-safe at any scale: the t fraction (num,den) is
10// normalized (lockstep >>1, ratio preserved) before multiplying by the edge delta, exactly as the
11// GEO-015 clip intersection does. Sovereign + offline; the road graph is seeded data, not a service.
12// Convention: points/edges in microdegrees, [lat,lon]; edges = flat [alat,alon,blat,blon, ...].
13// license_tier: ORIGINAL
14import "nx_syscalls.nx"
15const K_MAGIC_17179869184: i64 = 17179869184
16
17func geo_mm_abs(x: i64) -> i64 { if x < 0 { return 0 - x } return x }
18
19// snapped (closest) point of P on segment A->B, written to out2 = [lat,lon].
20func geo_snap_to_segment(alat: i64, alon: i64, blat: i64, blon: i64, plat: i64, plon: i64, out2: *i64) -> i64 {
21 let dlat: i64 = blat - alat
22 let dlon: i64 = blon - alon
23 let den: i64 = dlat * dlat + dlon * dlon
24 if den == 0 { out2[0] = alat; out2[1] = alon; return 0 } // degenerate edge = a point
25 let num: i64 = (plat - alat) * dlat + (plon - alon) * dlon
26 if num <= 0 { out2[0] = alat; out2[1] = alon; return 0 } // projects before A -> clamp A
27 if num >= den { out2[0] = blat; out2[1] = blon; return 0 } // projects after B -> clamp B
28 // interior: A + (num/den)*(B-A); normalize the fraction so n2*delta can't overflow i64.
29 var n2: i64 = num
30 var d2: i64 = den
31 var go: i64 = 1
32 while go == 1 {
33 if geo_mm_abs(n2) > K_MAGIC_17179869184 { n2 = n2 / 2; d2 = d2 / 2 } else {
34 if geo_mm_abs(d2) > K_MAGIC_17179869184 { n2 = n2 / 2; d2 = d2 / 2 } else { go = 0 }
35 }
36 }
37 if d2 == 0 { out2[0] = alat; out2[1] = alon; return 0 }
38 out2[0] = alat + n2 * dlat / d2
39 out2[1] = alon + n2 * dlon / d2
40 return 0
41}
42
43// squared distance between two [lat,lon] points (microdeg^2).
44func geo_mm_dist2(lat1: i64, lon1: i64, lat2: i64, lon2: i64) -> i64 {
45 let dy: i64 = lat1 - lat2
46 let dx: i64 = lon1 - lon2
47 return dy * dy + dx * dx
48}
49
50// snap P to the nearest road edge; writes the snapped point to out2, returns the best edge index.
51func geo_map_match(plat: i64, plon: i64, edges: *i64, nedges: i64, out2: *i64) -> i64 {
52 let tmp: *i64 = sys_mmap(16) as *i64
53 var best: i64 = 0 - 1
54 var bestd: i64 = 0
55 var e: i64 = 0
56 while e < nedges {
57 geo_snap_to_segment(edges[e * 4], edges[e * 4 + 1], edges[e * 4 + 2], edges[e * 4 + 3], plat, plon, tmp)
58 let d: i64 = geo_mm_dist2(plat, plon, tmp[0], tmp[1])
59 if best < 0 { best = e; bestd = d; out2[0] = tmp[0]; out2[1] = tmp[1] } else {
60 if d < bestd { best = e; bestd = d; out2[0] = tmp[0]; out2[1] = tmp[1] }
61 }
62 e = e + 1
63 }
64 return best
65}