642 lines
22 KiB
Python
642 lines
22 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from datetime import datetime
|
|
from typing import Any
|
|
from uuid import UUID
|
|
from uuid import UUID as _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 (
|
|
RasterClipRequest,
|
|
RasterStatsResponse,
|
|
RasterReprojectRequest,
|
|
RasterTileRequest,
|
|
RasterNdviRequest,
|
|
RasterNdwiRequest,
|
|
RasterNdbiRequest,
|
|
OrthophotoAcquireRequest,
|
|
VectorBBoxResponse,
|
|
VectorBufferRequest,
|
|
VectorClipRequest,
|
|
VectorIntersectRequest,
|
|
VectorSelectionBBox,
|
|
VectorSelectionDeriveRequest,
|
|
VectorSelectionRequest,
|
|
VectorSelectionResponse,
|
|
)
|
|
from app.schemas.job import JobCreate
|
|
from app.schemas.dataset import DatasetCreateResponse, DatasetTemporalUpdate
|
|
from app.schemas.operations import VectorOperationResult
|
|
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.orthophoto_acquisition_service import OrthophotoAcquisitionService
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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.get("/datasets", response_model=dict)
|
|
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/{dataset_id}", response_model=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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
|
|
if selection_area is not None:
|
|
full_dataset_area = VectorFeatureService.can_use_full_area_fast_path(dataset, selection_area.id)
|
|
selection_kwargs.update(
|
|
selection_geometry=selection_area.geometry,
|
|
selection_area_id=selection_area.id,
|
|
full_dataset_area=full_dataset_area,
|
|
)
|
|
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"] = selection_area.geometry
|
|
summary_kwargs["full_dataset_area"] = full_dataset_area
|
|
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=dict)
|
|
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)
|
|
derived = VectorOperationsService.derive_selection_dataset(
|
|
db=db,
|
|
dataset_id=dataset_id,
|
|
bbox=payload.bbox.model_dump(),
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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.get("/datasets/{dataset_id}/raster/stats", response_model=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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=dict)
|
|
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))
|