File size: 3,015 Bytes
1b141db
 
 
 
 
 
 
 
 
 
 
 
 
 
efcf0a3
 
1b141db
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
efcf0a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1b141db
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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.")