Persist map area selection exports
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-25 02:12:09 +02:00
parent f773225b98
commit 46fe3edfc5
15 changed files with 359 additions and 7 deletions
+10
View File
@@ -16,6 +16,16 @@ router = APIRouter(prefix="/exports", tags=["exports"])
@router.post("/geojson", response_model=dict)
def export_geojson(payload: GeoJsonExportRequest, db: Session = Depends(get_db)):
if payload.export_kind == "vector_selection" and payload.dataset_id is not None and payload.bbox is not None:
return envelope(
ExportService.export_vector_selection_geojson(
db,
payload.dataset_id,
payload.bbox.model_dump(),
limit=payload.limit,
name=payload.name,
).model_dump(mode="json")
)
if payload.export_kind == "detection_run" and payload.analysis_run_id is not None:
return envelope(
ExportService.export_detection_run_geojson(db, payload.analysis_run_id, payload.name).model_dump(mode="json")
+10 -1
View File
@@ -6,8 +6,10 @@ from uuid import UUID
from pydantic import BaseModel, model_validator
from app.schemas.operations import VectorSelectionBBox
ExportKind = Literal["dataset", "detection_run", "segmentation_run"]
ExportKind = Literal["dataset", "detection_run", "segmentation_run", "vector_selection"]
class GeoJsonExportRequest(BaseModel):
@@ -15,11 +17,18 @@ class GeoJsonExportRequest(BaseModel):
analysis_run_id: UUID | None = None
export_kind: ExportKind = "dataset"
name: str | None = None
bbox: VectorSelectionBBox | None = None
limit: int = 250
@model_validator(mode="after")
def validate_target(self) -> "GeoJsonExportRequest":
if self.export_kind == "dataset" and self.dataset_id is None:
raise ValueError("dataset_id is required for dataset GeoJSON exports")
if self.export_kind == "vector_selection":
if self.dataset_id is None:
raise ValueError("dataset_id is required for vector selection GeoJSON exports")
if self.bbox is None:
raise ValueError("bbox is required for vector selection GeoJSON exports")
if self.export_kind in {"detection_run", "segmentation_run"} and self.analysis_run_id is None:
raise ValueError("analysis_run_id is required for run GeoJSON exports")
return self
+45
View File
@@ -16,9 +16,54 @@ from app.services.dataset_service import DatasetService
from app.services.detection_service import DetectionService
from app.services.segmentation_service import SegmentationService
from app.services.storage_service import StorageService
from app.services.vector_feature_service import VectorFeatureService
class ExportService:
@staticmethod
def export_vector_selection_geojson(
db: Session,
dataset_id: uuid.UUID,
bbox: dict[str, Any],
limit: int = 250,
name: str | None = None,
) -> ExportCreateResponse:
dataset = db.get(Dataset, dataset_id)
if not dataset:
raise AppError(code="DATASET_NOT_FOUND", message="Dataset not found", status_code=404)
if dataset.dataset_type not in DatasetService.VECTOR_TYPES:
raise AppError(
code="INVALID_DATASET_TYPE",
message="Vector selection export requires a vector dataset",
details={"dataset_type": dataset.dataset_type},
status_code=400,
)
selection = VectorFeatureService.select_features_by_bbox(db, dataset_id=dataset_id, bbox=bbox, limit=limit)
filename = ExportService._filename(name, f"{dataset.id}-selection.geojson", ".geojson")
export_path = StorageService.dataset_export_path(str(dataset.project_id), str(dataset.id), filename)
metadata = {
"source": "vector_selection",
"project_id": str(dataset.project_id),
"dataset_id": str(dataset.id),
"dataset_type": dataset.dataset_type,
"selection_bbox": selection["selection_bbox"],
"feature_count": selection["feature_count"],
"limit": selection["limit"],
"truncated": selection["truncated"],
"source_table": "vector_features",
}
export = ExportService._write_json_export(
db,
project_id=dataset.project_id,
analysis_run_id=None,
export_type="vector_selection_geojson",
storage_path=export_path,
content=selection["geojson"],
metadata=metadata,
)
return ExportService._create_response(export)
@staticmethod
def export_dataset_geojson(db: Session, dataset_id: uuid.UUID, name: str | None = None) -> ExportCreateResponse:
dataset = db.get(Dataset, dataset_id)