Spaces:
Running
Running
Download intel/geo.py from Success-dev/venthrive-api: direct link, hf CLI and curl.
- Browser
- Download file 3.68 kB
-
https://huggingface.co/spaces/Success-dev/venthrive-api/resolve/main/intel/geo.py
- Command line
-
hf download hf://spaces/Success-dev/venthrive-api/intel/geo.py
-
curl -L -o geo.py https://huggingface.co/spaces/Success-dev/venthrive-api/resolve/main/intel/geo.py
3.68 kB
| """ | |
| Pure-Python geometry for the intel store — GeoJSON Polygon/MultiPolygon only. | |
| Enough for Phase 1 (LGA centroids, which-LGA-is-this-point-in, distances) without | |
| pulling a GIS stack into the backend; PostGIS replaces this when the store moves | |
| to Postgres (ADR-0020). | |
| """ | |
| import math | |
| EARTH_RADIUS_KM = 6371.0088 | |
| def _rings(geometry: dict) -> list[list[list[list[float]]]]: | |
| """Normalises Polygon and MultiPolygon to a list of polygons, each a list of | |
| rings (outer ring first, then holes), each ring a list of [lon, lat].""" | |
| if geometry["type"] == "Polygon": | |
| return [geometry["coordinates"]] | |
| if geometry["type"] == "MultiPolygon": | |
| return geometry["coordinates"] | |
| raise ValueError(f"Unsupported geometry type: {geometry['type']}") | |
| def _ring_area_centroid(ring: list[list[float]]) -> tuple[float, float, float]: | |
| """Signed area and area-weighted centroid of one ring (shoelace formula).""" | |
| a = cx = cy = 0.0 | |
| for (x0, y0), (x1, y1) in zip(ring, ring[1:] + ring[:1]): | |
| cross = x0 * y1 - x1 * y0 | |
| a += cross | |
| cx += (x0 + x1) * cross | |
| cy += (y0 + y1) * cross | |
| a /= 2.0 | |
| if a == 0: | |
| xs, ys = [p[0] for p in ring], [p[1] for p in ring] | |
| return 0.0, sum(xs) / len(xs), sum(ys) / len(ys) | |
| return a, cx / (6.0 * a), cy / (6.0 * a) | |
| def centroid(geometry: dict) -> tuple[float, float]: | |
| """Area-weighted centroid as (lon, lat). Holes subtract their area. Planar | |
| maths on degrees is fine at LGA scale this close to the equator.""" | |
| total = sx = sy = 0.0 | |
| for polygon in _rings(geometry): | |
| for i, ring in enumerate(polygon): | |
| a, cx, cy = _ring_area_centroid([p[:2] for p in ring]) | |
| a = abs(a) if i == 0 else -abs(a) | |
| total += a | |
| sx += cx * a | |
| sy += cy * a | |
| if total == 0: | |
| raise ValueError("Degenerate geometry") | |
| return sx / total, sy / total | |
| def bbox(geometry: dict) -> tuple[float, float, float, float]: | |
| xs, ys = [], [] | |
| for polygon in _rings(geometry): | |
| for p in polygon[0]: | |
| xs.append(p[0]) | |
| ys.append(p[1]) | |
| return min(xs), min(ys), max(xs), max(ys) | |
| def _in_ring(lon: float, lat: float, ring: list[list[float]]) -> bool: | |
| inside = False | |
| for (x0, y0), (x1, y1) in zip(ring, ring[1:] + ring[:1]): | |
| if (y0 > lat) != (y1 > lat) and lon < (x1 - x0) * (lat - y0) / (y1 - y0) + x0: | |
| inside = not inside | |
| return inside | |
| def contains(geometry: dict, lon: float, lat: float) -> bool: | |
| for polygon in _rings(geometry): | |
| outer, holes = polygon[0], polygon[1:] | |
| if _in_ring(lon, lat, [p[:2] for p in outer]) and not any(_in_ring(lon, lat, [p[:2] for p in h]) for h in holes): | |
| return True | |
| return False | |
| def haversine_km(lon1: float, lat1: float, lon2: float, lat2: float) -> float: | |
| p1, p2 = math.radians(lat1), math.radians(lat2) | |
| dp, dl = p2 - p1, math.radians(lon2 - lon1) | |
| h = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2 | |
| return 2 * EARTH_RADIUS_KM * math.asin(math.sqrt(h)) | |
| class PolygonIndex: | |
| """Point-in-polygon lookup over many features, with a bbox prefilter so a | |
| lookup only runs the ray-cast against the handful of polygons whose box | |
| contains the point.""" | |
| def __init__(self, features: list[tuple[str, dict]]): | |
| self._items = [(key, geom, bbox(geom)) for key, geom in features] | |
| def lookup(self, lon: float, lat: float) -> str | None: | |
| for key, geom, (x0, y0, x1, y1) in self._items: | |
| if x0 <= lon <= x1 and y0 <= lat <= y1 and contains(geom, lon, lat): | |
| return key | |
| return None | |