code wiki / _hdl_build / nx_geo_join.nx
nx_geo_join.nx source
↩ module page · 64 lines · 2838 B
1// nx_geo_join.nx -- LIB: GEO-013 SPATIAL JOIN (point-in-which-polygon), INTEGER-EXACT. The PostGIS
2// ST_Contains-join staple: assign each of N query points to the polygon (from a pool of M) that
3// contains it. Composes the GEO-001 exact ray-cast predicate, here as a pooled, base-offset variant
4// `geo_pip_off` so many polygons share one flat vertex buffer (pool + per-polygon offset/count).
5//
6// EXCEED ANGLE (measured, not asserted): the containment test is the same integer cross-multiplied
7// ray-cast as GEO-001 -- ZERO floating point -> the point->polygon assignment is EXACT and
8// DETERMINISTIC on edges/vertices, where float spatial joins (Turf/Shapely/PostGIS) can drop or
9// double-count boundary points. Overflow-safe for Earth coords in i64.
10//
11// (geo_pip_off restates GEO-001's ray-cast for the pooled layout; if a 3rd caller needs it, extract a
12// shared base-offset primitive into nx_geo per the DRY rule.) license_tier: ORIGINAL
13import "nx_syscalls.nx"
14
15// point-in-polygon for a polygon stored at `pool[(base+k)*2 .. ]`, npts vertices. 1 inside, 0 outside.
16// Same integer ray-cast as GEO-001 geo_point_in_poly (cross-multiplied by edge dy; no division/float).
17func geo_pip_off(pool: *i64, base: i64, npts: i64, lat: i64, lon: i64) -> i64 {
18 var inside: i64 = 0
19 var i: i64 = 0
20 var j: i64 = npts - 1
21 while i < npts {
22 let yi: i64 = pool[(base + i) * 2]
23 let xi: i64 = pool[(base + i) * 2 + 1]
24 let yj: i64 = pool[(base + j) * 2]
25 let xj: i64 = pool[(base + j) * 2 + 1]
26 var a: i64 = 0
27 if yi > lat { a = 1 }
28 var b: i64 = 0
29 if yj > lat { b = 1 }
30 if a != b {
31 let dy: i64 = yj - yi
32 let lhs: i64 = (lon - xi) * dy
33 let rhs: i64 = (xj - xi) * (lat - yi)
34 var cross: i64 = 0
35 if dy > 0 { if lhs < rhs { cross = 1 } }
36 if dy < 0 { if lhs > rhs { cross = 1 } }
37 if cross == 1 { inside = 1 - inside }
38 }
39 j = i
40 i = i + 1
41 }
42 return inside
43}
44
45// index of the first polygon (of npolys) containing (lat,lon); -1 if none. off[p]/cnt[p] = polygon p's
46// base vertex offset and vertex count within the shared pool.
47func geo_locate(pool: *i64, off: *i64, cnt: *i64, npolys: i64, lat: i64, lon: i64) -> i64 {
48 var p: i64 = 0
49 while p < npolys {
50 if geo_pip_off(pool, off[p], cnt[p], lat, lon) == 1 { return p }
51 p = p + 1
52 }
53 return 0 - 1
54}
55
56// spatial join: for each of npts query points write the containing polygon index (-1 if none) to out.
57func geo_spatial_join(pts: *i64, npts: i64, pool: *i64, off: *i64, cnt: *i64, npolys: i64, out: *i64) -> i64 {
58 var i: i64 = 0
59 while i < npts {
60 out[i] = geo_locate(pool, off, cnt, npolys, pts[i * 2], pts[i * 2 + 1])
61 i = i + 1
62 }
63 return npts
64}