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.")