From 46fe3edfc506df4fd1adb786baff1302ea074df9 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 25 Jun 2026 02:12:09 +0200 Subject: [PATCH] Persist map area selection exports --- CHANGELOG.md | 8 ++ backend/README.md | 5 + backend/app/api/routes/exports.py | 10 ++ backend/app/schemas/export.py | 11 +- backend/app/services/export_service.py | 45 ++++++ .../test_sprint107_map_selection_export.py | 135 ++++++++++++++++++ docs/API_CONTRACTS.md | 25 +++- docs/CODEX_EXECUTION_LOG.md | 31 ++++ docs/TODO.md | 1 + frontend/README.md | 1 + frontend/src/App.tsx | 8 ++ frontend/src/components/map/MapWorkspace.tsx | 28 ++++ frontend/src/hooks/useExportWorkflow.ts | 36 ++++- frontend/src/services/api/exports.ts | 20 ++- frontend/src/types.ts | 2 +- 15 files changed, 359 insertions(+), 7 deletions(-) create mode 100644 backend/tests/test_sprint107_map_selection_export.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 670402aa..ac0dcd6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ # Changelog +## Sprint 107 Map selection export handoff (2026-06-25) + +- Added `vector_selection` GeoJSON export support to persist bbox-selected map features as normal export artifacts. +- Selection exports query persisted PostGIS `vector_features`, write a `vector_selection_geojson` FeatureCollection and store selection bbox/count metadata in the export record. +- Added `Save area export` to the Map workspace after an area extract, including loading/error state and latest artifact path feedback. +- Updated frontend export typing/API hook wiring so saved selections appear in the existing Export Center history. +- No migrations, provider fetching, AI behavior, real model dependencies or new product domains were introduced. + ## Sprint 106 Map area selection extract (2026-06-25) - Added a read-only bbox selection endpoint for vector datasets: `POST /api/v1/projects/{project_id}/datasets/{dataset_id}/vector/select`. diff --git a/backend/README.md b/backend/README.md index 5b36c273..e38185fe 100644 --- a/backend/README.md +++ b/backend/README.md @@ -582,6 +582,11 @@ returns a canonical-envelope GeoJSON FeatureCollection. It is intended for the Map workspace area-extract flow and does not create derived datasets or export records. +`POST /api/v1/exports/geojson` with `export_kind="vector_selection"` persists +the same bbox-selected FeatureCollection as a normal export record with +`export_type="vector_selection_geojson"`. This creates a handoff artifact only; +it does not create a derived dataset or mutate `vector_features`. + ## Helpful repository scripts - `bash scripts/backend_install.sh` diff --git a/backend/app/api/routes/exports.py b/backend/app/api/routes/exports.py index a4d9d994..064f232e 100644 --- a/backend/app/api/routes/exports.py +++ b/backend/app/api/routes/exports.py @@ -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") diff --git a/backend/app/schemas/export.py b/backend/app/schemas/export.py index a32dea66..13da4bb7 100644 --- a/backend/app/schemas/export.py +++ b/backend/app/schemas/export.py @@ -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 diff --git a/backend/app/services/export_service.py b/backend/app/services/export_service.py index 2e21a95b..bf2742fb 100644 --- a/backend/app/services/export_service.py +++ b/backend/app/services/export_service.py @@ -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) diff --git a/backend/tests/test_sprint107_map_selection_export.py b/backend/tests/test_sprint107_map_selection_export.py new file mode 100644 index 00000000..f7665e96 --- /dev/null +++ b/backend/tests/test_sprint107_map_selection_export.py @@ -0,0 +1,135 @@ +from __future__ import annotations + +import json +from pathlib import Path +from uuid import uuid4 + +from fastapi.testclient import TestClient + +from app.main import app +from app.models import Dataset, Export +from app.schemas.export import ExportCreateResponse +from app.services.export_service import ExportService +from app.services.storage_service import StorageService +from app.services.vector_feature_service import VectorFeatureService + + +ROOT = Path(__file__).resolve().parents[2] + + +class FakeSession: + def __init__(self, rows): + self.rows = rows + self.added = [] + + def get(self, model, row_id): + row = self.rows.get((model, row_id)) + if row is not None: + return row + for item in self.added: + if isinstance(item, model) and item.id == row_id: + return item + return None + + def add(self, row): + self.added.append(row) + + def commit(self): + return None + + def refresh(self, row): + return row + + +def test_vector_selection_geojson_export_persists_handoff_artifact(tmp_path, monkeypatch) -> None: + project_id = uuid4() + dataset_id = uuid4() + export_path = tmp_path / "exports" / "selection.geojson" + dataset = Dataset( + id=dataset_id, + project_id=project_id, + name="candidate.geojson", + dataset_type="vector", + source="fixture", + status="ready", + ) + db = FakeSession({(Dataset, dataset_id): dataset}) + selection_bbox = {"min_x": 4.9, "min_y": 50.9, "max_x": 5.2, "max_y": 51.2, "crs": "EPSG:4326"} + selection_payload = { + "selection_bbox": selection_bbox, + "feature_count": 1, + "limit": 250, + "truncated": False, + "geojson": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [5.0, 51.0]}, + "properties": {"vector_feature_id": "vf-1"}, + } + ], + }, + } + monkeypatch.setattr(StorageService, "dataset_export_path", lambda *_args: str(export_path)) + monkeypatch.setattr(VectorFeatureService, "select_features_by_bbox", lambda *_args, **_kwargs: selection_payload) + + response = ExportService.export_vector_selection_geojson(db, dataset_id, selection_bbox, limit=250, name="selected-buildings") + + exports = [item for item in db.added if isinstance(item, Export)] + assert len(exports) == 1 + assert response.export_id == exports[0].id + assert response.export_type == "vector_selection_geojson" + assert response.metadata_json["source"] == "vector_selection" + assert response.metadata_json["dataset_id"] == str(dataset_id) + assert response.metadata_json["selection_bbox"] == selection_bbox + assert response.metadata_json["feature_count"] == 1 + assert response.metadata_json["source_table"] == "vector_features" + assert json.loads(export_path.read_text(encoding="utf-8"))["features"][0]["properties"]["vector_feature_id"] == "vf-1" + + +def test_vector_selection_geojson_export_endpoint_returns_canonical_envelope(monkeypatch) -> None: + export_id = uuid4() + dataset_id = uuid4() + expected_bbox = {"min_x": 4.9, "min_y": 50.9, "max_x": 5.2, "max_y": 51.2, "crs": "EPSG:4326"} + + monkeypatch.setattr( + ExportService, + "export_vector_selection_geojson", + lambda *_args, **_kwargs: ExportCreateResponse( + export_id=export_id, + path="storage/exports/demo-selection.geojson", + status="ready", + export_type="vector_selection_geojson", + metadata_json={"source": "vector_selection", "selection_bbox": expected_bbox}, + ), + ) + + response = TestClient(app).post( + "/api/v1/exports/geojson", + json={"dataset_id": str(dataset_id), "export_kind": "vector_selection", "bbox": expected_bbox, "limit": 250}, + ) + + assert response.status_code == 200 + payload = response.json() + assert set(payload) == {"data"} + assert payload["data"]["export_id"] == str(export_id) + assert payload["data"]["export_type"] == "vector_selection_geojson" + assert payload["data"]["metadata_json"]["selection_bbox"] == expected_bbox + + +def test_frontend_exposes_map_selection_export_action() -> None: + types = (ROOT / "frontend" / "src" / "types.ts").read_text(encoding="utf-8") + exports_api = (ROOT / "frontend" / "src" / "services" / "api" / "exports.ts").read_text(encoding="utf-8") + export_hook = (ROOT / "frontend" / "src" / "hooks" / "useExportWorkflow.ts").read_text(encoding="utf-8") + map_workspace = (ROOT / "frontend" / "src" / "components" / "map" / "MapWorkspace.tsx").read_text(encoding="utf-8") + app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") + + assert "'vector_selection'" in types + assert "bbox?: VectorSelectionBBox" in exports_api + assert "exportMapSelectionGeoJson" in export_hook + assert "vector_selection" in export_hook + assert "Save area export" in map_workspace + assert "onExportMapSelection" in map_workspace + assert "selectionExportError" in map_workspace + assert "onExportMapSelection={exportMapSelectionGeoJson}" in app diff --git a/docs/API_CONTRACTS.md b/docs/API_CONTRACTS.md index ab86f78e..0fb2f2a2 100644 --- a/docs/API_CONTRACTS.md +++ b/docs/API_CONTRACTS.md @@ -1013,6 +1013,24 @@ Dataset vector export request: } ``` +Map vector selection export request: + +```json +{ + "export_kind": "vector_selection", + "dataset_id": "uuid", + "bbox": { + "min_x": 5.0, + "min_y": 51.0, + "max_x": 5.1, + "max_y": 51.1, + "crs": "EPSG:4326" + }, + "limit": 250, + "name": "optional-basename" +} +``` + Detection run export request: ```json @@ -1050,8 +1068,11 @@ Response persists an `exports` row and writes a deterministic JSON artifact: Vector dataset exports use the stored dataset GeoJSON. Detection and segmentation exports use persisted first-class geometry records and the -existing Detection/Segmentation GeoJSON conversion services. Raster datasets -are rejected for dataset GeoJSON export. +existing Detection/Segmentation GeoJSON conversion services. Vector selection +exports query persisted PostGIS `vector_features` with the supplied EPSG:4326 +bbox, write the selected FeatureCollection as a `vector_selection_geojson` +artifact, and persist bbox/feature-count metadata in the export record. Raster +datasets are rejected for dataset and selection GeoJSON export. ### POST `/api/v1/exports/metadata` diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index bb2ba833..582fabeb 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -1,3 +1,34 @@ +## Sprint 107 Map selection export handoff (2026-06-25) + +Changed: +- Added `vector_selection` to the GeoJSON export contract. +- Added `ExportService.export_vector_selection_geojson`, which queries persisted PostGIS `vector_features` through `VectorFeatureService.select_features_by_bbox`, writes the selected FeatureCollection and persists an `exports` row with `export_type="vector_selection_geojson"`. +- Extended `POST /api/v1/exports/geojson` to accept `export_kind="vector_selection"` with EPSG:4326 bbox and feature limit. +- Added frontend export API typing for bbox/limit and `useExportWorkflow.exportMapSelectionGeoJson`. +- Added `Save area export` to the Map workspace selection result state with loading/error/latest-path feedback. +- Updated `docs/API_CONTRACTS.md`, `backend/README.md`, `frontend/README.md`, `CHANGELOG.md` and `docs/TODO.md`. +- Added regression coverage in `backend/tests/test_sprint107_map_selection_export.py`. + +Validation: +- RED: `python -m pytest backend\tests\test_sprint107_map_selection_export.py -q` failed before implementation because the selection export service, route contract and frontend wiring were absent. +- `python -m pytest backend\tests\test_sprint107_map_selection_export.py -q` passed: 3 tests. +- `python -m compileall backend/app` passed. +- `cd backend && python -m pytest -q` passed: 341 tests. +- `cd frontend && npm run typecheck` passed. +- `cd frontend && npm run build` passed. +- `cd backend && python -m alembic heads` passed: `202606120900 (head)`. +- `cd backend && python -m alembic upgrade head --sql` passed. +- `bash -n scripts/live_migration_smoke.sh` passed. +- `bash scripts/run_readiness_check.sh` passed. + +Limitations: +- Selection exports are bbox-only and reuse the same EPSG:4326 constraints as the Map area selection endpoint. +- Saving a selection creates an export artifact, not a derived dataset. +- No migrations, live provider fetching, AI dependency, real model behavior or new product domain were added. + +Next recommended pass: +- Add a live browser smoke that exercises `Save area export`, confirms the export appears in Export Center history and previews/downloads the persisted `vector_selection_geojson` artifact. + ## Sprint 106 Map area selection extract (2026-06-25) Changed: diff --git a/docs/TODO.md b/docs/TODO.md index 5c73ce99..1ddf15ab 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -373,3 +373,4 @@ This file now starts with the current implementation status. Older preparation/b - [x] Add AI Lab run-readiness checks for Detection and Segmentation before job submission. - [x] Add AI Lab action guardrails so explicit fixture models are not exposed as normal operator runs. - [x] Add persisted vector area selection from the Map workspace with bbox extract and GeoJSON download. +- [x] Persist map area selections as Export Center handoff artifacts. diff --git a/frontend/README.md b/frontend/README.md index b029331f..87608d5a 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -278,6 +278,7 @@ AI Lab run controls explicitly explain when no raster dataset is available, inst - The Map workspace shows active layer source/provenance/draw-state context and selected-feature property chips before the raw JSON inspector. - When the Map workspace has no active result layer, it lists ready vector/GeoJSON datasets as direct quick actions so populated demo projects can jump straight from the empty state to map inspection. - The Map workspace can extract persisted vector features by area: open a ready vector dataset, use `Start map bbox` and click two map corners or enter EPSG:4326 bbox values, then run `Run area extract` to query backend `vector_features`. Results are highlighted on the map and can be downloaded as GeoJSON. +- After an area extract, `Save area export` persists the selected FeatureCollection as a normal Export Center artifact (`vector_selection_geojson`) so the handoff remains in project export history. - The Data catalog shows a compact selected/reference/candidate/source summary and scan-friendly badges. Persisted `reference` datasets are shown as Reference, non-reference vector/GeoJSON layers are shown as QA Candidates for workbench scanning, and raster/other uploads remain Source. - Dataset cards explain the recommended next action and use compact two-line action buttons for inspect, map, export/QA and metadata refresh. Disabled actions keep a visible reason, such as `Vector/GeoJSON only`. - Raster controls show the latest generated tile manifest path from persisted `raster.tile` jobs and can hand that path directly to Detection Lab or Segmentation Lab with the selected raster dataset. diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8691b482..ff540cd7 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -325,11 +325,15 @@ function App(): JSX.Element { exportError, loadingExports, exporting, + selectionExporting, + selectionExportError, + latestSelectionExport, exportPreview, loadExports, exportSelectedDatasetGeoJson, exportSelectedDetectionRunGeoJson, exportSelectedSegmentationRunGeoJson, + exportMapSelectionGeoJson, exportProjectMetadata, exportProjectReport, previewExportContent, @@ -802,6 +806,9 @@ function App(): JSX.Element { mapSelectionResult={mapSelectionResult} mapSelectionLoading={mapSelectionLoading} mapSelectionError={mapSelectionError} + selectionExporting={selectionExporting} + selectionExportError={selectionExportError} + latestSelectionExportPath={latestSelectionExport?.path ?? null} availableMapDatasets={availableMapDatasets} selectedFeature={selectedMapFeature} onSelectMapArea={setSelectedMapAreaId} @@ -814,6 +821,7 @@ function App(): JSX.Element { onSetMapSelectionBbox={setMapSelectionBbox} onRunMapSelectionExtract={runMapSelectionExtract} onClearMapSelectionExtract={resetMapSelectionExtract} + onExportMapSelection={exportMapSelectionGeoJson} /> ) : null} diff --git a/frontend/src/components/map/MapWorkspace.tsx b/frontend/src/components/map/MapWorkspace.tsx index 7cdab30e..8eaac968 100644 --- a/frontend/src/components/map/MapWorkspace.tsx +++ b/frontend/src/components/map/MapWorkspace.tsx @@ -191,6 +191,9 @@ interface MapWorkspaceProps { mapSelectionResult: VectorSelectionResponse | null mapSelectionLoading: boolean mapSelectionError: string | null + selectionExporting: boolean + selectionExportError: string | null + latestSelectionExportPath: string | null availableMapDatasets: DatasetCreateResponse[] onSelectMapArea: (areaId: string) => void onOpenDatasetInMap: (dataset: DatasetCreateResponse) => void @@ -202,6 +205,7 @@ interface MapWorkspaceProps { onSetMapSelectionBbox: (bbox: VectorSelectionBBox | null) => void onRunMapSelectionExtract: (bbox: VectorSelectionBBox) => void onClearMapSelectionExtract: () => void + onExportMapSelection: (bbox: VectorSelectionBBox) => void } export function MapWorkspace({ @@ -224,6 +228,9 @@ export function MapWorkspace({ mapSelectionResult, mapSelectionLoading, mapSelectionError, + selectionExporting, + selectionExportError, + latestSelectionExportPath, availableMapDatasets, onSelectMapArea, onOpenDatasetInMap, @@ -235,6 +242,7 @@ export function MapWorkspace({ onSetMapSelectionBbox, onRunMapSelectionExtract, onClearMapSelectionExtract, + onExportMapSelection, }: MapWorkspaceProps): JSX.Element { const [bboxSelectionMode, setBboxSelectionMode] = useState(false) const [firstSelectionCorner, setFirstSelectionCorner] = useState<[number, number] | null>(null) @@ -322,6 +330,14 @@ export function MapWorkspace({ copyText(JSON.stringify(mapSelectionResult?.geojson ?? { type: 'FeatureCollection', features: [] }, null, 2)) } + const saveAreaSelectionExport = () => { + const bbox = parseBboxInput(bboxInput) + if (!bbox) { + return + } + onExportMapSelection(bbox) + } + return (
@@ -587,7 +603,19 @@ export function MapWorkspace({ +
+ {selectionExportError ?

{selectionExportError}

: null} + {latestSelectionExportPath ? ( +

Saved selection artifact: {latestSelectionExportPath}

+ ) : null} {areaSelectionPreviewFeatures.length > 0 ? (
diff --git a/frontend/src/hooks/useExportWorkflow.ts b/frontend/src/hooks/useExportWorkflow.ts index 471bc884..8539c391 100644 --- a/frontend/src/hooks/useExportWorkflow.ts +++ b/frontend/src/hooks/useExportWorkflow.ts @@ -1,6 +1,6 @@ import { useState } from 'react' import { exportsApi } from '../services/api' -import type { DatasetCreateResponse, ExportCreateResponse, ExportRead } from '../types' +import type { DatasetCreateResponse, ExportCreateResponse, ExportRead, VectorSelectionBBox } from '../types' import { formatError } from '../lib/formatError' interface ExportWorkflowOptions { @@ -23,6 +23,9 @@ export function useExportWorkflow({ const [exportError, setExportError] = useState(null) const [loadingExports, setLoadingExports] = useState(false) const [exporting, setExporting] = useState(false) + const [selectionExporting, setSelectionExporting] = useState(false) + const [selectionExportError, setSelectionExportError] = useState(null) + const [latestSelectionExport, setLatestSelectionExport] = useState(null) const [exportPreview, setExportPreview] = useState | null>(null) const loadExports = async (projectId = selectedProjectId) => { @@ -106,6 +109,31 @@ export function useExportWorkflow({ } } + const exportMapSelectionGeoJson = async (bbox: VectorSelectionBBox) => { + if (!selectedDataset || !isVectorDatasetType(selectedDataset.dataset_type)) { + setSelectionExportError('Select a vector dataset before saving an area export.') + return + } + setSelectionExporting(true) + setSelectionExportError(null) + try { + const response = await exportsApi.exportGeojson({ + dataset_id: selectedDataset.id, + export_kind: 'vector_selection', + bbox: { ...bbox, crs: 'EPSG:4326' }, + limit: 250, + name: `${selectedDataset.name.replace(/\.(geo)?json$/i, '')}-selection`, + }) + setLatestExport(response) + setLatestSelectionExport(response) + await loadExports(selectedDataset.project_id) + } catch (error) { + setSelectionExportError(formatError(error, 'Failed to save area export')) + } finally { + setSelectionExporting(false) + } + } + const exportProjectMetadata = async () => { if (!selectedProjectId) { setExportError('Select a project before exporting metadata.') @@ -159,6 +187,8 @@ export function useExportWorkflow({ const resetExportsForProject = () => { setExports([]) setLatestExport(null) + setLatestSelectionExport(null) + setSelectionExportError(null) setExportPreview(null) } @@ -168,11 +198,15 @@ export function useExportWorkflow({ exportError, loadingExports, exporting, + selectionExporting, + selectionExportError, + latestSelectionExport, exportPreview, loadExports, exportSelectedDatasetGeoJson, exportSelectedDetectionRunGeoJson, exportSelectedSegmentationRunGeoJson, + exportMapSelectionGeoJson, exportProjectMetadata, exportProjectReport, previewExportContent, diff --git a/frontend/src/services/api/exports.ts b/frontend/src/services/api/exports.ts index 7b7afa31..acc9ca01 100644 --- a/frontend/src/services/api/exports.ts +++ b/frontend/src/services/api/exports.ts @@ -1,9 +1,25 @@ import { apiGet, apiPost, apiUrl } from './client' -import type { ExportContentResponse, ExportCreateResponse, ExportKind, ExportListResponse, ExportRead } from '../../types' +import type { + ExportContentResponse, + ExportCreateResponse, + ExportKind, + ExportListResponse, + ExportRead, + VectorSelectionBBox, +} from '../../types' export const exportsApi = { exportGeojson: ( - payload: { dataset_id?: string; analysis_run_id?: string; export_kind?: ExportKind; name?: string } | string, + payload: + | { + dataset_id?: string + analysis_run_id?: string + export_kind?: ExportKind + name?: string + bbox?: VectorSelectionBBox + limit?: number + } + | string, ): Promise => { const body = typeof payload === 'string' ? { dataset_id: payload, export_kind: 'dataset' } : payload return apiPost(`/api/v1/exports/geojson`, body) diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 6ebf6d9f..35cb1f92 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -635,7 +635,7 @@ export interface QualityCheckListResponse { offset: number } -export type ExportKind = 'dataset' | 'detection_run' | 'segmentation_run' +export type ExportKind = 'dataset' | 'detection_run' | 'segmentation_run' | 'vector_selection' export interface ExportRead { id: string