66 lines
2.5 KiB
Python
66 lines
2.5 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
from geoalchemy2.shape import from_shape
|
|
from shapely.geometry import shape
|
|
from shapely.validation import make_valid
|
|
|
|
from app.core.errors import AppError
|
|
from app.models import VectorFeature
|
|
|
|
|
|
class VectorFeatureService:
|
|
@staticmethod
|
|
def persist_geojson_features(
|
|
db,
|
|
dataset_id: UUID,
|
|
payload: dict[str, Any],
|
|
feature_class: str | None = None,
|
|
*,
|
|
commit: bool = True,
|
|
) -> list[VectorFeature]:
|
|
features = payload.get("features")
|
|
if payload.get("type") != "FeatureCollection" or not isinstance(features, list):
|
|
raise AppError(code="INVALID_GEOJSON", message="GeoJSON payload must be a FeatureCollection", status_code=400)
|
|
|
|
persisted: list[VectorFeature] = []
|
|
for index, feature in enumerate(features):
|
|
if not isinstance(feature, dict):
|
|
raise AppError(code="INVALID_GEOJSON", message=f"Feature {index} must be an object", status_code=400)
|
|
geometry_payload = feature.get("geometry")
|
|
if geometry_payload is None:
|
|
continue
|
|
try:
|
|
geometry = shape(geometry_payload)
|
|
except Exception as exc:
|
|
raise AppError(code="INVALID_GEOJSON", message=f"Invalid feature geometry at index {index}", status_code=400) from exc
|
|
if geometry.is_empty:
|
|
continue
|
|
if not geometry.is_valid:
|
|
geometry = make_valid(geometry)
|
|
if geometry.is_empty or not geometry.is_valid:
|
|
raise AppError(code="INVALID_GEOMETRY", message=f"Invalid feature geometry at index {index}", status_code=400)
|
|
|
|
properties = feature.get("properties") if isinstance(feature.get("properties"), dict) else {}
|
|
source_feature_id = feature.get("id")
|
|
if source_feature_id is None:
|
|
source_feature_id = properties.get("id") or properties.get("source_feature_id")
|
|
|
|
row = VectorFeature(
|
|
dataset_id=dataset_id,
|
|
feature_class=feature_class,
|
|
source_feature_id=str(source_feature_id) if source_feature_id is not None else None,
|
|
properties_json=properties,
|
|
geometry=from_shape(geometry, srid=4326),
|
|
)
|
|
db.add(row)
|
|
persisted.append(row)
|
|
|
|
if commit:
|
|
db.commit()
|
|
for row in persisted:
|
|
db.refresh(row)
|
|
return persisted
|