85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
from __future__ import annotations
|
|
|
|
import uuid
|
|
|
|
from sqlalchemy.orm import Session
|
|
from geoalchemy2.shape import from_shape, to_shape
|
|
from shapely.geometry import mapping
|
|
|
|
from app.core.errors import AppError
|
|
from app.models import Area, Project
|
|
from app.schemas.area import AreaCreate, AreaRead, AreaUpdate
|
|
from app.utils.geometry import area_m2, geometry_bbox_polygon, normalize_to_multipolygon
|
|
|
|
|
|
class AreaService:
|
|
@staticmethod
|
|
def serialize_area(area: Area) -> dict:
|
|
payload = AreaRead.model_validate(area).model_dump()
|
|
payload["geometry"] = mapping(to_shape(area.geometry)) if area.geometry else None
|
|
return payload
|
|
|
|
@staticmethod
|
|
def list_areas(db: Session, project_id: uuid.UUID, limit: int = 50, offset: int = 0) -> tuple[list[Area], int]:
|
|
total = db.query(Area).filter(Area.project_id == project_id).count()
|
|
areas = (
|
|
db.query(Area)
|
|
.filter(Area.project_id == project_id)
|
|
.order_by(Area.created_at.desc())
|
|
.offset(offset)
|
|
.limit(limit)
|
|
.all()
|
|
)
|
|
return areas, total
|
|
|
|
@staticmethod
|
|
def create_area(db: Session, project_id: uuid.UUID, payload: AreaCreate) -> Area:
|
|
if not db.get(Project, project_id):
|
|
raise AppError(code="PROJECT_NOT_FOUND", message="Project not found", status_code=404)
|
|
|
|
try:
|
|
multipolygon = normalize_to_multipolygon(payload.geometry)
|
|
except ValueError as exc:
|
|
raise AppError(code="INVALID_GEOMETRY", message=str(exc), status_code=400) from exc
|
|
|
|
area = Area(
|
|
project_id=project_id,
|
|
name=payload.name.strip() or "Unnamed area",
|
|
geometry=from_shape(multipolygon, srid=4326),
|
|
original_crs=payload.crs or "EPSG:4326",
|
|
area_m2=area_m2(multipolygon),
|
|
bbox=from_shape(geometry_bbox_polygon(multipolygon), srid=4326),
|
|
)
|
|
db.add(area)
|
|
db.commit()
|
|
db.refresh(area)
|
|
return area
|
|
|
|
@staticmethod
|
|
def get_area(db: Session, area_id: uuid.UUID) -> Area:
|
|
area = db.get(Area, area_id)
|
|
if not area:
|
|
raise AppError(code="AREA_NOT_FOUND", message="Area not found", status_code=404)
|
|
return area
|
|
|
|
@staticmethod
|
|
def update_area(db: Session, area_id: uuid.UUID, payload: AreaUpdate) -> Area:
|
|
area = db.get(Area, area_id)
|
|
if not area:
|
|
raise AppError(code="AREA_NOT_FOUND", message="Area not found", status_code=404)
|
|
|
|
changed = False
|
|
if payload.name:
|
|
area.name = payload.name.strip() or area.name
|
|
changed = True
|
|
if payload.crs:
|
|
area.original_crs = payload.crs
|
|
changed = True
|
|
if not changed:
|
|
raise AppError(code="NO_CHANGES", message="No updatable fields provided", status_code=422)
|
|
|
|
db.add(area)
|
|
db.commit()
|
|
db.refresh(area)
|
|
return area
|