site-intelligence-studio / src /geojson_parser.py
Eishaan's picture
Harden uploads and map fallback
efcf0a3
Raw
History Blame Contribute Delete
3.02 kB
from __future__ import annotations
import json
import uuid
from pathlib import Path
from typing import Any
from .geometry import calculate_site_metrics
from .models import SiteSelection
def parse_geojson_file(path: str | Path) -> SiteSelection:
source = Path(path)
data = json.loads(source.read_text(encoding="utf-8"))
if not isinstance(data, dict):
raise ValueError("GeoJSON root must be a Feature, FeatureCollection, Polygon, or MultiPolygon object.")
geometry = _extract_geometry(data)
metrics = calculate_site_metrics(geometry)
return SiteSelection(
id=f"S-{uuid.uuid4().hex[:8]}",
selection_type="geojson_boundary",
coordinate_mode="wgs84",
geometry_geojson=geometry,
local_geometry=None,
anchor_lat=metrics["centroid"][0],
anchor_lon=metrics["centroid"][1],
radius_m=None,
area_sqm=metrics["area_sqm"],
perimeter_m=metrics["perimeter_m"],
centroid=metrics["centroid"],
bbox=metrics["bbox"],
unit_source="WGS84 GeoJSON",
accuracy_label="uploaded georeferenced boundary",
source_files=[source.name],
selected_boundary_id=None,
limitations=[
"GeoJSON is treated as WGS84 if coordinates look like longitude/latitude.",
"Verify boundary with the original source drawing or site survey.",
],
)
def _extract_geometry(data: dict[str, Any]) -> dict[str, Any]:
if data.get("type") == "Feature":
geometry = data.get("geometry")
elif data.get("type") == "FeatureCollection":
features = data.get("features") or []
if not features:
raise ValueError("GeoJSON FeatureCollection has no features.")
geometry = features[0].get("geometry")
else:
geometry = data
if not isinstance(geometry, dict) or geometry.get("type") not in {"Polygon", "MultiPolygon"}:
raise ValueError("Only Polygon and MultiPolygon GeoJSON are supported.")
_validate_wgs84(geometry)
return geometry
def _validate_wgs84(geometry: dict[str, Any]) -> None:
try:
if geometry.get("type") == "Polygon":
points = geometry["coordinates"][0]
else:
points = geometry["coordinates"][0][0]
except (KeyError, IndexError, TypeError) as exc:
raise ValueError("GeoJSON polygon coordinates are missing or malformed.") from exc
if not isinstance(points, list) or len(points) < 4:
raise ValueError("GeoJSON polygon needs at least three vertices and a closed ring.")
for point in points[:50]:
try:
lon, lat = point[:2]
lon_f = float(lon)
lat_f = float(lat)
except (TypeError, ValueError) as exc:
raise ValueError("GeoJSON coordinates must be longitude/latitude number pairs.") from exc
if not (-180 <= lon_f <= 180 and -90 <= lat_f <= 90):
raise ValueError("GeoJSON coordinates do not look like WGS84 longitude/latitude.")