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

This commit is contained in:
Codex
2026-06-25 02:12:09 +02:00
parent f773225b98
commit 46fe3edfc5
15 changed files with 359 additions and 7 deletions
+8
View File
@@ -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}
@@ -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 (
<section className="map-workspace-shell" data-testid="map-workspace">
<div className="panel-title-row">
@@ -587,7 +603,19 @@ export function MapWorkspace({
<button className="secondary-action" type="button" onClick={copyAreaSelection}>
Copy area GeoJSON
</button>
<button
className="secondary-action"
disabled={!currentSelectionBbox || selectionExporting}
type="button"
onClick={saveAreaSelectionExport}
>
{selectionExporting ? 'Saving export...' : 'Save area export'}
</button>
</div>
{selectionExportError ? <p className="error">{selectionExportError}</p> : null}
{latestSelectionExportPath ? (
<p className="muted">Saved selection artifact: {latestSelectionExportPath}</p>
) : null}
{areaSelectionPreviewFeatures.length > 0 ? (
<div className="table-scroll feature-property-table" aria-label="Area selection feature table">
<table>
+35 -1
View File
@@ -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<string | null>(null)
const [loadingExports, setLoadingExports] = useState(false)
const [exporting, setExporting] = useState(false)
const [selectionExporting, setSelectionExporting] = useState(false)
const [selectionExportError, setSelectionExportError] = useState<string | null>(null)
const [latestSelectionExport, setLatestSelectionExport] = useState<ExportCreateResponse | null>(null)
const [exportPreview, setExportPreview] = useState<Record<string, unknown> | 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,
+18 -2
View File
@@ -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<ExportCreateResponse> => {
const body = typeof payload === 'string' ? { dataset_id: payload, export_kind: 'dataset' } : payload
return apiPost<ExportCreateResponse>(`/api/v1/exports/geojson`, body)
+1 -1
View File
@@ -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