Add V1 selected area map overlay
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-17 00:21:01 +02:00
parent 97be9d9293
commit 47cd2c6c1e
12 changed files with 315 additions and 21 deletions
+81 -2
View File
@@ -96,6 +96,9 @@ function App(): JSX.Element {
const [datasetContent, setDatasetContent] = useState<GeoJSON.FeatureCollection | null>(null)
const [mapLayerVisible, setMapLayerVisible] = useState(true)
const [mapLayerOpacity, setMapLayerOpacity] = useState(0.4)
const [selectedMapAreaId, setSelectedMapAreaId] = useState('')
const [areaLayerVisible, setAreaLayerVisible] = useState(true)
const [areaLayerOpacity, setAreaLayerOpacity] = useState(0.18)
const [selectedMapFeature, setSelectedMapFeature] = useState<GeoJSON.Feature | null>(null)
const [jobs, setJobs] = useState<JobRead[]>([])
const [providerCapabilities, setProviderCapabilities] = useState<ProviderCapability[]>([])
@@ -243,6 +246,31 @@ function App(): JSX.Element {
() => segmentationModels.find((model) => model.model_id === selectedSegmentationModelId) ?? null,
[segmentationModels, selectedSegmentationModelId],
)
const selectedMapArea = useMemo(
() => areas.find((area) => area.id === selectedMapAreaId) ?? null,
[areas, selectedMapAreaId],
)
const areaFeatureCollection = useMemo<GeoJSON.FeatureCollection | null>(() => {
if (!selectedMapArea?.geometry) {
return null
}
return {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: selectedMapArea.geometry,
properties: {
layer_type: 'project_area',
area_id: selectedMapArea.id,
name: selectedMapArea.name,
area_m2: selectedMapArea.area_m2 ?? null,
original_crs: selectedMapArea.original_crs ?? null,
},
},
],
}
}, [selectedMapArea])
const mapFeatureCollection = useMemo(
() => changeDetectionResult?.geojson ?? segmentationGeoJson ?? detectionGeoJson ?? datasetContent,
[changeDetectionResult, segmentationGeoJson, detectionGeoJson, datasetContent],
@@ -263,6 +291,7 @@ function App(): JSX.Element {
return 'No active vector layer'
}, [changeDetectionResult, datasetContent, detectionGeoJson, segmentationGeoJson, selectedDataset])
const mapFeatureCount = mapFeatureCollection?.features.length ?? 0
const areaFeatureCount = areaFeatureCollection?.features.length ?? 0
const isRasterTileInputValid = useMemo(
() => rasterTileSize > 0 && rasterTileOverlap >= 0 && rasterTileOverlap < rasterTileSize,
[rasterTileSize, rasterTileOverlap],
@@ -313,6 +342,11 @@ function App(): JSX.Element {
if (!selectedClipAreaId && areaResponse.items.length > 0) {
setSelectedClipAreaId(areaResponse.items[0].id)
}
if (areaResponse.items.length === 0) {
setSelectedMapAreaId('')
} else if (!selectedMapAreaId || !areaResponse.items.some((area) => area.id === selectedMapAreaId)) {
setSelectedMapAreaId(areaResponse.items[0].id)
}
} catch (error) {
setErrorMessage(error instanceof Error ? error.message : 'Failed to load project data')
} finally {
@@ -645,7 +679,7 @@ function App(): JSX.Element {
useEffect(() => {
setSelectedMapFeature(null)
}, [mapFeatureCollection])
}, [mapFeatureCollection, areaFeatureCollection])
useEffect(() => {
if (!selectedProjectId) {
@@ -653,6 +687,7 @@ function App(): JSX.Element {
setDatasets([])
setSelectedDatasetId(null)
setSelectedDataset(null)
setSelectedMapAreaId('')
setSelectedDatasetSummary(null)
setSelectedRasterMetadata(null)
setSelectedRasterStats(null)
@@ -716,6 +751,7 @@ function App(): JSX.Element {
const result = await demoApi.seedWorkflow()
setSelectedProjectId(result.project_id)
setSelectedDatasetId(result.candidate_dataset_id)
setSelectedMapAreaId(result.area_id)
setQaCandidateDatasetId(result.candidate_dataset_id)
setQaReferenceDatasetId(result.reference_dataset_id)
setQaAreaId(result.area_id)
@@ -751,11 +787,12 @@ function App(): JSX.Element {
return
}
try {
await areasApi.create(selectedProjectId, {
const createdArea = await areasApi.create(selectedProjectId, {
name: areaForm.name,
crs: areaForm.crs,
geometry,
})
setSelectedMapAreaId(createdArea.id)
await loadProjectData(selectedProjectId)
setAreaForm((previous) => ({ ...previous, name: '' }))
} catch (error) {
@@ -1349,8 +1386,10 @@ function App(): JSX.Element {
selectedProjectId={selectedProjectId}
loadingAreas={loadingAreas}
areaForm={areaForm}
selectedMapAreaId={selectedMapAreaId}
onCreateArea={createArea}
onUpdateAreaForm={setAreaForm}
onSelectMapArea={setSelectedMapAreaId}
/>
<ProviderPanel
@@ -1851,6 +1890,42 @@ function App(): JSX.Element {
<h2>Map workspace</h2>
<p>{mapLayerLabel}</p>
<div className="map-controls">
<label>
Selected area
<select
value={selectedMapAreaId}
onChange={(event) => setSelectedMapAreaId(event.target.value)}
disabled={areas.length === 0}
>
<option value="">No area</option>
{areas.map((area) => (
<option key={area.id} value={area.id}>
{area.name}
</option>
))}
</select>
</label>
<label className="checkbox-row">
<input
checked={areaLayerVisible}
disabled={!areaFeatureCollection}
type="checkbox"
onChange={(event) => setAreaLayerVisible(event.target.checked)}
/>
Area visible
</label>
<label>
Area opacity
<input
disabled={!areaFeatureCollection}
max="0.7"
min="0.05"
step="0.05"
type="range"
value={areaLayerOpacity}
onChange={(event) => setAreaLayerOpacity(Number(event.target.value))}
/>
</label>
<label className="checkbox-row">
<input
checked={mapLayerVisible}
@@ -1873,13 +1948,17 @@ function App(): JSX.Element {
/>
</label>
<div className="map-status">
{areaFeatureCollection ? `${areaFeatureCount} area loaded` : 'No area loaded'} -{' '}
{mapFeatureCollection ? `${mapFeatureCount} features loaded` : 'No vector layer loaded'}
</div>
</div>
<GeoMap
data={mapFeatureCollection}
areaData={areaFeatureCollection}
visible={mapLayerVisible}
opacity={mapLayerOpacity}
areaVisible={areaLayerVisible}
areaOpacity={areaLayerOpacity}
onFeatureSelect={setSelectedMapFeature}
/>
<div className="feature-inspector">