""" Data model — unified internal representation for all coordinate data. """ from dataclasses import dataclass, field from typing import Any @dataclass class CoordPoint: x: float y: float z: float | None = None attrs: dict[str, Any] = field(default_factory=dict) @dataclass class CoordData: """Unified container for all coordinate data regardless of source format.""" points: list[CoordPoint] = field(default_factory=list) crs: str | None = None # e.g. "EPSG:4326" columns: list[str] = field(default_factory=list) # all column names geometry_type: str = "Point" # Point / LineString / Polygon extra_meta: dict[str, Any] = field(default_factory=dict) @property def count(self) -> int: return len(self.points) def to_records(self) -> list[dict[str, Any]]: """Convert to list of flat dicts for DataFrame/Excel export.""" records = [] for pt in self.points: row = dict(pt.attrs) # Always put X, Y (and Z) at front row["X"] = pt.x row["Y"] = pt.y if pt.z is not None: row["Z"] = pt.z records.append(row) return records @classmethod def from_records(cls, records: list[dict], crs: str | None = None, x_col: str = "X", y_col: str = "Y", z_col: str | None = "Z", geometry_type: str = "Point") -> "CoordData": points = [] for rec in records: pt = CoordPoint( x=float(rec[x_col]), y=float(rec[y_col]), z=float(rec[z_col]) if z_col and z_col in rec else None, attrs={k: v for k, v in rec.items() if k not in (x_col, y_col, z_col)}, ) points.append(pt) cols = list(records[0].keys()) if records else [] return cls(points=points, crs=crs, columns=cols, geometry_type=geometry_type)