Keep map selections within active areas
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-16 14:35:16 +02:00
parent e48d6c9873
commit 2723043e3d
7 changed files with 28 additions and 12 deletions
+3
View File
@@ -25,6 +25,9 @@
render all 28 matching MapLibre partitions, deduplicate VMM into twelve render all 28 matching MapLibre partitions, deduplicate VMM into twelve
scenario choices and analyse a drawn cross-boundary rectangle without first scenario choices and analyse a drawn cross-boundary rectangle without first
selecting a municipality. selecting a municipality.
- Forwarded the active Area through drawn, coordinate, manual, temporal,
derived-dataset and full-workflow selections so every result remains clipped
to the chosen municipality or approved regional boundary.
- Preserved the distinction between DHMV height, modeled VMM scenario depth, - Preserved the distinction between DHMV height, modeled VMM scenario depth,
permanent water, bathymetry and concurrent flood volume. permanent water, bathymetry and concurrent flood volume.
@@ -36,7 +36,7 @@ def test_map_rectangle_drag_is_wired_to_automatic_analysis() -> None:
assert "onMapBboxPreview={handleMapBboxPreview}" in workspace assert "onMapBboxPreview={handleMapBboxPreview}" in workspace
assert "onMapBboxSelect={handleMapBboxSelect}" in workspace assert "onMapBboxSelect={handleMapBboxSelect}" in workspace
assert "void analyzeSelection(bbox)" in workspace assert "void analyzeSelection(bbox, selectedMapArea?.id)" in workspace
assert "map.on('mousedown'" in geomap assert "map.on('mousedown'" in geomap
assert "map.on('mousemove'" in geomap assert "map.on('mousemove'" in geomap
assert "map.on('mouseup'" in geomap assert "map.on('mouseup'" in geomap
@@ -33,6 +33,8 @@ def test_regional_map_uses_logical_partition_groups_and_exact_analysis() -> None
assert "/datasets/raster/terrain/select" in api assert "/datasets/raster/terrain/select" in api
assert "/datasets/raster/flood-hazard/select" in api assert "/datasets/raster/flood-hazard/select" in api
assert "Rasterlaag actief" in app assert "Rasterlaag actief" in app
assert "void analyzeSelection(bbox, selectedMapArea?.id)" in workspace
assert "onDeriveMapSelectionDataset(bbox, selectedMapArea?.id)" in workspace
def test_maplibre_supports_multiple_persisted_raster_overlays() -> None: def test_maplibre_supports_multiple_persisted_raster_overlays() -> None:
+5
View File
@@ -50,6 +50,11 @@ Validation evidence:
deduplicated VMM scenarios, one 28-municipality MapLibre layer and zero deduplicated VMM scenarios, one 28-municipality MapLibre layer and zero
console warnings/errors. A follow-up status-label fix makes a visible raster console warnings/errors. A follow-up status-label fix makes a visible raster
report `Rasterlaag actief` instead of `Geen actieve laag`. report `Rasterlaag actief` instead of `Geen actieve laag`.
- Interactive browser drawing over the central Kempen returned all fifteen
theme summaries, including 619.71 ha modeled pluvial T100 area, 18.26 m TAW
mean terrain height and 56,310 inhabitants. The boundary probe also exposed
and closed a frontend inconsistency: every selection and derive path now
forwards the active Area id for authoritative backend clipping.
Next: Next:
- Use the now-complete regional current-state layers as the baseline for a - Use the now-complete regional current-state layers as the baseline for a
+6
View File
@@ -528,6 +528,12 @@ combined cell statistics. A monolithic full-region 5 m calculation remains
disabled because it exceeds the governed raster-cell limit; users draw a disabled because it exceeds the governed raster-cell limit; users draw a
bounded rectangle without first choosing a municipality. bounded rectangle without first choosing a municipality.
Every selection entry path forwards the active Area id. This includes dragged
and coordinate rectangles, manual bbox input, temporal comparison, saving a
derived selection Dataset and the advanced GIS workflow. Backend geometry
intersection therefore remains the authority for the municipality or regional
boundary even when a rectangle extends beyond it.
## Useful repository scripts ## Useful repository scripts
- `bash scripts/frontend_install.sh` - `bash scripts/frontend_install.sh`
+9 -10
View File
@@ -676,7 +676,7 @@ interface MapWorkspaceProps {
onRunMapSelectionExtract: (bbox: VectorSelectionBBox, areaId?: string) => Promise<VectorSelectionResponse | null> onRunMapSelectionExtract: (bbox: VectorSelectionBBox, areaId?: string) => Promise<VectorSelectionResponse | null>
onClearMapSelectionExtract: () => void onClearMapSelectionExtract: () => void
onExportMapSelection: (bbox: VectorSelectionBBox) => Promise<unknown> onExportMapSelection: (bbox: VectorSelectionBBox) => Promise<unknown>
onDeriveMapSelectionDataset: (bbox: VectorSelectionBBox) => Promise<DatasetCreateResponse | null> onDeriveMapSelectionDataset: (bbox: VectorSelectionBBox, areaId?: string) => Promise<DatasetCreateResponse | null>
onSelectMapQaReferenceDataset: (datasetId: string) => void onSelectMapQaReferenceDataset: (datasetId: string) => void
onRunMapSelectionQa: (candidateDataset?: DatasetCreateResponse | null) => Promise<QaComparisonResult | null> onRunMapSelectionQa: (candidateDataset?: DatasetCreateResponse | null) => Promise<QaComparisonResult | null>
onOpenMapSelectionQualityEvidence: () => void onOpenMapSelectionQualityEvidence: () => void
@@ -1123,7 +1123,7 @@ export function MapWorkspace({
setSelectionBbox(bbox) setSelectionBbox(bbox)
setFirstSelectionCorner(null) setFirstSelectionCorner(null)
setBboxSelectionMode(false) setBboxSelectionMode(false)
void analyzeSelection(bbox) void analyzeSelection(bbox, selectedMapArea?.id)
} }
const runAreaExtract = () => { const runAreaExtract = () => {
@@ -1131,7 +1131,7 @@ export function MapWorkspace({
if (!bbox) { if (!bbox) {
return return
} }
void analyzeSelection(bbox) void analyzeSelection(bbox, selectedMapArea?.id)
} }
const clearAreaSelection = () => { const clearAreaSelection = () => {
@@ -1183,7 +1183,7 @@ export function MapWorkspace({
if (!bbox) { if (!bbox) {
return return
} }
onDeriveMapSelectionDataset(bbox) onDeriveMapSelectionDataset(bbox, selectedMapArea?.id)
} }
const openSelectedDatabaseLayer = (datasetId: string) => { const openSelectedDatabaseLayer = (datasetId: string) => {
@@ -1251,8 +1251,7 @@ export function MapWorkspace({
if (!mapSelectionBbox || !earlierDatasetId || !laterDatasetId) { if (!mapSelectionBbox || !earlierDatasetId || !laterDatasetId) {
return return
} }
const areaId = selectedAreaBbox && bboxesEqual(mapSelectionBbox, selectedAreaBbox) ? selectedMapAreaId : undefined void compareTemporalSnapshots(earlierDatasetId, laterDatasetId, mapSelectionBbox, selectedMapArea?.id)
void compareTemporalSnapshots(earlierDatasetId, laterDatasetId, mapSelectionBbox, areaId)
} }
const handleMapBboxPreview = (bbox: VectorSelectionBBox) => { const handleMapBboxPreview = (bbox: VectorSelectionBBox) => {
@@ -1262,7 +1261,7 @@ export function MapWorkspace({
const handleMapBboxSelect = (bbox: VectorSelectionBBox) => { const handleMapBboxSelect = (bbox: VectorSelectionBBox) => {
setFirstSelectionCorner(null) setFirstSelectionCorner(null)
setBboxSelectionMode(false) setBboxSelectionMode(false)
void analyzeSelection(bbox) void analyzeSelection(bbox, selectedMapArea?.id)
} }
const runQuickAoiExtract = () => { const runQuickAoiExtract = () => {
@@ -1271,7 +1270,7 @@ export function MapWorkspace({
return return
} }
setSelectionBbox(bbox) setSelectionBbox(bbox)
void analyzeSelection(bbox, selectedAreaBbox ? selectedMapArea?.id : undefined) void analyzeSelection(bbox, selectedMapArea?.id)
} }
const runFullGisWorkflow = async () => { const runFullGisWorkflow = async () => {
@@ -1310,7 +1309,7 @@ export function MapWorkspace({
try { try {
setFullWorkflowStatus('1/4 Querying persisted vector_features...') setFullWorkflowStatus('1/4 Querying persisted vector_features...')
setSelectionBbox(bbox) setSelectionBbox(bbox)
const selection = await onRunMapSelectionExtract(bbox) const selection = await onRunMapSelectionExtract(bbox, selectedMapArea?.id)
if (!selection) { if (!selection) {
setFullWorkflowError('Persisted vector query did not complete.') setFullWorkflowError('Persisted vector query did not complete.')
setFullWorkflowStatus('Stopped at query.') setFullWorkflowStatus('Stopped at query.')
@@ -1318,7 +1317,7 @@ export function MapWorkspace({
} }
setFullWorkflowStatus('2/4 Saving derived result dataset...') setFullWorkflowStatus('2/4 Saving derived result dataset...')
const derived = await onDeriveMapSelectionDataset(bbox) const derived = await onDeriveMapSelectionDataset(bbox, selectedMapArea?.id)
if (!derived) { if (!derived) {
setFullWorkflowError('Derived result dataset was not created.') setFullWorkflowError('Derived result dataset was not created.')
setFullWorkflowStatus('Stopped at dataset save.') setFullWorkflowStatus('Stopped at dataset save.')
+2 -1
View File
@@ -24,7 +24,7 @@ export function useMapSelectionDataset({
const [selectionDatasetError, setSelectionDatasetError] = useState<string | null>(null) const [selectionDatasetError, setSelectionDatasetError] = useState<string | null>(null)
const [latestSelectionDataset, setLatestSelectionDataset] = useState<DatasetCreateResponse | null>(null) const [latestSelectionDataset, setLatestSelectionDataset] = useState<DatasetCreateResponse | null>(null)
const deriveMapSelectionDataset = async (bbox: VectorSelectionBBox) => { const deriveMapSelectionDataset = async (bbox: VectorSelectionBBox, areaId?: string) => {
if (!selectedProjectId || !selectedDataset || !isVectorDatasetType(selectedDataset.dataset_type)) { if (!selectedProjectId || !selectedDataset || !isVectorDatasetType(selectedDataset.dataset_type)) {
setSelectionDatasetError('Select a vector dataset before saving the area as a dataset.') setSelectionDatasetError('Select a vector dataset before saving the area as a dataset.')
return null return null
@@ -34,6 +34,7 @@ export function useMapSelectionDataset({
try { try {
const derived = await datasetsApi.deriveVectorSelection(selectedProjectId, selectedDataset.id, { const derived = await datasetsApi.deriveVectorSelection(selectedProjectId, selectedDataset.id, {
bbox: { ...bbox, crs: 'EPSG:4326' }, bbox: { ...bbox, crs: 'EPSG:4326' },
area_id: areaId,
limit: 250, limit: 250,
output_name: `${selectedDataset.name.replace(/\.(geo)?json$/i, '')}-selection-dataset`, output_name: `${selectedDataset.name.replace(/\.(geo)?json$/i, '')}-selection-dataset`,
}) })