1233 lines
40 KiB
Python
1233 lines
40 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import datetime
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, Response
|
|
from fastapi import UploadFile
|
|
from sqlalchemy.orm import Session
|
|
from app.models import Area, Project
|
|
|
|
from app.core.errors import AppError
|
|
from app.db.session import get_db
|
|
from app.schemas import (
|
|
BathymetryPartitionFinalizationResult,
|
|
BathymetrySourceProbeRead,
|
|
BathymetrySourceRead,
|
|
DatasetList,
|
|
DhmvProductRead,
|
|
Envelope,
|
|
FloodHazardProductRead,
|
|
FloodHazardSelectionResponse,
|
|
GeoJsonFeatureCollection,
|
|
GrbProductRead,
|
|
GrbRefreshPlan,
|
|
ItemList,
|
|
JobRead,
|
|
OfficialVectorProductRead,
|
|
OrthophotoProductRead,
|
|
RasterMetadataResponse,
|
|
RasterOperationResult,
|
|
RasterPreviewResponse,
|
|
RasterClipRequest,
|
|
RasterStatsResponse,
|
|
RasterReprojectRequest,
|
|
RasterTileRequest,
|
|
RasterNdviRequest,
|
|
RasterNdwiRequest,
|
|
RasterNdbiRequest,
|
|
OrthophotoAcquireRequest,
|
|
DhmvAcquireRequest,
|
|
TerrainPartitionSelectionRequest,
|
|
TerrainSelectionResponse,
|
|
TerrainSelectionRequest,
|
|
FloodHazardAcquireRequest,
|
|
FloodHazardPartitionSelectionRequest,
|
|
FloodHazardSelectionRequest,
|
|
BathymetryPartitionFinalizeRequest,
|
|
BathymetryProfileAcquireRequest,
|
|
BathymetryRasterSelectionRequest,
|
|
BathymetryRasterSelectionResponse,
|
|
MdkBathymetryAcquireRequest,
|
|
ThematicRasterAcquireRequest,
|
|
ThematicRasterProductRead,
|
|
ThematicRasterSelectionResponse,
|
|
ThematicRasterSelectionRequest,
|
|
GrbAcquireRequest,
|
|
OfficialVectorAcquireRequest,
|
|
VectorBBoxResponse,
|
|
VectorBufferRequest,
|
|
VectorClipRequest,
|
|
VectorIntersectRequest,
|
|
VectorOperationResult,
|
|
VectorSelectionBBox, # noqa: F401 - retained as a route-module compatibility export
|
|
VectorSelectionDeriveRequest,
|
|
VectorSelectionRequest,
|
|
VectorSelectionResponse,
|
|
VectorStatsResponse,
|
|
)
|
|
from app.schemas.dataset import (
|
|
DatasetCreateResponse,
|
|
DatasetTemporalUpdate,
|
|
DatasetVectorSummary,
|
|
DatasetVersionRead,
|
|
)
|
|
from app.schemas.source_catalog import SourceCatalogProbeReport
|
|
from app.schemas.source_freshness import SourceFreshnessReport
|
|
from app.services.job_service import JobService
|
|
from app.services.raster_operations_service import RasterOperationsService
|
|
from app.services.vector_operations_service import VectorOperationsService
|
|
from app.services.vector_feature_service import VectorFeatureService
|
|
from app.services.dataset_service import DatasetService
|
|
from app.services.source_freshness_service import SourceFreshnessService
|
|
from app.services.source_catalog_probe_service import SourceCatalogProbeService
|
|
from app.services.grb_refresh_plan_service import GrbRefreshPlanService
|
|
from app.services.grb_acquisition_service import GrbAcquisitionService
|
|
from app.services.official_vector_acquisition_service import OfficialVectorAcquisitionService
|
|
from app.services.orthophoto_acquisition_service import OrthophotoAcquisitionService
|
|
from app.services.dhmv_acquisition_service import DhmvAcquisitionService
|
|
from app.services.terrain_analysis_service import TerrainAnalysisService
|
|
from app.services.flood_hazard_acquisition_service import FloodHazardAcquisitionService
|
|
from app.services.flood_hazard_analysis_service import FloodHazardAnalysisService
|
|
from app.services.bathymetry_profile_acquisition_service import BathymetryProfileAcquisitionService
|
|
from app.services.bathymetry_raster_analysis_service import BathymetryRasterAnalysisService
|
|
from app.services.mdk_bathymetry_acquisition_service import MdkBathymetryAcquisitionService
|
|
from app.services.mdk_bathymetry_probe_service import MdkBathymetryProbeService
|
|
from app.services.thematic_raster_acquisition_service import ThematicRasterAcquisitionService
|
|
from app.services.thematic_raster_analysis_service import ThematicRasterAnalysisService
|
|
from app.utils.response import envelope
|
|
|
|
router = APIRouter(prefix="/projects/{project_id}", tags=["datasets"])
|
|
|
|
|
|
def _parse_metadata_json(raw: str | None, field_name: str) -> dict | None:
|
|
if raw is None:
|
|
return None
|
|
raw = raw.strip()
|
|
if not raw:
|
|
return None
|
|
try:
|
|
value = json.loads(raw)
|
|
except json.JSONDecodeError as exc:
|
|
raise AppError(code="INVALID_JSON", message=f"Invalid JSON for {field_name}", details={"field": field_name}, status_code=400) from exc
|
|
if not isinstance(value, dict):
|
|
raise AppError(code="INVALID_JSON", message=f"{field_name} must be a JSON object", details={"field": field_name}, status_code=400)
|
|
return value
|
|
|
|
|
|
def _run_job_sync(
|
|
db: Session,
|
|
project_id: UUID,
|
|
input_dataset_id: UUID,
|
|
job_type: str,
|
|
parameters: dict[str, Any],
|
|
operation,
|
|
) -> dict[str, Any]:
|
|
return JobService.run_sync_job(
|
|
db=db,
|
|
project_id=project_id,
|
|
job_type=job_type,
|
|
parameters=parameters,
|
|
operation=operation,
|
|
input_dataset_id=input_dataset_id,
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/datasets/upload",
|
|
status_code=201,
|
|
response_model=Envelope[DatasetCreateResponse],
|
|
)
|
|
async def upload_dataset(
|
|
project_id: UUID,
|
|
file: UploadFile = File(...),
|
|
dataset_type: str = Form(...),
|
|
source: str = Form("user_upload"),
|
|
area_id: UUID | None = Form(None),
|
|
dataset_role: str = Form("source"),
|
|
source_name: str | None = Form(None),
|
|
reference_layer_name: str | None = Form(None),
|
|
source_metadata_json: str | None = Form(None),
|
|
provenance_metadata_json: str | None = Form(None),
|
|
temporal_series_key: str | None = Form(None),
|
|
observed_at: datetime | None = Form(None),
|
|
valid_from: datetime | None = Form(None),
|
|
valid_to: datetime | None = Form(None),
|
|
temporal_granularity: str | None = Form(None),
|
|
source_version: str | None = Form(None),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
if area_id is not None:
|
|
area = db.get(Area, area_id)
|
|
if not area:
|
|
raise AppError(code="AREA_NOT_FOUND", message="Area not found", status_code=404)
|
|
if area.project_id != project_id:
|
|
raise AppError(code="INVALID_DATASET_SCOPE", message="Area does not belong to this project", status_code=400)
|
|
|
|
created = await DatasetService.upload_dataset(
|
|
db,
|
|
project_id=project_id,
|
|
file=file,
|
|
dataset_type=dataset_type,
|
|
source=source,
|
|
dataset_role=dataset_role,
|
|
source_name=source_name,
|
|
reference_layer_name=reference_layer_name,
|
|
source_metadata=_parse_metadata_json(source_metadata_json, "source_metadata_json"),
|
|
provenance_metadata=_parse_metadata_json(provenance_metadata_json, "provenance_metadata_json"),
|
|
area_id=area_id,
|
|
temporal_series_key=temporal_series_key,
|
|
observed_at=observed_at,
|
|
valid_from=valid_from,
|
|
valid_to=valid_to,
|
|
temporal_granularity=temporal_granularity,
|
|
source_version=source_version,
|
|
)
|
|
return envelope(created.model_dump())
|
|
|
|
|
|
@router.post("/datasets/orthophoto/acquire", response_model=Envelope[JobRead])
|
|
def acquire_bounded_orthophoto(
|
|
project_id: UUID,
|
|
payload: OrthophotoAcquireRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
job = JobService.run_sync_job(
|
|
db=db,
|
|
project_id=project_id,
|
|
job_type="raster.orthophoto.acquire",
|
|
parameters=payload.model_dump(mode="json"),
|
|
operation=lambda: OrthophotoAcquisitionService.acquire(db, project_id, payload),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.get(
|
|
"/datasets/orthophoto/products",
|
|
response_model=Envelope[ItemList[OrthophotoProductRead]],
|
|
)
|
|
def list_orthophoto_products(project_id: UUID, db: Session = Depends(get_db)):
|
|
if not db.get(Project, project_id):
|
|
raise AppError(code="PROJECT_NOT_FOUND", message="Project not found", status_code=404)
|
|
items = OrthophotoAcquisitionService.list_products()
|
|
return envelope({"items": items, "total": len(items)})
|
|
|
|
|
|
@router.post("/datasets/dhmv/acquire", response_model=Envelope[JobRead])
|
|
def acquire_bounded_dhmv(
|
|
project_id: UUID,
|
|
payload: DhmvAcquireRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
job = JobService.run_sync_job(
|
|
db=db,
|
|
project_id=project_id,
|
|
job_type="raster.dhmv.acquire",
|
|
parameters=payload.model_dump(mode="json"),
|
|
operation=lambda: DhmvAcquisitionService.acquire(db, project_id, payload),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.get(
|
|
"/datasets/dhmv/products",
|
|
response_model=Envelope[ItemList[DhmvProductRead]],
|
|
)
|
|
def list_dhmv_products(project_id: UUID, db: Session = Depends(get_db)):
|
|
if not db.get(Project, project_id):
|
|
raise AppError(code="PROJECT_NOT_FOUND", message="Project not found", status_code=404)
|
|
items = DhmvAcquisitionService.list_products()
|
|
return envelope({"items": items, "total": len(items)})
|
|
|
|
|
|
@router.post("/datasets/grb/acquire", response_model=Envelope[JobRead])
|
|
def acquire_bounded_grb(
|
|
project_id: UUID,
|
|
payload: GrbAcquireRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
job = JobService.run_sync_job(
|
|
db=db,
|
|
project_id=project_id,
|
|
job_type="vector.grb.acquire",
|
|
parameters=payload.model_dump(mode="json"),
|
|
operation=lambda: GrbAcquisitionService.acquire(db, project_id, payload),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.get(
|
|
"/datasets/grb/products",
|
|
response_model=Envelope[ItemList[GrbProductRead]],
|
|
)
|
|
def list_grb_products(project_id: UUID, db: Session = Depends(get_db)):
|
|
if not db.get(Project, project_id):
|
|
raise AppError(code="PROJECT_NOT_FOUND", message="Project not found", status_code=404)
|
|
items = GrbAcquisitionService.list_products()
|
|
return envelope({"items": items, "total": len(items)})
|
|
|
|
|
|
@router.post("/datasets/official-vector/acquire", response_model=Envelope[JobRead])
|
|
def acquire_bounded_official_vector(
|
|
project_id: UUID,
|
|
payload: OfficialVectorAcquireRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
job = JobService.run_sync_job(
|
|
db=db,
|
|
project_id=project_id,
|
|
job_type="vector.official.acquire",
|
|
parameters=payload.model_dump(mode="json"),
|
|
operation=lambda: OfficialVectorAcquisitionService.acquire(db, project_id, payload),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.get(
|
|
"/datasets/official-vector/products",
|
|
response_model=Envelope[ItemList[OfficialVectorProductRead]],
|
|
)
|
|
def list_official_vector_products(project_id: UUID, db: Session = Depends(get_db)):
|
|
if not db.get(Project, project_id):
|
|
raise AppError(code="PROJECT_NOT_FOUND", message="Project not found", status_code=404)
|
|
items = OfficialVectorAcquisitionService.list_products()
|
|
return envelope({"items": items, "total": len(items)})
|
|
|
|
|
|
@router.post("/datasets/flood-hazard/acquire", response_model=Envelope[JobRead])
|
|
def acquire_bounded_flood_hazard(
|
|
project_id: UUID,
|
|
payload: FloodHazardAcquireRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
job = JobService.run_sync_job(
|
|
db=db,
|
|
project_id=project_id,
|
|
job_type="raster.flood_hazard.acquire",
|
|
parameters=payload.model_dump(mode="json"),
|
|
operation=lambda: FloodHazardAcquisitionService.acquire(db, project_id, payload),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.get(
|
|
"/datasets/flood-hazard/products",
|
|
response_model=Envelope[ItemList[FloodHazardProductRead]],
|
|
)
|
|
def list_flood_hazard_products(project_id: UUID, db: Session = Depends(get_db)):
|
|
if not db.get(Project, project_id):
|
|
raise AppError(code="PROJECT_NOT_FOUND", message="Project not found", status_code=404)
|
|
items = FloodHazardAcquisitionService.list_products()
|
|
return envelope({"items": items, "total": len(items)})
|
|
|
|
|
|
@router.get(
|
|
"/datasets/bathymetry/sources",
|
|
response_model=Envelope[ItemList[BathymetrySourceRead]],
|
|
)
|
|
def list_bathymetry_sources(project_id: UUID, db: Session = Depends(get_db)):
|
|
if not db.get(Project, project_id):
|
|
raise AppError(code="PROJECT_NOT_FOUND", message="Project not found", status_code=404)
|
|
items = BathymetryProfileAcquisitionService.list_sources()
|
|
return envelope({"items": items, "total": len(items)})
|
|
|
|
|
|
@router.get(
|
|
"/datasets/bathymetry/sources/mdk_bcp_bathymetry/readiness",
|
|
response_model=Envelope[BathymetrySourceProbeRead],
|
|
)
|
|
def probe_mdk_bathymetry_readiness(project_id: UUID, db: Session = Depends(get_db)):
|
|
if not db.get(Project, project_id):
|
|
raise AppError(code="PROJECT_NOT_FOUND", message="Project not found", status_code=404)
|
|
return envelope(MdkBathymetryProbeService.probe())
|
|
|
|
|
|
@router.post(
|
|
"/datasets/bathymetry/mdk/acquire",
|
|
response_model=Envelope[JobRead],
|
|
)
|
|
def acquire_bounded_mdk_bathymetry(
|
|
project_id: UUID,
|
|
payload: MdkBathymetryAcquireRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
job = JobService.run_sync_job(
|
|
db=db,
|
|
project_id=project_id,
|
|
job_type="raster.mdk_bathymetry.acquire",
|
|
parameters=payload.model_dump(mode="json"),
|
|
operation=lambda: MdkBathymetryAcquisitionService.acquire(db, project_id, payload),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.post(
|
|
"/datasets/bathymetry/profiles/acquire",
|
|
response_model=Envelope[JobRead],
|
|
)
|
|
def acquire_bounded_bathymetry_profiles(
|
|
project_id: UUID,
|
|
payload: BathymetryProfileAcquireRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
job = JobService.run_sync_job(
|
|
db=db,
|
|
project_id=project_id,
|
|
job_type="vector.bathymetry_profiles.acquire",
|
|
parameters=payload.model_dump(mode="json"),
|
|
operation=lambda: BathymetryProfileAcquisitionService.acquire(db, project_id, payload),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.post(
|
|
"/datasets/bathymetry/profiles/partitions/finalize",
|
|
response_model=Envelope[BathymetryPartitionFinalizationResult],
|
|
)
|
|
def finalize_bathymetry_profile_partitions(
|
|
project_id: UUID,
|
|
payload: BathymetryPartitionFinalizeRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
return envelope(BathymetryProfileAcquisitionService.finalize_partitions(db, project_id, payload))
|
|
|
|
|
|
@router.post(
|
|
"/datasets/bathymetry/profiles/partitions/select",
|
|
response_model=Envelope[VectorSelectionResponse],
|
|
)
|
|
def select_bathymetry_profile_partitions(
|
|
project_id: UUID,
|
|
payload: VectorSelectionRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
selection_geometry = None
|
|
selection_area_id = None
|
|
partition_area_id = None
|
|
if payload.area_id is not None:
|
|
selection_area = db.get(Area, payload.area_id)
|
|
if selection_area is None or selection_area.project_id != project_id:
|
|
raise AppError(code="AREA_NOT_FOUND", message="Area not found", status_code=404)
|
|
selection_geometry, _covers_full_area = VectorFeatureService.constrain_bbox_to_area(
|
|
payload.bbox.model_dump(),
|
|
selection_area.geometry,
|
|
)
|
|
selection_area_id = selection_area.id
|
|
if str(selection_area.name or "").lower().startswith("gemeente "):
|
|
partition_area_id = selection_area.id
|
|
|
|
result = VectorFeatureService.select_partitioned_features_by_bbox(
|
|
db,
|
|
project_id=project_id,
|
|
source_name=BathymetryProfileAcquisitionService.PROVIDER,
|
|
partition_scope_key="flanders",
|
|
bbox=payload.bbox.model_dump(),
|
|
limit=payload.limit,
|
|
selection_geometry=selection_geometry,
|
|
selection_area_id=selection_area_id,
|
|
partition_area_id=partition_area_id,
|
|
)
|
|
return envelope(VectorSelectionResponse(**result).model_dump(exclude_none=True))
|
|
|
|
|
|
@router.post("/datasets/thematic-raster/acquire", response_model=Envelope[JobRead])
|
|
def acquire_bounded_thematic_raster(
|
|
project_id: UUID,
|
|
payload: ThematicRasterAcquireRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
job = JobService.run_sync_job(
|
|
db=db,
|
|
project_id=project_id,
|
|
job_type="raster.thematic.acquire",
|
|
parameters=payload.model_dump(mode="json"),
|
|
operation=lambda: ThematicRasterAcquisitionService.acquire(db, project_id, payload),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.get(
|
|
"/datasets/thematic-raster/products",
|
|
response_model=Envelope[ItemList[ThematicRasterProductRead]],
|
|
)
|
|
def list_thematic_raster_products(project_id: UUID, db: Session = Depends(get_db)):
|
|
if not db.get(Project, project_id):
|
|
raise AppError(code="PROJECT_NOT_FOUND", message="Project not found", status_code=404)
|
|
items = ThematicRasterAcquisitionService.list_products()
|
|
return envelope({"items": items, "total": len(items)})
|
|
|
|
|
|
@router.get("/datasets", response_model=Envelope[DatasetList])
|
|
def list_datasets(
|
|
project_id: UUID,
|
|
limit: int = Query(default=50, ge=1, le=200),
|
|
offset: int = Query(default=0, ge=0),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
datasets, total = DatasetService.list_datasets(db, project_id, limit=limit, offset=offset)
|
|
return envelope({"items": [item.model_dump() for item in datasets], "total": total, "limit": limit, "offset": offset})
|
|
|
|
|
|
@router.get(
|
|
"/datasets/source-freshness",
|
|
response_model=Envelope[SourceFreshnessReport],
|
|
)
|
|
def audit_dataset_source_freshness(
|
|
project_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
report = SourceFreshnessService.audit_project(db, project_id)
|
|
return envelope(report.model_dump())
|
|
|
|
|
|
@router.get(
|
|
"/datasets/source-catalog-probes",
|
|
response_model=Envelope[SourceCatalogProbeReport],
|
|
)
|
|
def probe_dataset_source_catalogs(
|
|
project_id: UUID,
|
|
refresh: bool = Query(default=False),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
report = SourceCatalogProbeService.audit_project(db, project_id, force=refresh)
|
|
return envelope(report.model_dump())
|
|
|
|
|
|
@router.get(
|
|
"/datasets/grb-refresh-plan",
|
|
response_model=Envelope[GrbRefreshPlan],
|
|
)
|
|
def plan_grb_dataset_refresh(
|
|
project_id: UUID,
|
|
scope: str = Query(default=GrbRefreshPlanService.SCOPE),
|
|
refresh_catalog: bool = Query(default=False),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
report = GrbRefreshPlanService.build(
|
|
db,
|
|
project_id,
|
|
scope=scope,
|
|
refresh_catalog=refresh_catalog,
|
|
)
|
|
return envelope(report.model_dump())
|
|
|
|
|
|
@router.get("/datasets/{dataset_id}", response_model=Envelope[DatasetCreateResponse])
|
|
def get_dataset(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
return envelope(DatasetCreateResponse.model_validate(dataset).model_dump())
|
|
|
|
|
|
@router.patch(
|
|
"/datasets/{dataset_id}/temporal",
|
|
response_model=Envelope[DatasetCreateResponse],
|
|
)
|
|
def update_dataset_temporal_metadata(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: DatasetTemporalUpdate,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
updated = DatasetService.update_temporal_metadata(db, dataset_id, payload)
|
|
return envelope(updated.model_dump())
|
|
|
|
|
|
@router.get(
|
|
"/datasets/{dataset_id}/versions",
|
|
response_model=Envelope[ItemList[DatasetVersionRead]],
|
|
)
|
|
def list_dataset_versions(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
versions = DatasetService.list_versions(db, dataset_id)
|
|
return envelope({"items": [item.model_dump() for item in versions], "total": len(versions)})
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/metadata/refresh",
|
|
response_model=Envelope[DatasetCreateResponse],
|
|
)
|
|
def refresh_dataset_metadata(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
refreshed = DatasetService.refresh_metadata(db, dataset_id)
|
|
return envelope(refreshed.model_dump())
|
|
|
|
|
|
@router.get(
|
|
"/datasets/{dataset_id}/vector/inspect",
|
|
response_model=Envelope[VectorOperationResult],
|
|
)
|
|
def inspect_vector_dataset(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
return envelope(VectorOperationsService.inspect(db, dataset_id).model_dump())
|
|
|
|
|
|
@router.get(
|
|
"/datasets/{dataset_id}/vector/bbox",
|
|
response_model=Envelope[VectorBBoxResponse],
|
|
)
|
|
def vector_bbox(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
payload = VectorOperationsService.bbox(db, dataset_id)
|
|
return envelope(VectorBBoxResponse(**payload).model_dump())
|
|
|
|
|
|
@router.get(
|
|
"/datasets/{dataset_id}/vector/stats",
|
|
response_model=Envelope[VectorStatsResponse],
|
|
)
|
|
def vector_stats(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
return envelope(VectorOperationsService.stats(db, dataset_id))
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/vector/select",
|
|
response_model=Envelope[VectorSelectionResponse],
|
|
)
|
|
def select_vector_features(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: VectorSelectionRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
if dataset.dataset_type not in {"vector", "geojson"}:
|
|
raise AppError(code="DATASET_NOT_VECTOR", message="Area selection requires a vector dataset", status_code=400)
|
|
selection_area = None
|
|
if payload.area_id is not None:
|
|
selection_area = db.get(Area, payload.area_id)
|
|
if selection_area is None or selection_area.project_id != project_id:
|
|
raise AppError(code="AREA_NOT_FOUND", message="Area not found", status_code=404)
|
|
|
|
selection_kwargs = {
|
|
"dataset_id": dataset_id,
|
|
"bbox": payload.bbox.model_dump(),
|
|
"limit": payload.limit,
|
|
}
|
|
full_dataset_area = False
|
|
preclipped_partition_filter = None
|
|
if selection_area is not None:
|
|
selection_geometry, covers_full_area = VectorFeatureService.constrain_bbox_to_area(
|
|
payload.bbox.model_dump(),
|
|
selection_area.geometry,
|
|
)
|
|
dataset_is_preclipped_to_area = VectorFeatureService.can_use_full_area_fast_path(
|
|
dataset,
|
|
selection_area.id,
|
|
)
|
|
full_dataset_area = covers_full_area and dataset_is_preclipped_to_area
|
|
preclipped_partition_filter = VectorFeatureService.preclipped_partition_filter(
|
|
dataset,
|
|
getattr(selection_area, "name", None),
|
|
)
|
|
selection_kwargs.update(
|
|
selection_geometry=None if dataset_is_preclipped_to_area else selection_geometry,
|
|
selection_area_id=selection_area.id,
|
|
full_dataset_area=full_dataset_area,
|
|
preclipped_partition_filter=preclipped_partition_filter,
|
|
)
|
|
result = VectorFeatureService.select_features_by_bbox(db, **selection_kwargs)
|
|
if VectorFeatureService.supports_selection_summary(dataset):
|
|
summary_kwargs = {
|
|
"dataset": dataset,
|
|
"bbox": payload.bbox.model_dump(),
|
|
"total_feature_count": result.get("total_feature_count"),
|
|
}
|
|
if selection_area is not None:
|
|
summary_kwargs["selection_geometry"] = None if dataset_is_preclipped_to_area else selection_geometry
|
|
summary_kwargs["full_dataset_area"] = full_dataset_area
|
|
summary_kwargs["preclipped_partition_filter"] = preclipped_partition_filter
|
|
result["summary"] = VectorFeatureService.summarize_features_by_bbox(db, **summary_kwargs)
|
|
return envelope(VectorSelectionResponse(**result).model_dump(exclude_none=True))
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/vector/select/derive",
|
|
status_code=201,
|
|
response_model=Envelope[DatasetCreateResponse],
|
|
)
|
|
def derive_vector_selection_dataset(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: VectorSelectionDeriveRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
if dataset.dataset_type not in {"vector", "geojson"}:
|
|
raise AppError(code="DATASET_NOT_VECTOR", message="Area selection requires a vector dataset", status_code=400)
|
|
selection_geometry = None
|
|
selection_area_id = None
|
|
if payload.area_id is not None:
|
|
selection_area = db.get(Area, payload.area_id)
|
|
if selection_area is None or selection_area.project_id != project_id:
|
|
raise AppError(code="AREA_NOT_FOUND", message="Area not found", status_code=404)
|
|
selection_geometry, _covers_full_area = VectorFeatureService.constrain_bbox_to_area(
|
|
payload.bbox.model_dump(),
|
|
selection_area.geometry,
|
|
)
|
|
selection_area_id = selection_area.id
|
|
derived = VectorOperationsService.derive_selection_dataset(
|
|
db=db,
|
|
dataset_id=dataset_id,
|
|
bbox=payload.bbox.model_dump(),
|
|
selection_geometry=selection_geometry,
|
|
selection_area_id=selection_area_id,
|
|
limit=payload.limit,
|
|
output_name=payload.output_name,
|
|
)
|
|
return envelope(derived.model_dump())
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/vector/clip",
|
|
status_code=201,
|
|
response_model=Envelope[JobRead],
|
|
)
|
|
def clip_vector_dataset(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: VectorClipRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
job = _run_job_sync(
|
|
db=db,
|
|
project_id=project_id,
|
|
input_dataset_id=dataset_id,
|
|
job_type="vector.clip",
|
|
parameters=payload.model_dump(),
|
|
operation=lambda: VectorOperationsService.clip_by_area(
|
|
db,
|
|
dataset_id=dataset_id,
|
|
area_id=payload.area_id,
|
|
output_name=payload.output_name,
|
|
),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/vector/buffer",
|
|
status_code=201,
|
|
response_model=Envelope[JobRead],
|
|
)
|
|
def buffer_vector_dataset(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: VectorBufferRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
job = _run_job_sync(
|
|
db=db,
|
|
project_id=project_id,
|
|
input_dataset_id=dataset_id,
|
|
job_type="vector.buffer",
|
|
parameters=payload.model_dump(),
|
|
operation=lambda: VectorOperationsService.buffer(
|
|
db,
|
|
dataset_id=dataset_id,
|
|
distance_m=payload.distance_m,
|
|
dissolve=payload.dissolve,
|
|
output_name=payload.output_name,
|
|
),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/vector/intersect",
|
|
status_code=201,
|
|
response_model=Envelope[JobRead],
|
|
)
|
|
def intersect_vector_dataset(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: VectorIntersectRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
job = _run_job_sync(
|
|
db=db,
|
|
project_id=project_id,
|
|
input_dataset_id=dataset_id,
|
|
job_type="vector.intersect",
|
|
parameters=payload.model_dump(),
|
|
operation=lambda: VectorOperationsService.intersect(
|
|
db,
|
|
source_dataset_id=dataset_id,
|
|
target_dataset_id=UUID(payload.other_dataset_id),
|
|
output_name=payload.output_name,
|
|
),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.get(
|
|
"/datasets/{dataset_id}/vector/summary",
|
|
response_model=Envelope[DatasetVectorSummary],
|
|
)
|
|
def vector_dataset_summary(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
return envelope(DatasetService.vector_summary(db, dataset_id))
|
|
|
|
|
|
@router.get(
|
|
"/datasets/{dataset_id}/raster/inspect",
|
|
response_model=Envelope[RasterOperationResult],
|
|
)
|
|
def raster_dataset_inspect(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
payload = RasterOperationsService.inspect(db, dataset_id)
|
|
return envelope(payload)
|
|
|
|
|
|
@router.get(
|
|
"/datasets/{dataset_id}/raster/preview",
|
|
response_model=Envelope[RasterPreviewResponse],
|
|
)
|
|
def raster_preview_readiness(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
return envelope(RasterOperationsService.preview(db, dataset_id))
|
|
|
|
|
|
@router.get("/datasets/{dataset_id}/raster/image")
|
|
def raster_orthophoto_image(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
content = OrthophotoAcquisitionService.render_png(db, project_id, dataset_id)
|
|
return Response(
|
|
content=content,
|
|
media_type="image/png",
|
|
headers={"Cache-Control": "private, max-age=86400"},
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/raster/terrain/select",
|
|
response_model=Envelope[TerrainSelectionResponse],
|
|
)
|
|
def raster_terrain_selection(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: TerrainSelectionRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
return envelope(TerrainAnalysisService.analyze(db, project_id, dataset_id, payload))
|
|
|
|
|
|
@router.post(
|
|
"/datasets/raster/terrain/select",
|
|
response_model=Envelope[TerrainSelectionResponse],
|
|
)
|
|
def partitioned_raster_terrain_selection(
|
|
project_id: UUID,
|
|
payload: TerrainPartitionSelectionRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
return envelope(TerrainAnalysisService.analyze_partitions(db, project_id, payload))
|
|
|
|
|
|
@router.get("/datasets/{dataset_id}/raster/terrain/image")
|
|
def raster_terrain_image(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
content = TerrainAnalysisService.render_png(db, project_id, dataset_id)
|
|
return Response(
|
|
content=content,
|
|
media_type="image/png",
|
|
headers={"Cache-Control": "private, max-age=86400"},
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/raster/bathymetry/select",
|
|
response_model=Envelope[BathymetryRasterSelectionResponse],
|
|
)
|
|
def raster_bathymetry_selection(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: BathymetryRasterSelectionRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
return envelope(BathymetryRasterAnalysisService.analyze(db, project_id, dataset_id, payload))
|
|
|
|
|
|
@router.get("/datasets/{dataset_id}/raster/bathymetry/image")
|
|
def raster_bathymetry_image(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
content = BathymetryRasterAnalysisService.render_png(db, project_id, dataset_id)
|
|
return Response(
|
|
content=content,
|
|
media_type="image/png",
|
|
headers={"Cache-Control": "private, max-age=86400"},
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/raster/flood-hazard/select",
|
|
response_model=Envelope[FloodHazardSelectionResponse],
|
|
)
|
|
def raster_flood_hazard_selection(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: FloodHazardSelectionRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
return envelope(FloodHazardAnalysisService.analyze(db, project_id, dataset_id, payload))
|
|
|
|
|
|
@router.post(
|
|
"/datasets/raster/flood-hazard/select",
|
|
response_model=Envelope[FloodHazardSelectionResponse],
|
|
)
|
|
def partitioned_raster_flood_hazard_selection(
|
|
project_id: UUID,
|
|
payload: FloodHazardPartitionSelectionRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
return envelope(FloodHazardAnalysisService.analyze_partitions(db, project_id, payload))
|
|
|
|
|
|
@router.get("/datasets/{dataset_id}/raster/flood-hazard/image")
|
|
def raster_flood_hazard_image(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
content = FloodHazardAnalysisService.render_png(db, project_id, dataset_id)
|
|
return Response(
|
|
content=content,
|
|
media_type="image/png",
|
|
headers={"Cache-Control": "private, max-age=86400"},
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/raster/thematic/select",
|
|
response_model=Envelope[ThematicRasterSelectionResponse],
|
|
)
|
|
def raster_thematic_selection(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: ThematicRasterSelectionRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
return envelope(ThematicRasterAnalysisService.analyze(db, project_id, dataset_id, payload))
|
|
|
|
|
|
@router.get("/datasets/{dataset_id}/raster/thematic/image")
|
|
def raster_thematic_image(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
content = ThematicRasterAnalysisService.render_png(db, project_id, dataset_id)
|
|
return Response(
|
|
content=content,
|
|
media_type="image/png",
|
|
headers={"Cache-Control": "private, max-age=86400"},
|
|
)
|
|
|
|
|
|
@router.get(
|
|
"/datasets/{dataset_id}/raster/stats",
|
|
response_model=Envelope[RasterStatsResponse],
|
|
)
|
|
def raster_stats(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
payload = RasterOperationsService.stats(db, dataset_id)
|
|
return envelope(RasterStatsResponse(**payload).model_dump())
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/raster/reproject",
|
|
status_code=201,
|
|
response_model=Envelope[JobRead],
|
|
)
|
|
def raster_reproject_dataset(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: RasterReprojectRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
job = _run_job_sync(
|
|
db=db,
|
|
project_id=project_id,
|
|
input_dataset_id=dataset_id,
|
|
job_type="raster.reproject",
|
|
parameters=payload.model_dump(),
|
|
operation=lambda: RasterOperationsService.reproject(
|
|
db,
|
|
dataset_id,
|
|
target_crs=payload.target_crs,
|
|
output_name=payload.output_name,
|
|
resampling=payload.resampling,
|
|
),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/raster/clip",
|
|
status_code=201,
|
|
response_model=Envelope[JobRead],
|
|
)
|
|
def raster_clip_dataset(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: RasterClipRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
job = _run_job_sync(
|
|
db=db,
|
|
project_id=project_id,
|
|
input_dataset_id=dataset_id,
|
|
job_type="raster.clip",
|
|
parameters=payload.model_dump(),
|
|
operation=lambda: RasterOperationsService.clip(db, dataset_id, UUID(payload.area_id), payload.output_name),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/raster/tile",
|
|
status_code=201,
|
|
response_model=Envelope[JobRead],
|
|
)
|
|
def raster_tile_dataset(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: RasterTileRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
job = _run_job_sync(
|
|
db=db,
|
|
project_id=project_id,
|
|
input_dataset_id=dataset_id,
|
|
job_type="raster.tile",
|
|
parameters=payload.model_dump(),
|
|
operation=lambda: RasterOperationsService.tile(
|
|
db,
|
|
dataset_id,
|
|
tile_size=payload.tile_size,
|
|
overlap=payload.overlap,
|
|
output_name=payload.output_name,
|
|
),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/raster/indices/ndvi",
|
|
status_code=201,
|
|
response_model=Envelope[JobRead],
|
|
)
|
|
def raster_ndvi_dataset(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: RasterNdviRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
job = _run_job_sync(
|
|
db=db,
|
|
project_id=project_id,
|
|
input_dataset_id=dataset_id,
|
|
job_type="raster.ndvi",
|
|
parameters=payload.model_dump(),
|
|
operation=lambda: RasterOperationsService.ndvi(
|
|
db,
|
|
dataset_id=dataset_id,
|
|
nir_band=payload.nir_band,
|
|
red_band=payload.red_band,
|
|
output_name=payload.output_name,
|
|
),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/raster/indices/ndwi",
|
|
status_code=201,
|
|
response_model=Envelope[JobRead],
|
|
)
|
|
def raster_ndwi_dataset(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: RasterNdwiRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
job = _run_job_sync(
|
|
db=db,
|
|
project_id=project_id,
|
|
input_dataset_id=dataset_id,
|
|
job_type="raster.ndwi",
|
|
parameters=payload.model_dump(),
|
|
operation=lambda: RasterOperationsService.ndwi(
|
|
db,
|
|
dataset_id=dataset_id,
|
|
green_band=payload.green_band,
|
|
nir_band=payload.nir_band,
|
|
output_name=payload.output_name,
|
|
),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.post(
|
|
"/datasets/{dataset_id}/raster/indices/ndbi",
|
|
status_code=201,
|
|
response_model=Envelope[JobRead],
|
|
)
|
|
def raster_ndbi_dataset(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
payload: RasterNdbiRequest,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
job = _run_job_sync(
|
|
db=db,
|
|
project_id=project_id,
|
|
input_dataset_id=dataset_id,
|
|
job_type="raster.ndbi",
|
|
parameters=payload.model_dump(),
|
|
operation=lambda: RasterOperationsService.ndbi(
|
|
db,
|
|
dataset_id=dataset_id,
|
|
swir_band=payload.swir_band,
|
|
nir_band=payload.nir_band,
|
|
output_name=payload.output_name,
|
|
),
|
|
)
|
|
return envelope(job)
|
|
|
|
|
|
@router.get(
|
|
"/datasets/{dataset_id}/raster/metadata",
|
|
response_model=Envelope[RasterMetadataResponse],
|
|
)
|
|
def raster_dataset_metadata(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
return envelope(RasterOperationsService.metadata(db, dataset_id))
|
|
|
|
|
|
@router.get(
|
|
"/datasets/{dataset_id}/content",
|
|
response_model=Envelope[GeoJsonFeatureCollection],
|
|
)
|
|
def dataset_content(
|
|
project_id: UUID,
|
|
dataset_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
):
|
|
dataset = DatasetService.get_dataset(db, dataset_id)
|
|
if dataset.project_id != project_id:
|
|
raise HTTPException(status_code=404, detail="Dataset not found")
|
|
return envelope(DatasetService.get_dataset_geojson(db, dataset_id))
|