Complete operational map result workflow
This commit is contained in:
@@ -11,20 +11,199 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.errors import AppError
|
||||
from app.models import AnalysisRun, Area, Dataset, Export, Project, QualityCheck
|
||||
from app.schemas.export import ExportContentResponse, ExportCreateResponse, ExportListResponse, ExportRead
|
||||
from app.schemas.dhmv import TerrainPartitionSelectionRequest, TerrainSelectionRequest
|
||||
from app.schemas.export import (
|
||||
ExportContentResponse,
|
||||
ExportCreateResponse,
|
||||
ExportListResponse,
|
||||
ExportRead,
|
||||
MapResultExportRequest,
|
||||
)
|
||||
from app.schemas.flood_hazard import FloodHazardPartitionSelectionRequest, FloodHazardSelectionRequest
|
||||
from app.schemas.temporal import TemporalComparisonRequest
|
||||
from app.schemas.thematic_raster import ThematicRasterSelectionRequest
|
||||
from app.services.dataset_service import DatasetService
|
||||
from app.services.detection_service import DetectionService
|
||||
from app.services.flood_hazard_analysis_service import FloodHazardAnalysisService
|
||||
from app.services.segmentation_service import SegmentationService
|
||||
from app.services.storage_service import StorageService
|
||||
from app.services.temporal_analysis_service import TemporalAnalysisService
|
||||
from app.services.terrain_analysis_service import TerrainAnalysisService
|
||||
from app.services.thematic_raster_analysis_service import ThematicRasterAnalysisService
|
||||
from app.services.vector_feature_service import VectorFeatureService
|
||||
|
||||
|
||||
class ExportService:
|
||||
@staticmethod
|
||||
def export_map_result(
|
||||
db: Session,
|
||||
payload: MapResultExportRequest,
|
||||
) -> ExportCreateResponse:
|
||||
if payload.mode == "evolution":
|
||||
comparison = TemporalAnalysisService.compare(
|
||||
db,
|
||||
project_id=payload.project_id,
|
||||
payload=TemporalComparisonRequest(
|
||||
earlier_dataset_id=payload.earlier_dataset_id,
|
||||
later_dataset_id=payload.later_dataset_id,
|
||||
bbox=payload.bbox,
|
||||
area_id=payload.area_id,
|
||||
),
|
||||
)
|
||||
content = comparison.model_dump(mode="json")
|
||||
target_id = str(payload.later_dataset_id)
|
||||
filename = ExportService._filename(
|
||||
payload.name,
|
||||
f"{target_id}-evolution.json",
|
||||
".json",
|
||||
)
|
||||
export_path = StorageService.dataset_export_path(
|
||||
str(payload.project_id),
|
||||
target_id,
|
||||
filename,
|
||||
)
|
||||
metadata = {
|
||||
"source": "map_evolution",
|
||||
"project_id": str(payload.project_id),
|
||||
"earlier_dataset_id": str(payload.earlier_dataset_id),
|
||||
"later_dataset_id": str(payload.later_dataset_id),
|
||||
"selection_bbox": payload.bbox.model_dump(mode="json"),
|
||||
"selection_area_id": str(payload.area_id) if payload.area_id else None,
|
||||
"theme_id": payload.theme_id,
|
||||
"server_recomputed": True,
|
||||
}
|
||||
export = ExportService._write_json_export(
|
||||
db,
|
||||
project_id=payload.project_id,
|
||||
analysis_run_id=None,
|
||||
export_type="map_evolution_json",
|
||||
storage_path=export_path,
|
||||
content=content,
|
||||
metadata=metadata,
|
||||
)
|
||||
return ExportService._create_response(export)
|
||||
|
||||
dataset = db.get(Dataset, payload.dataset_id)
|
||||
if not dataset or dataset.project_id != payload.project_id:
|
||||
raise AppError(code="DATASET_NOT_FOUND", message="Dataset not found", status_code=404)
|
||||
if dataset.dataset_type in DatasetService.VECTOR_TYPES:
|
||||
return ExportService.export_vector_selection_geojson(
|
||||
db,
|
||||
dataset.id,
|
||||
payload.bbox.model_dump(mode="json"),
|
||||
area_id=payload.area_id,
|
||||
name=payload.name,
|
||||
limit=1000,
|
||||
)
|
||||
if dataset.dataset_type != "raster":
|
||||
raise AppError(
|
||||
code="INVALID_DATASET_TYPE",
|
||||
message="Map-result export requires a vector or governed raster dataset",
|
||||
details={"dataset_type": dataset.dataset_type},
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
if dataset.source_name == "digitaal_vlaanderen_dhmv":
|
||||
result = (
|
||||
TerrainAnalysisService.analyze_partitions(
|
||||
db,
|
||||
payload.project_id,
|
||||
TerrainPartitionSelectionRequest(
|
||||
bbox=payload.bbox,
|
||||
area_id=payload.area_id,
|
||||
product_key=payload.product_key or "dtm_1m",
|
||||
),
|
||||
)
|
||||
if payload.partitioned
|
||||
else TerrainAnalysisService.analyze(
|
||||
db,
|
||||
payload.project_id,
|
||||
dataset.id,
|
||||
TerrainSelectionRequest(bbox=payload.bbox, area_id=payload.area_id),
|
||||
)
|
||||
)
|
||||
elif dataset.source_name == "vmm_flood_hazard":
|
||||
result = (
|
||||
FloodHazardAnalysisService.analyze_partitions(
|
||||
db,
|
||||
payload.project_id,
|
||||
FloodHazardPartitionSelectionRequest(
|
||||
bbox=payload.bbox,
|
||||
area_id=payload.area_id,
|
||||
product_key=payload.product_key or "pluviaal_current_t100",
|
||||
),
|
||||
)
|
||||
if payload.partitioned
|
||||
else FloodHazardAnalysisService.analyze(
|
||||
db,
|
||||
payload.project_id,
|
||||
dataset.id,
|
||||
FloodHazardSelectionRequest(bbox=payload.bbox, area_id=payload.area_id),
|
||||
)
|
||||
)
|
||||
elif dataset.source_name == "department_omgeving_thematic_raster":
|
||||
result = ThematicRasterAnalysisService.analyze(
|
||||
db,
|
||||
payload.project_id,
|
||||
dataset.id,
|
||||
ThematicRasterSelectionRequest(bbox=payload.bbox, area_id=payload.area_id),
|
||||
)
|
||||
else:
|
||||
raise AppError(
|
||||
code="MAP_RESULT_EXPORT_UNSUPPORTED",
|
||||
message="This raster source does not expose a governed map-result export",
|
||||
details={"source_name": dataset.source_name},
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
content = {
|
||||
"mode": "current",
|
||||
"theme_id": payload.theme_id,
|
||||
"dataset": {
|
||||
"id": str(dataset.id),
|
||||
"name": dataset.name,
|
||||
"source_name": dataset.source_name,
|
||||
},
|
||||
"result": result,
|
||||
}
|
||||
filename = ExportService._filename(
|
||||
payload.name,
|
||||
f"{dataset.id}-map-analysis.json",
|
||||
".json",
|
||||
)
|
||||
export_path = StorageService.dataset_export_path(
|
||||
str(payload.project_id),
|
||||
str(dataset.id),
|
||||
filename,
|
||||
)
|
||||
metadata = {
|
||||
"source": "map_analysis",
|
||||
"project_id": str(payload.project_id),
|
||||
"dataset_id": str(dataset.id),
|
||||
"selection_bbox": payload.bbox.model_dump(mode="json"),
|
||||
"selection_area_id": str(payload.area_id) if payload.area_id else None,
|
||||
"theme_id": payload.theme_id,
|
||||
"partitioned": payload.partitioned,
|
||||
"product_key": payload.product_key,
|
||||
"server_recomputed": True,
|
||||
}
|
||||
export = ExportService._write_json_export(
|
||||
db,
|
||||
project_id=payload.project_id,
|
||||
analysis_run_id=None,
|
||||
export_type="map_analysis_json",
|
||||
storage_path=export_path,
|
||||
content=content,
|
||||
metadata=metadata,
|
||||
)
|
||||
return ExportService._create_response(export)
|
||||
|
||||
@staticmethod
|
||||
def export_vector_selection_geojson(
|
||||
db: Session,
|
||||
dataset_id: uuid.UUID,
|
||||
bbox: dict[str, Any],
|
||||
area_id: uuid.UUID | None = None,
|
||||
limit: int = 250,
|
||||
name: str | None = None,
|
||||
) -> ExportCreateResponse:
|
||||
@@ -39,7 +218,28 @@ class ExportService:
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
selection = VectorFeatureService.select_features_by_bbox(db, dataset_id=dataset_id, bbox=bbox, limit=limit)
|
||||
selection_kwargs: dict[str, Any] = {
|
||||
"dataset_id": dataset_id,
|
||||
"bbox": bbox,
|
||||
"limit": limit,
|
||||
}
|
||||
if area_id is not None:
|
||||
area = db.get(Area, area_id)
|
||||
if not area or area.project_id != dataset.project_id:
|
||||
raise AppError(code="AREA_NOT_FOUND", message="Area not found", status_code=404)
|
||||
full_dataset_area = VectorFeatureService.can_use_full_area_fast_path(dataset, area.id)
|
||||
preclipped_partition_filter = (
|
||||
None
|
||||
if full_dataset_area
|
||||
else VectorFeatureService.preclipped_partition_filter(dataset, getattr(area, "name", None))
|
||||
)
|
||||
selection_kwargs.update(
|
||||
selection_geometry=area.geometry,
|
||||
selection_area_id=area.id,
|
||||
full_dataset_area=full_dataset_area,
|
||||
preclipped_partition_filter=preclipped_partition_filter,
|
||||
)
|
||||
selection = VectorFeatureService.select_features_by_bbox(db, **selection_kwargs)
|
||||
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 = {
|
||||
@@ -48,6 +248,7 @@ class ExportService:
|
||||
"dataset_id": str(dataset.id),
|
||||
"dataset_type": dataset.dataset_type,
|
||||
"selection_bbox": selection["selection_bbox"],
|
||||
"selection_area_id": selection.get("selection_area_id"),
|
||||
"feature_count": selection["feature_count"],
|
||||
"limit": selection["limit"],
|
||||
"truncated": selection["truncated"],
|
||||
|
||||
Reference in New Issue
Block a user