From 47cde21e1735cf6b8fa93c71d3a566cd7b4f80a0 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 17 Jul 2026 20:36:45 +0200 Subject: [PATCH] Add bounded terrain and flood analysis --- CHANGELOG.md | 18 ++ backend/app/schemas/flood_hazard.py | 2 +- .../test_sprint233_operational_completion.py | 2 +- ...t_sprint237_flanders_thematic_on_demand.py | 17 +- ...test_sprint238_flanders_raster_catalogs.py | 43 +++ docs/API_CONTRACTS.md | 3 + docs/CODEX_EXECUTION_LOG.md | 22 ++ frontend/README.md | 15 + frontend/src/components/map/MapWorkspace.tsx | 268 ++++++++++++++---- .../src/hooks/useMapThemeSelectionInsights.ts | 56 ++-- .../src/hooks/useOfficialRasterProducts.ts | 72 +++++ .../src/hooks/useThematicRasterProducts.ts | 48 ---- 12 files changed, 441 insertions(+), 125 deletions(-) create mode 100644 backend/tests/test_sprint238_flanders_raster_catalogs.py create mode 100644 frontend/src/hooks/useOfficialRasterProducts.ts delete mode 100644 frontend/src/hooks/useThematicRasterProducts.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index b5c677c5..c964c5f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ # Changelog +## Sprint 238 Governed Flanders terrain and flood selection (2026-07-17) + +- Unified the Flanders map selection flow behind one official-raster + acquisition contract covering the five policy rasters, DHMV and VMM flood + hazard without adding browser-side provider calls. +- Added explicit DTM/DSM and twelve-scenario VMM registry selectors. Products + are acquired only for the selected municipality or rectangle, persisted as + ordinary Datasets and reused by exact request identity. +- Made one cross-domain area run include measured terrain and scenario-bound + flood metrics alongside the existing policy and bathymetry results. +- Corrected the default VMM request key from the invalid + `pluvial_current_t100` spelling to the governed + `pluviaal_current_t100` product. +- Kept whole-Flanders raster acquisition behind the existing bounded-area + safety gate and retained explicit source semantics: DHMV is height in TAW, + while VMM flood depth is modeled hazard rather than current water level or + bathymetry. + ## Sprint 237 Bounded Flanders cross-domain profile (2026-07-17) - Exposed the five existing governed MercatorNet policy rasters as diff --git a/backend/app/schemas/flood_hazard.py b/backend/app/schemas/flood_hazard.py index ce02ff0f..65eb0178 100644 --- a/backend/app/schemas/flood_hazard.py +++ b/backend/app/schemas/flood_hazard.py @@ -10,7 +10,7 @@ from .operations import VectorSelectionBBox class FloodHazardAcquireRequest(BaseModel): bbox: VectorSelectionBBox area_id: UUID | None = None - product_key: str = "pluvial_current_t100" + product_key: str = "pluviaal_current_t100" resolution_m: float | None = Field(default=None, ge=2.0, le=20.0) force_refresh: bool = False diff --git a/backend/tests/test_sprint233_operational_completion.py b/backend/tests/test_sprint233_operational_completion.py index b265897f..8d3e2a01 100644 --- a/backend/tests/test_sprint233_operational_completion.py +++ b/backend/tests/test_sprint233_operational_completion.py @@ -340,7 +340,7 @@ def test_theme_failures_name_the_source_and_reason() -> None: hook = (root / "frontend/src/hooks/useMapThemeSelectionInsights.ts").read_text(encoding="utf-8") assert "queries[index]?.dataset?.name" in hook - assert "queries[index]?.thematicProductKey" in hook + assert "queries[index]?.acquisition?.displayName" in hook assert "reason: formatError(item.reason" in hook assert "failure.dataset}: ${failure.reason}" in hook diff --git a/backend/tests/test_sprint237_flanders_thematic_on_demand.py b/backend/tests/test_sprint237_flanders_thematic_on_demand.py index 1119359c..84466bff 100644 --- a/backend/tests/test_sprint237_flanders_thematic_on_demand.py +++ b/backend/tests/test_sprint237_flanders_thematic_on_demand.py @@ -10,21 +10,22 @@ def read(path: str) -> str: def test_flanders_workspace_exposes_governed_thematic_products_on_demand() -> None: workspace = read("frontend/src/components/map/MapWorkspace.tsx") - product_hook = read("frontend/src/hooks/useThematicRasterProducts.ts") + product_hook = read("frontend/src/hooks/useOfficialRasterProducts.ts") selection_hook = read("frontend/src/hooks/useMapThemeSelectionInsights.ts") api = read("frontend/src/services/api/datasets.ts") assert "activeScopeProject?.name === FLANDERS_WORKSPACE_PROJECT_NAME" in workspace - assert "new Map" in workspace + assert "new Map" in workspace assert "'Op aanvraag'" in workspace assert "theme.id === 'space_occupation'" in workspace assert "setActiveThemeId(fallbackTheme.id)" in workspace assert "return `referentiejaar ${observationYear}`" in workspace - assert "thematicProductKey: thematicProduct.key" in workspace + assert "kind: 'thematic_raster'" in workspace + assert "productKey: onDemandProduct.productKey" in workspace assert "datasetsApi.listThematicRasterProducts" in product_hook assert "datasetsApi.acquireThematicRaster" in selection_hook assert "datasetsApi.selectThematicRaster" in selection_hook - assert "datasetsApi.get(selectedProjectId, acquisition.output_dataset_id)" in selection_hook + assert "datasetsApi.get(selectedProjectId, acquisitionJob.output_dataset_id)" in selection_hook assert "/datasets/thematic-raster/acquire" in api @@ -35,18 +36,18 @@ def test_selection_runs_all_available_themes_and_refreshes_persisted_datasets() assert "for (const theme of DATA_THEMES)" in workspace assert "await loadThemeInsights(bbox, availableThemes, areaId)" in workspace - assert "!regionalPartitionedThemeActive && !onDemandThematicThemeActive" in workspace + assert "!regionalPartitionedThemeActive && !onDemandRasterThemeActive" in workspace assert "onRefreshProjectData" in workspace assert "selectedProjectId ? loadProjectData(selectedProjectId)" in app - assert "successful.some((item) => item.thematicProductKey)" in selection_hook + assert "successful.some((item) => item.acquisition)" in selection_hook assert "await onDatasetsChanged()" in selection_hook def test_regional_on_demand_rasters_require_a_bounded_drawn_selection() -> None: workspace = read("frontend/src/components/map/MapWorkspace.tsx") - assert "regionalOnDemandThematicThemeActive" in workspace - assert "regionalRasterThemeActive || regionalOnDemandThematicThemeActive" in workspace + assert "regionalOnDemandRasterThemeActive" in workspace + assert "regionalRasterThemeActive || regionalOnDemandRasterThemeActive" in workspace assert "Teken een begrensde rechthoek voor een regionale rasteranalyse." in workspace assert "officiële Vlaamse rasters worden begrensd opgehaald, bewaard en hergebruikt" in workspace diff --git a/backend/tests/test_sprint238_flanders_raster_catalogs.py b/backend/tests/test_sprint238_flanders_raster_catalogs.py new file mode 100644 index 00000000..79a9be51 --- /dev/null +++ b/backend/tests/test_sprint238_flanders_raster_catalogs.py @@ -0,0 +1,43 @@ +from pathlib import Path + +from app.schemas.flood_hazard import FloodHazardAcquireRequest +from app.schemas.operations import VectorSelectionBBox + + +ROOT = Path(__file__).resolve().parents[2] + + +def read(path: str) -> str: + return (ROOT / path).read_text(encoding="utf-8") + + +def test_official_raster_catalog_hook_loads_all_governed_registries() -> None: + hook = read("frontend/src/hooks/useOfficialRasterProducts.ts") + + assert "datasetsApi.listThematicRasterProducts" in hook + assert "datasetsApi.listDhmvProducts" in hook + assert "datasetsApi.listFloodHazardProducts" in hook + assert "Promise.all([" in hook + + +def test_map_selection_can_acquire_dhmv_and_flood_hazard_products() -> None: + selection_hook = read("frontend/src/hooks/useMapThemeSelectionInsights.ts") + workspace = read("frontend/src/components/map/MapWorkspace.tsx") + + assert "'thematic_raster' | 'dhmv' | 'flood_hazard'" in selection_hook + assert "datasetsApi.acquireDhmv" in selection_hook + assert "datasetsApi.acquireFloodHazard" in selection_hook + assert "datasetsApi.acquireThematicRaster" in selection_hook + assert 'aria-label="Hoogtemodel"' in workspace + assert 'aria-label="Overstromingsscenario"' in workspace + assert "product.display_name" in workspace + assert "DTM meet het maaiveld; DSM bevat ook gebouwen en vegetatie." in workspace + assert "geen actuele waterstand" in workspace + + +def test_default_flood_hazard_product_exists_in_the_governed_registry() -> None: + request = FloodHazardAcquireRequest( + bbox=VectorSelectionBBox(min_x=5.0, min_y=51.0, max_x=5.01, max_y=51.01), + ) + + assert request.product_key == "pluviaal_current_t100" diff --git a/docs/API_CONTRACTS.md b/docs/API_CONTRACTS.md index f0220b85..2740249e 100644 --- a/docs/API_CONTRACTS.md +++ b/docs/API_CONTRACTS.md @@ -314,6 +314,9 @@ metres, publication metadata, attribution and limitation. Acquires one bounded official VMM OGRK WCS 1.1 coverage behind the synchronous Job abstraction. Arbitrary coverage identifiers and service URLs are rejected. +When `product_key` is omitted, the governed default is +`pluviaal_current_t100`; the default is itself present in the fixed product +registry. ```json { diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index 339ac04f..7ce465ea 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -10200,3 +10200,25 @@ Final live acceptance: and labelled `coverage_scope=municipality`. The UI reports product reference years directly so an end-of-year UTC timestamp cannot roll 2025 into a misleading local `1 jan 2026` label. + +## Sprint 238 - Governed Flanders terrain and flood selection (2026-07-17) + +Implemented: +- Replaced the policy-raster-specific map query field with a typed official + raster acquisition contract for `thematic_raster`, `dhmv` and + `flood_hazard`. +- Added a single Flanders product hook that loads all three fixed backend + registries in parallel. The browser still uses only canonical GeoIntel API + routes and has no external WCS URL. +- Added an end-user DTM/DSM selector and the full twelve-product VMM scenario + selector. Existing exact-area Datasets are reused; absent products are + acquired, persisted, analyzed and reflected back into the Dataset catalog. +- Extended the all-theme selection run with terrain and flood hazard while + preserving area intersection and the no-whole-Flanders-raster guard. +- Fixed the invalid default flood key spelling in the Pydantic request schema. + +Validation: +- Focused DHMV, VMM, map-orchestration and source-contract tests passed + (49 tests). +- Frontend TypeScript typecheck and production build passed before the full + release gate. diff --git a/frontend/README.md b/frontend/README.md index b4d7ae70..7b9f193d 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -598,6 +598,21 @@ pattern and a rectangle returns scenario-bound hectare/depth metrics. Raster GeoJSON export stays disabled. The UI never labels the maximum-depth area integral as current, permanent or concurrent water volume. +In the Flanders workspace, DHMV and VMM products do not need to be +pre-provisioned before they become usable. The explorer reads the governed +backend registries, exposes DTM/DSM and all twelve VMM scenarios, and acquires +only the municipality or rectangle the user explicitly analyses. The +acquisition still runs through the backend Job and Dataset boundaries; the +browser never contacts WCS directly. An exact repeat reuses the persisted +request. A complete-Flanders raster request remains disabled by the regional +size guard, while a complete municipality remains a valid bounded analysis. + +Selecting `Hoogte & reliëf` shows the source distinction before analysis: +DTM represents terrain after removal of buildings and other objects; DSM +represents the visible surface including buildings and vegetation. Selecting +`Overstroming` shows mechanism, climate context and return period. The result +therefore cannot be mistaken for an observed current water level. + Regional VMM provisioning creates one scenario raster per municipality Area. For a municipality the explorer still uses only that exact Area-linked file. For the complete Kempen Area it presents the 28 VMM and DHMV partitions as one diff --git a/frontend/src/components/map/MapWorkspace.tsx b/frontend/src/components/map/MapWorkspace.tsx index df9bb60d..2e32368e 100644 --- a/frontend/src/components/map/MapWorkspace.tsx +++ b/frontend/src/components/map/MapWorkspace.tsx @@ -1,8 +1,8 @@ import { useEffect, useMemo, useState } from 'react' import GeoMap from '../GeoMap' -import type { AreaRead, DatasetCreateResponse, DetectionQaResult, MapResultExportRequest, MapViewportState, OrthophotoAcquisitionResult, OrthophotoProductRead, ProjectRead, QaComparisonResult, ThematicRasterProductRead, VectorSelectionBBox, VectorSelectionMetric, VectorSelectionResponse } from '../../types' -import { useMapThemeSelectionInsights, type MapThemeQuery } from '../../hooks/useMapThemeSelectionInsights' -import { useThematicRasterProducts } from '../../hooks/useThematicRasterProducts' +import type { AreaRead, DatasetCreateResponse, DetectionQaResult, MapResultExportRequest, MapViewportState, OrthophotoAcquisitionResult, OrthophotoProductRead, ProjectRead, QaComparisonResult, VectorSelectionBBox, VectorSelectionMetric, VectorSelectionResponse } from '../../types' +import { useMapThemeSelectionInsights, type MapThemeAcquisition, type MapThemeQuery } from '../../hooks/useMapThemeSelectionInsights' +import { useOfficialRasterProducts } from '../../hooks/useOfficialRasterProducts' import { useTemporalComparison } from '../../hooks/useTemporalComparison' import { getDatasetDisplayName, getDatasetSourceDisplayName } from '../../lib/datasetDisplay' import { TemporalTrendChart } from './TemporalTrendChart' @@ -54,6 +54,14 @@ interface TemporalSeriesGroup { items: DatasetCreateResponse[] } +interface OnDemandRasterProduct extends MapThemeAcquisition { + theme: DataThemeId + nativeResolutionM: number + referenceLabel: string + attribution: string + limitationMessage: string +} + const DATA_THEMES: DataTheme[] = [ { id: 'buildings', @@ -661,10 +669,10 @@ export function MapWorkspace({ clearThemeInsights, } = useMapThemeSelectionInsights(selectedProjectId, onRefreshProjectData) const { - products: thematicRasterProducts, - loading: thematicRasterProductsLoading, - error: thematicRasterProductsError, - } = useThematicRasterProducts(flandersScopeSelected ? selectedProjectId : null) + products: officialRasterProducts, + loading: officialRasterProductsLoading, + error: officialRasterProductsError, + } = useOfficialRasterProducts(flandersScopeSelected ? selectedProjectId : null) const { temporalComparison, temporalComparisonLoading, @@ -674,6 +682,8 @@ export function MapWorkspace({ } = useTemporalComparison(selectedProjectId) const [analysisMode, setAnalysisMode] = useState<'current' | 'evolution'>('current') const [selectedFloodHazardDatasetId, setSelectedFloodHazardDatasetId] = useState('') + const [selectedDhmvProductKey, setSelectedDhmvProductKey] = useState<'dtm_1m' | 'dsm_1m'>('dtm_1m') + const [selectedFloodHazardProductKey, setSelectedFloodHazardProductKey] = useState('pluviaal_current_t100') const [selectedTemporalSeriesKey, setSelectedTemporalSeriesKey] = useState('') const [earlierDatasetId, setEarlierDatasetId] = useState('') const [laterDatasetId, setLaterDatasetId] = useState('') @@ -743,12 +753,37 @@ export function MapWorkspace({ pickThemeDataset(availableMapDatasets, theme, selectedMapAreaId, regionalScopeSelected), ]), ) as Record - const selectedFloodHazard = floodHazardDatasets.find((dataset) => dataset.id === selectedFloodHazardDatasetId) + const selectedFloodHazard = floodHazardDatasets.find( + (dataset) => + dataset.id === selectedFloodHazardDatasetId + || (flandersScopeSelected && datasetProductKey(dataset) === selectedFloodHazardProductKey), + ) if (selectedFloodHazard) { result.flood_hazard = selectedFloodHazard + } else if (flandersScopeSelected && officialRasterProducts.floodHazard.length > 0) { + result.flood_hazard = null + } + if (flandersScopeSelected && officialRasterProducts.dhmv.length > 0) { + result.elevation = availableMapDatasets.find( + (dataset) => + dataset.source_name === 'digitaal_vlaanderen_dhmv' + && datasetProductKey(dataset) === selectedDhmvProductKey + && datasetCoversSelectedArea(dataset, selectedMapAreaId, regionalScopeSelected), + ) ?? null } return result - }, [availableMapDatasets, floodHazardDatasets, regionalScopeSelected, selectedFloodHazardDatasetId, selectedMapAreaId]) + }, [ + availableMapDatasets, + flandersScopeSelected, + floodHazardDatasets, + officialRasterProducts.dhmv.length, + officialRasterProducts.floodHazard.length, + regionalScopeSelected, + selectedDhmvProductKey, + selectedFloodHazardDatasetId, + selectedFloodHazardProductKey, + selectedMapAreaId, + ]) const themePartitionMap = useMemo( () => Object.fromEntries( @@ -765,15 +800,62 @@ export function MapWorkspace({ [availableMapDatasets, regionalScopeSelected, selectedMapAreaId, themeDatasetMap], ) const activeTheme = DATA_THEMES.find((theme) => theme.id === activeThemeId) ?? DATA_THEMES[0] - const onDemandThematicProductMap = useMemo( - () => flandersScopeSelected - ? new Map( - thematicRasterProducts.map((product) => [product.theme, product]), - ) - : new Map(), - [flandersScopeSelected, thematicRasterProducts], + const onDemandRasterProductMap = useMemo( + () => { + const result = new Map() + if (!flandersScopeSelected) { + return result + } + for (const product of officialRasterProducts.thematic) { + result.set(product.theme, { + kind: 'thematic_raster', + productKey: product.key, + displayName: product.display_name, + theme: product.theme, + nativeResolutionM: product.native_resolution_m, + referenceLabel: String(product.observation_year), + attribution: product.attribution, + limitationMessage: product.limitation_message, + }) + } + const dhmvProduct = officialRasterProducts.dhmv.find((product) => product.key === selectedDhmvProductKey) + if (dhmvProduct) { + result.set('elevation', { + kind: 'dhmv', + productKey: dhmvProduct.key, + displayName: dhmvProduct.display_name, + theme: 'elevation', + nativeResolutionM: dhmvProduct.native_resolution_m, + referenceLabel: dhmvProduct.acquisition_period, + attribution: dhmvProduct.attribution, + limitationMessage: dhmvProduct.limitation_message, + }) + } + const floodProduct = officialRasterProducts.floodHazard.find( + (product) => product.key === selectedFloodHazardProductKey, + ) + if (floodProduct) { + result.set('flood_hazard', { + kind: 'flood_hazard', + productKey: floodProduct.key, + displayName: floodProduct.display_name, + theme: 'flood_hazard', + nativeResolutionM: floodProduct.native_resolution_m, + referenceLabel: `${floodProduct.climate_context} · T${floodProduct.return_period_years}`, + attribution: floodProduct.attribution, + limitationMessage: floodProduct.limitation_message, + }) + } + return result + }, + [ + flandersScopeSelected, + officialRasterProducts, + selectedDhmvProductKey, + selectedFloodHazardProductKey, + ], ) - const activeOnDemandThematicProduct = onDemandThematicProductMap.get(activeTheme.id) ?? null + const activeOnDemandRasterProduct = onDemandRasterProductMap.get(activeTheme.id) ?? null const activeThemeMapStyle = DATA_THEME_MAP_STYLES[activeTheme.id] const analysisOverlayActive = mapContentMode === 'analysis' && analysisLayerAvailable && Boolean(mapFeatureCollection) const selectedOrthophotoProduct = orthophotoProducts.find((item) => item.key === selectedOrthophotoProductKey) ?? null @@ -793,24 +875,24 @@ export function MapWorkspace({ const regionalRasterThemeActive = regionalScopeSelected && isPartitionedRaster(activeThemeDataset) const regionalBathymetryThemeActive = regionalScopeSelected && isPartitionedBathymetry(activeThemeDataset) const regionalPartitionedThemeActive = regionalRasterThemeActive || regionalBathymetryThemeActive - const onDemandThematicThemeActive = analysisMode === 'current' && Boolean(activeOnDemandThematicProduct) - const regionalOnDemandThematicThemeActive = regionalScopeSelected && onDemandThematicThemeActive - const activeThemeAvailable = Boolean(activeThemeDataset) || onDemandThematicThemeActive + const onDemandRasterThemeActive = analysisMode === 'current' && Boolean(activeOnDemandRasterProduct) + const regionalOnDemandRasterThemeActive = regionalScopeSelected && onDemandRasterThemeActive + const activeThemeAvailable = Boolean(activeThemeDataset) || onDemandRasterThemeActive useEffect(() => { if ( !flandersScopeSelected || analysisMode !== 'current' || activeThemeAvailable - || (onDemandThematicProductMap.size === 0 && !thematicRasterProductsError) + || (onDemandRasterProductMap.size === 0 && !officialRasterProductsError) ) { return } const fallbackTheme = DATA_THEMES.find((theme) => theme.id === 'space_occupation' - && Boolean(themeDatasetMap[theme.id] || onDemandThematicProductMap.get(theme.id)), + && Boolean(themeDatasetMap[theme.id] || onDemandRasterProductMap.get(theme.id)), ) ?? DATA_THEMES.find((theme) => - Boolean(themeDatasetMap[theme.id] || onDemandThematicProductMap.get(theme.id)), + Boolean(themeDatasetMap[theme.id] || onDemandRasterProductMap.get(theme.id)), ) if (!fallbackTheme) { return @@ -824,9 +906,9 @@ export function MapWorkspace({ activeThemeAvailable, analysisMode, flandersScopeSelected, - onDemandThematicProductMap, + officialRasterProductsError, + onDemandRasterProductMap, onOpenDatasetInMap, - thematicRasterProductsError, themeDatasetMap, ]) @@ -917,12 +999,12 @@ export function MapWorkspace({ useEffect(() => { const contextSourceLabel = analysisMode === 'evolution' ? activeTemporalSeriesGroup?.label ?? null : regionalBathymetryThemeActive ? 'VHA-dwarsprofielen Vlaanderen' - : activeOnDemandThematicProduct?.display_name ?? null + : activeOnDemandRasterProduct?.displayName ?? null onSetContextSourceLabel(contextSourceLabel) return () => onSetContextSourceLabel(null) }, [ activeTemporalSeriesGroup?.label, - activeOnDemandThematicProduct?.display_name, + activeOnDemandRasterProduct?.displayName, analysisMode, onSetContextSourceLabel, regionalBathymetryThemeActive, @@ -1021,14 +1103,33 @@ export function MapWorkspace({ }, [activeTemporalSeriesGroups]) useEffect(() => { - if (floodHazardDatasets.some((dataset) => dataset.id === selectedFloodHazardDatasetId)) { + const selected = floodHazardDatasets.find( + (dataset) => datasetProductKey(dataset) === selectedFloodHazardProductKey, + ) + if (selected) { + if (selected.id !== selectedFloodHazardDatasetId) { + setSelectedFloodHazardDatasetId(selected.id) + } + return + } + if (flandersScopeSelected && officialRasterProducts.floodHazard.length > 0) { + setSelectedFloodHazardDatasetId('') return } const preferred = floodHazardDatasets.find( (dataset) => dataset.source_metadata?.['product_key'] === 'pluviaal_current_t100', ) ?? floodHazardDatasets[0] setSelectedFloodHazardDatasetId(preferred?.id ?? '') - }, [floodHazardDatasets, selectedFloodHazardDatasetId]) + if (preferred && datasetProductKey(preferred)) { + setSelectedFloodHazardProductKey(datasetProductKey(preferred)) + } + }, [ + flandersScopeSelected, + floodHazardDatasets, + officialRasterProducts.floodHazard.length, + selectedFloodHazardDatasetId, + selectedFloodHazardProductKey, + ]) useEffect(() => { const first = activeTemporalSeries[0] @@ -1049,6 +1150,17 @@ export function MapWorkspace({ if (!selectedMapDataset) { return } + const selectedProductKey = datasetProductKey(selectedMapDataset) + if ( + selectedMapDataset.source_name === 'digitaal_vlaanderen_dhmv' + && (selectedProductKey === 'dtm_1m' || selectedProductKey === 'dsm_1m') + ) { + setSelectedDhmvProductKey(selectedProductKey) + } + if (selectedMapDataset.source_name === 'vmm_flood_hazard' && selectedProductKey) { + setSelectedFloodHazardProductKey(selectedProductKey) + setSelectedFloodHazardDatasetId(selectedMapDataset.id) + } const matchingThemeId = themeIdForDataset(selectedMapDataset) if (matchingThemeId) { setActiveThemeId(matchingThemeId) @@ -1238,7 +1350,7 @@ export function MapWorkspace({ ? temporalGroup?.items[temporalGroup.items.length - 1] ?? null : themeDatasetMap[theme.id] const onDemandProduct = analysisMode === 'current' - ? onDemandThematicProductMap.get(theme.id) + ? onDemandRasterProductMap.get(theme.id) : null if (!dataset && !onDemandProduct) { return @@ -1268,13 +1380,17 @@ export function MapWorkspace({ const loadAllThemeResults = async (bbox: VectorSelectionBBox, areaId?: string) => { const availableThemes: Array> = [] for (const theme of DATA_THEMES) { - const thematicProduct = analysisMode === 'current' - ? onDemandThematicProductMap.get(theme.id) + const onDemandProduct = analysisMode === 'current' + ? onDemandRasterProductMap.get(theme.id) : null - if (thematicProduct) { + if (onDemandProduct) { availableThemes.push({ themeId: theme.id, - thematicProductKey: thematicProduct.key, + acquisition: { + kind: onDemandProduct.kind, + productKey: onDemandProduct.productKey, + displayName: onDemandProduct.displayName, + }, }) continue } @@ -1294,7 +1410,7 @@ export function MapWorkspace({ const analyzeSelection = async (bbox: VectorSelectionBBox, areaId?: string) => { setSelectionBbox(bbox) const tasks: Array> = [loadAllThemeResults(bbox, areaId)] - if (!regionalPartitionedThemeActive && !onDemandThematicThemeActive) { + if (!regionalPartitionedThemeActive && !onDemandRasterThemeActive) { tasks.push(onRunMapSelectionExtract(bbox, areaId)) } if (analysisMode === 'evolution' && earlierDatasetId && laterDatasetId) { @@ -1455,7 +1571,7 @@ export function MapWorkspace({
{DATA_THEMES.map((theme) => { const dataset = themeDatasetMap[theme.id] - const onDemandProduct = onDemandThematicProductMap.get(theme.id) + const onDemandProduct = onDemandRasterProductMap.get(theme.id) const partitions = themePartitionMap[theme.id] const temporalGroups = themeTemporalSeriesMap[theme.id] const temporalGroup = temporalGroups[0] @@ -1487,7 +1603,7 @@ export function MapWorkspace({ : dataset ? datasetAvailabilityLabel(dataset, partitions) : onDemandProduct - ? `${onDemandProduct.native_resolution_m} m · ${onDemandProduct.observation_year} · laad bij selectie` + ? `${onDemandProduct.nativeResolutionM} m · ${onDemandProduct.referenceLabel} · laad bij selectie` : 'Bron nog niet ingeladen'} @@ -1512,7 +1628,7 @@ export function MapWorkspace({ ? 'VHA-dwarsprofielen Vlaanderen' : activeThemeDataset ? getDatasetDisplayName(activeThemeDataset) - : activeOnDemandThematicProduct?.display_name ?? 'Geen databron beschikbaar'} + : activeOnDemandRasterProduct?.displayName ?? 'Geen databron beschikbaar'} {analysisOverlayActive @@ -1525,19 +1641,71 @@ export function MapWorkspace({ ? `${activeThemePartitions.length} gecontroleerde gemeentepartities · selectie wordt ruimtelijk samengevoegd` : activeThemeDataset ? `${getDatasetSourceDisplayName(activeThemeDataset)} · ${formatDatasetObservation(activeThemeDataset)}` - : activeOnDemandThematicProduct - ? `${activeOnDemandThematicProduct.attribution} · wordt alleen voor de gekozen selectie ingeladen` + : activeOnDemandRasterProduct + ? `${activeOnDemandRasterProduct.attribution} · wordt alleen voor de gekozen selectie ingeladen` : activeTheme.description}
- {thematicRasterProductsLoading && flandersScopeSelected ? ( -

Beschikbare Vlaamse beleidsbronnen worden gecontroleerd…

+ {officialRasterProductsLoading && flandersScopeSelected ? ( +

Beschikbare Vlaamse rasterbronnen worden gecontroleerd…

) : null} - {thematicRasterProductsError && flandersScopeSelected ? ( -

{thematicRasterProductsError}

+ {officialRasterProductsError && flandersScopeSelected ? ( +

{officialRasterProductsError}

) : null} - {analysisMode === 'current' && activeTheme.id === 'flood_hazard' && floodHazardDatasets.length > 0 ? ( + {analysisMode === 'current' && activeTheme.id === 'elevation' && officialRasterProducts.dhmv.length > 0 ? ( + + ) : null} + + {analysisMode === 'current' && activeTheme.id === 'flood_hazard' && officialRasterProducts.floodHazard.length > 0 ? ( + + ) : analysisMode === 'current' && activeTheme.id === 'flood_hazard' && floodHazardDatasets.length > 0 ? (