Add bounded terrain and flood analysis
This commit is contained in:
@@ -7,6 +7,24 @@
|
|||||||
|
|
||||||
# Changelog
|
# 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)
|
## Sprint 237 Bounded Flanders cross-domain profile (2026-07-17)
|
||||||
|
|
||||||
- Exposed the five existing governed MercatorNet policy rasters as
|
- Exposed the five existing governed MercatorNet policy rasters as
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from .operations import VectorSelectionBBox
|
|||||||
class FloodHazardAcquireRequest(BaseModel):
|
class FloodHazardAcquireRequest(BaseModel):
|
||||||
bbox: VectorSelectionBBox
|
bbox: VectorSelectionBBox
|
||||||
area_id: UUID | None = None
|
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)
|
resolution_m: float | None = Field(default=None, ge=2.0, le=20.0)
|
||||||
force_refresh: bool = False
|
force_refresh: bool = False
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
hook = (root / "frontend/src/hooks/useMapThemeSelectionInsights.ts").read_text(encoding="utf-8")
|
||||||
|
|
||||||
assert "queries[index]?.dataset?.name" in hook
|
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 "reason: formatError(item.reason" in hook
|
||||||
assert "failure.dataset}: ${failure.reason}" in hook
|
assert "failure.dataset}: ${failure.reason}" in hook
|
||||||
|
|
||||||
|
|||||||
@@ -10,21 +10,22 @@ def read(path: str) -> str:
|
|||||||
|
|
||||||
def test_flanders_workspace_exposes_governed_thematic_products_on_demand() -> None:
|
def test_flanders_workspace_exposes_governed_thematic_products_on_demand() -> None:
|
||||||
workspace = read("frontend/src/components/map/MapWorkspace.tsx")
|
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")
|
selection_hook = read("frontend/src/hooks/useMapThemeSelectionInsights.ts")
|
||||||
api = read("frontend/src/services/api/datasets.ts")
|
api = read("frontend/src/services/api/datasets.ts")
|
||||||
|
|
||||||
assert "activeScopeProject?.name === FLANDERS_WORKSPACE_PROJECT_NAME" in workspace
|
assert "activeScopeProject?.name === FLANDERS_WORKSPACE_PROJECT_NAME" in workspace
|
||||||
assert "new Map<DataThemeId, ThematicRasterProductRead>" in workspace
|
assert "new Map<DataThemeId, OnDemandRasterProduct>" in workspace
|
||||||
assert "'Op aanvraag'" in workspace
|
assert "'Op aanvraag'" in workspace
|
||||||
assert "theme.id === 'space_occupation'" in workspace
|
assert "theme.id === 'space_occupation'" in workspace
|
||||||
assert "setActiveThemeId(fallbackTheme.id)" in workspace
|
assert "setActiveThemeId(fallbackTheme.id)" in workspace
|
||||||
assert "return `referentiejaar ${observationYear}`" 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.listThematicRasterProducts" in product_hook
|
||||||
assert "datasetsApi.acquireThematicRaster" in selection_hook
|
assert "datasetsApi.acquireThematicRaster" in selection_hook
|
||||||
assert "datasetsApi.selectThematicRaster" 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
|
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 "for (const theme of DATA_THEMES)" in workspace
|
||||||
assert "await loadThemeInsights(bbox, availableThemes, areaId)" 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 "onRefreshProjectData" in workspace
|
||||||
assert "selectedProjectId ? loadProjectData(selectedProjectId)" in app
|
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
|
assert "await onDatasetsChanged()" in selection_hook
|
||||||
|
|
||||||
|
|
||||||
def test_regional_on_demand_rasters_require_a_bounded_drawn_selection() -> None:
|
def test_regional_on_demand_rasters_require_a_bounded_drawn_selection() -> None:
|
||||||
workspace = read("frontend/src/components/map/MapWorkspace.tsx")
|
workspace = read("frontend/src/components/map/MapWorkspace.tsx")
|
||||||
|
|
||||||
assert "regionalOnDemandThematicThemeActive" in workspace
|
assert "regionalOnDemandRasterThemeActive" in workspace
|
||||||
assert "regionalRasterThemeActive || regionalOnDemandThematicThemeActive" in workspace
|
assert "regionalRasterThemeActive || regionalOnDemandRasterThemeActive" in workspace
|
||||||
assert "Teken een begrensde rechthoek voor een regionale rasteranalyse." 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
|
assert "officiële Vlaamse rasters worden begrensd opgehaald, bewaard en hergebruikt" in workspace
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -314,6 +314,9 @@ metres, publication metadata, attribution and limitation.
|
|||||||
|
|
||||||
Acquires one bounded official VMM OGRK WCS 1.1 coverage behind the synchronous
|
Acquires one bounded official VMM OGRK WCS 1.1 coverage behind the synchronous
|
||||||
Job abstraction. Arbitrary coverage identifiers and service URLs are rejected.
|
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
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10200,3 +10200,25 @@ Final live acceptance:
|
|||||||
and labelled `coverage_scope=municipality`. The UI reports product reference
|
and labelled `coverage_scope=municipality`. The UI reports product reference
|
||||||
years directly so an end-of-year UTC timestamp cannot roll 2025 into a
|
years directly so an end-of-year UTC timestamp cannot roll 2025 into a
|
||||||
misleading local `1 jan 2026` label.
|
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.
|
||||||
|
|||||||
@@ -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
|
GeoJSON export stays disabled. The UI never labels the maximum-depth area
|
||||||
integral as current, permanent or concurrent water volume.
|
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.
|
Regional VMM provisioning creates one scenario raster per municipality Area.
|
||||||
For a municipality the explorer still uses only that exact Area-linked file.
|
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
|
For the complete Kempen Area it presents the 28 VMM and DHMV partitions as one
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import GeoMap from '../GeoMap'
|
import GeoMap from '../GeoMap'
|
||||||
import type { AreaRead, DatasetCreateResponse, DetectionQaResult, MapResultExportRequest, MapViewportState, OrthophotoAcquisitionResult, OrthophotoProductRead, ProjectRead, QaComparisonResult, ThematicRasterProductRead, VectorSelectionBBox, VectorSelectionMetric, VectorSelectionResponse } from '../../types'
|
import type { AreaRead, DatasetCreateResponse, DetectionQaResult, MapResultExportRequest, MapViewportState, OrthophotoAcquisitionResult, OrthophotoProductRead, ProjectRead, QaComparisonResult, VectorSelectionBBox, VectorSelectionMetric, VectorSelectionResponse } from '../../types'
|
||||||
import { useMapThemeSelectionInsights, type MapThemeQuery } from '../../hooks/useMapThemeSelectionInsights'
|
import { useMapThemeSelectionInsights, type MapThemeAcquisition, type MapThemeQuery } from '../../hooks/useMapThemeSelectionInsights'
|
||||||
import { useThematicRasterProducts } from '../../hooks/useThematicRasterProducts'
|
import { useOfficialRasterProducts } from '../../hooks/useOfficialRasterProducts'
|
||||||
import { useTemporalComparison } from '../../hooks/useTemporalComparison'
|
import { useTemporalComparison } from '../../hooks/useTemporalComparison'
|
||||||
import { getDatasetDisplayName, getDatasetSourceDisplayName } from '../../lib/datasetDisplay'
|
import { getDatasetDisplayName, getDatasetSourceDisplayName } from '../../lib/datasetDisplay'
|
||||||
import { TemporalTrendChart } from './TemporalTrendChart'
|
import { TemporalTrendChart } from './TemporalTrendChart'
|
||||||
@@ -54,6 +54,14 @@ interface TemporalSeriesGroup {
|
|||||||
items: DatasetCreateResponse[]
|
items: DatasetCreateResponse[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OnDemandRasterProduct extends MapThemeAcquisition {
|
||||||
|
theme: DataThemeId
|
||||||
|
nativeResolutionM: number
|
||||||
|
referenceLabel: string
|
||||||
|
attribution: string
|
||||||
|
limitationMessage: string
|
||||||
|
}
|
||||||
|
|
||||||
const DATA_THEMES: DataTheme[] = [
|
const DATA_THEMES: DataTheme[] = [
|
||||||
{
|
{
|
||||||
id: 'buildings',
|
id: 'buildings',
|
||||||
@@ -661,10 +669,10 @@ export function MapWorkspace({
|
|||||||
clearThemeInsights,
|
clearThemeInsights,
|
||||||
} = useMapThemeSelectionInsights<DataThemeId>(selectedProjectId, onRefreshProjectData)
|
} = useMapThemeSelectionInsights<DataThemeId>(selectedProjectId, onRefreshProjectData)
|
||||||
const {
|
const {
|
||||||
products: thematicRasterProducts,
|
products: officialRasterProducts,
|
||||||
loading: thematicRasterProductsLoading,
|
loading: officialRasterProductsLoading,
|
||||||
error: thematicRasterProductsError,
|
error: officialRasterProductsError,
|
||||||
} = useThematicRasterProducts(flandersScopeSelected ? selectedProjectId : null)
|
} = useOfficialRasterProducts(flandersScopeSelected ? selectedProjectId : null)
|
||||||
const {
|
const {
|
||||||
temporalComparison,
|
temporalComparison,
|
||||||
temporalComparisonLoading,
|
temporalComparisonLoading,
|
||||||
@@ -674,6 +682,8 @@ export function MapWorkspace({
|
|||||||
} = useTemporalComparison(selectedProjectId)
|
} = useTemporalComparison(selectedProjectId)
|
||||||
const [analysisMode, setAnalysisMode] = useState<'current' | 'evolution'>('current')
|
const [analysisMode, setAnalysisMode] = useState<'current' | 'evolution'>('current')
|
||||||
const [selectedFloodHazardDatasetId, setSelectedFloodHazardDatasetId] = useState('')
|
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 [selectedTemporalSeriesKey, setSelectedTemporalSeriesKey] = useState('')
|
||||||
const [earlierDatasetId, setEarlierDatasetId] = useState('')
|
const [earlierDatasetId, setEarlierDatasetId] = useState('')
|
||||||
const [laterDatasetId, setLaterDatasetId] = useState('')
|
const [laterDatasetId, setLaterDatasetId] = useState('')
|
||||||
@@ -743,12 +753,37 @@ export function MapWorkspace({
|
|||||||
pickThemeDataset(availableMapDatasets, theme, selectedMapAreaId, regionalScopeSelected),
|
pickThemeDataset(availableMapDatasets, theme, selectedMapAreaId, regionalScopeSelected),
|
||||||
]),
|
]),
|
||||||
) as Record<DataThemeId, DatasetCreateResponse | null>
|
) as Record<DataThemeId, DatasetCreateResponse | null>
|
||||||
const selectedFloodHazard = floodHazardDatasets.find((dataset) => dataset.id === selectedFloodHazardDatasetId)
|
const selectedFloodHazard = floodHazardDatasets.find(
|
||||||
|
(dataset) =>
|
||||||
|
dataset.id === selectedFloodHazardDatasetId
|
||||||
|
|| (flandersScopeSelected && datasetProductKey(dataset) === selectedFloodHazardProductKey),
|
||||||
|
)
|
||||||
if (selectedFloodHazard) {
|
if (selectedFloodHazard) {
|
||||||
result.flood_hazard = 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
|
return result
|
||||||
}, [availableMapDatasets, floodHazardDatasets, regionalScopeSelected, selectedFloodHazardDatasetId, selectedMapAreaId])
|
}, [
|
||||||
|
availableMapDatasets,
|
||||||
|
flandersScopeSelected,
|
||||||
|
floodHazardDatasets,
|
||||||
|
officialRasterProducts.dhmv.length,
|
||||||
|
officialRasterProducts.floodHazard.length,
|
||||||
|
regionalScopeSelected,
|
||||||
|
selectedDhmvProductKey,
|
||||||
|
selectedFloodHazardDatasetId,
|
||||||
|
selectedFloodHazardProductKey,
|
||||||
|
selectedMapAreaId,
|
||||||
|
])
|
||||||
const themePartitionMap = useMemo(
|
const themePartitionMap = useMemo(
|
||||||
() =>
|
() =>
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
@@ -765,15 +800,62 @@ export function MapWorkspace({
|
|||||||
[availableMapDatasets, regionalScopeSelected, selectedMapAreaId, themeDatasetMap],
|
[availableMapDatasets, regionalScopeSelected, selectedMapAreaId, themeDatasetMap],
|
||||||
)
|
)
|
||||||
const activeTheme = DATA_THEMES.find((theme) => theme.id === activeThemeId) ?? DATA_THEMES[0]
|
const activeTheme = DATA_THEMES.find((theme) => theme.id === activeThemeId) ?? DATA_THEMES[0]
|
||||||
const onDemandThematicProductMap = useMemo(
|
const onDemandRasterProductMap = useMemo(
|
||||||
() => flandersScopeSelected
|
() => {
|
||||||
? new Map<DataThemeId, ThematicRasterProductRead>(
|
const result = new Map<DataThemeId, OnDemandRasterProduct>()
|
||||||
thematicRasterProducts.map((product) => [product.theme, product]),
|
if (!flandersScopeSelected) {
|
||||||
)
|
return result
|
||||||
: new Map<DataThemeId, ThematicRasterProductRead>(),
|
}
|
||||||
[flandersScopeSelected, thematicRasterProducts],
|
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 activeThemeMapStyle = DATA_THEME_MAP_STYLES[activeTheme.id]
|
||||||
const analysisOverlayActive = mapContentMode === 'analysis' && analysisLayerAvailable && Boolean(mapFeatureCollection)
|
const analysisOverlayActive = mapContentMode === 'analysis' && analysisLayerAvailable && Boolean(mapFeatureCollection)
|
||||||
const selectedOrthophotoProduct = orthophotoProducts.find((item) => item.key === selectedOrthophotoProductKey) ?? null
|
const selectedOrthophotoProduct = orthophotoProducts.find((item) => item.key === selectedOrthophotoProductKey) ?? null
|
||||||
@@ -793,24 +875,24 @@ export function MapWorkspace({
|
|||||||
const regionalRasterThemeActive = regionalScopeSelected && isPartitionedRaster(activeThemeDataset)
|
const regionalRasterThemeActive = regionalScopeSelected && isPartitionedRaster(activeThemeDataset)
|
||||||
const regionalBathymetryThemeActive = regionalScopeSelected && isPartitionedBathymetry(activeThemeDataset)
|
const regionalBathymetryThemeActive = regionalScopeSelected && isPartitionedBathymetry(activeThemeDataset)
|
||||||
const regionalPartitionedThemeActive = regionalRasterThemeActive || regionalBathymetryThemeActive
|
const regionalPartitionedThemeActive = regionalRasterThemeActive || regionalBathymetryThemeActive
|
||||||
const onDemandThematicThemeActive = analysisMode === 'current' && Boolean(activeOnDemandThematicProduct)
|
const onDemandRasterThemeActive = analysisMode === 'current' && Boolean(activeOnDemandRasterProduct)
|
||||||
const regionalOnDemandThematicThemeActive = regionalScopeSelected && onDemandThematicThemeActive
|
const regionalOnDemandRasterThemeActive = regionalScopeSelected && onDemandRasterThemeActive
|
||||||
const activeThemeAvailable = Boolean(activeThemeDataset) || onDemandThematicThemeActive
|
const activeThemeAvailable = Boolean(activeThemeDataset) || onDemandRasterThemeActive
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!flandersScopeSelected
|
!flandersScopeSelected
|
||||||
|| analysisMode !== 'current'
|
|| analysisMode !== 'current'
|
||||||
|| activeThemeAvailable
|
|| activeThemeAvailable
|
||||||
|| (onDemandThematicProductMap.size === 0 && !thematicRasterProductsError)
|
|| (onDemandRasterProductMap.size === 0 && !officialRasterProductsError)
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const fallbackTheme = DATA_THEMES.find((theme) =>
|
const fallbackTheme = DATA_THEMES.find((theme) =>
|
||||||
theme.id === 'space_occupation'
|
theme.id === 'space_occupation'
|
||||||
&& Boolean(themeDatasetMap[theme.id] || onDemandThematicProductMap.get(theme.id)),
|
&& Boolean(themeDatasetMap[theme.id] || onDemandRasterProductMap.get(theme.id)),
|
||||||
) ?? DATA_THEMES.find((theme) =>
|
) ?? DATA_THEMES.find((theme) =>
|
||||||
Boolean(themeDatasetMap[theme.id] || onDemandThematicProductMap.get(theme.id)),
|
Boolean(themeDatasetMap[theme.id] || onDemandRasterProductMap.get(theme.id)),
|
||||||
)
|
)
|
||||||
if (!fallbackTheme) {
|
if (!fallbackTheme) {
|
||||||
return
|
return
|
||||||
@@ -824,9 +906,9 @@ export function MapWorkspace({
|
|||||||
activeThemeAvailable,
|
activeThemeAvailable,
|
||||||
analysisMode,
|
analysisMode,
|
||||||
flandersScopeSelected,
|
flandersScopeSelected,
|
||||||
onDemandThematicProductMap,
|
officialRasterProductsError,
|
||||||
|
onDemandRasterProductMap,
|
||||||
onOpenDatasetInMap,
|
onOpenDatasetInMap,
|
||||||
thematicRasterProductsError,
|
|
||||||
themeDatasetMap,
|
themeDatasetMap,
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -917,12 +999,12 @@ export function MapWorkspace({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const contextSourceLabel = analysisMode === 'evolution' ? activeTemporalSeriesGroup?.label ?? null
|
const contextSourceLabel = analysisMode === 'evolution' ? activeTemporalSeriesGroup?.label ?? null
|
||||||
: regionalBathymetryThemeActive ? 'VHA-dwarsprofielen Vlaanderen'
|
: regionalBathymetryThemeActive ? 'VHA-dwarsprofielen Vlaanderen'
|
||||||
: activeOnDemandThematicProduct?.display_name ?? null
|
: activeOnDemandRasterProduct?.displayName ?? null
|
||||||
onSetContextSourceLabel(contextSourceLabel)
|
onSetContextSourceLabel(contextSourceLabel)
|
||||||
return () => onSetContextSourceLabel(null)
|
return () => onSetContextSourceLabel(null)
|
||||||
}, [
|
}, [
|
||||||
activeTemporalSeriesGroup?.label,
|
activeTemporalSeriesGroup?.label,
|
||||||
activeOnDemandThematicProduct?.display_name,
|
activeOnDemandRasterProduct?.displayName,
|
||||||
analysisMode,
|
analysisMode,
|
||||||
onSetContextSourceLabel,
|
onSetContextSourceLabel,
|
||||||
regionalBathymetryThemeActive,
|
regionalBathymetryThemeActive,
|
||||||
@@ -1021,14 +1103,33 @@ export function MapWorkspace({
|
|||||||
}, [activeTemporalSeriesGroups])
|
}, [activeTemporalSeriesGroups])
|
||||||
|
|
||||||
useEffect(() => {
|
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
|
return
|
||||||
}
|
}
|
||||||
const preferred = floodHazardDatasets.find(
|
const preferred = floodHazardDatasets.find(
|
||||||
(dataset) => dataset.source_metadata?.['product_key'] === 'pluviaal_current_t100',
|
(dataset) => dataset.source_metadata?.['product_key'] === 'pluviaal_current_t100',
|
||||||
) ?? floodHazardDatasets[0]
|
) ?? floodHazardDatasets[0]
|
||||||
setSelectedFloodHazardDatasetId(preferred?.id ?? '')
|
setSelectedFloodHazardDatasetId(preferred?.id ?? '')
|
||||||
}, [floodHazardDatasets, selectedFloodHazardDatasetId])
|
if (preferred && datasetProductKey(preferred)) {
|
||||||
|
setSelectedFloodHazardProductKey(datasetProductKey(preferred))
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
flandersScopeSelected,
|
||||||
|
floodHazardDatasets,
|
||||||
|
officialRasterProducts.floodHazard.length,
|
||||||
|
selectedFloodHazardDatasetId,
|
||||||
|
selectedFloodHazardProductKey,
|
||||||
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const first = activeTemporalSeries[0]
|
const first = activeTemporalSeries[0]
|
||||||
@@ -1049,6 +1150,17 @@ export function MapWorkspace({
|
|||||||
if (!selectedMapDataset) {
|
if (!selectedMapDataset) {
|
||||||
return
|
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)
|
const matchingThemeId = themeIdForDataset(selectedMapDataset)
|
||||||
if (matchingThemeId) {
|
if (matchingThemeId) {
|
||||||
setActiveThemeId(matchingThemeId)
|
setActiveThemeId(matchingThemeId)
|
||||||
@@ -1238,7 +1350,7 @@ export function MapWorkspace({
|
|||||||
? temporalGroup?.items[temporalGroup.items.length - 1] ?? null
|
? temporalGroup?.items[temporalGroup.items.length - 1] ?? null
|
||||||
: themeDatasetMap[theme.id]
|
: themeDatasetMap[theme.id]
|
||||||
const onDemandProduct = analysisMode === 'current'
|
const onDemandProduct = analysisMode === 'current'
|
||||||
? onDemandThematicProductMap.get(theme.id)
|
? onDemandRasterProductMap.get(theme.id)
|
||||||
: null
|
: null
|
||||||
if (!dataset && !onDemandProduct) {
|
if (!dataset && !onDemandProduct) {
|
||||||
return
|
return
|
||||||
@@ -1268,13 +1380,17 @@ export function MapWorkspace({
|
|||||||
const loadAllThemeResults = async (bbox: VectorSelectionBBox, areaId?: string) => {
|
const loadAllThemeResults = async (bbox: VectorSelectionBBox, areaId?: string) => {
|
||||||
const availableThemes: Array<MapThemeQuery<DataThemeId>> = []
|
const availableThemes: Array<MapThemeQuery<DataThemeId>> = []
|
||||||
for (const theme of DATA_THEMES) {
|
for (const theme of DATA_THEMES) {
|
||||||
const thematicProduct = analysisMode === 'current'
|
const onDemandProduct = analysisMode === 'current'
|
||||||
? onDemandThematicProductMap.get(theme.id)
|
? onDemandRasterProductMap.get(theme.id)
|
||||||
: null
|
: null
|
||||||
if (thematicProduct) {
|
if (onDemandProduct) {
|
||||||
availableThemes.push({
|
availableThemes.push({
|
||||||
themeId: theme.id,
|
themeId: theme.id,
|
||||||
thematicProductKey: thematicProduct.key,
|
acquisition: {
|
||||||
|
kind: onDemandProduct.kind,
|
||||||
|
productKey: onDemandProduct.productKey,
|
||||||
|
displayName: onDemandProduct.displayName,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1294,7 +1410,7 @@ export function MapWorkspace({
|
|||||||
const analyzeSelection = async (bbox: VectorSelectionBBox, areaId?: string) => {
|
const analyzeSelection = async (bbox: VectorSelectionBBox, areaId?: string) => {
|
||||||
setSelectionBbox(bbox)
|
setSelectionBbox(bbox)
|
||||||
const tasks: Array<Promise<unknown>> = [loadAllThemeResults(bbox, areaId)]
|
const tasks: Array<Promise<unknown>> = [loadAllThemeResults(bbox, areaId)]
|
||||||
if (!regionalPartitionedThemeActive && !onDemandThematicThemeActive) {
|
if (!regionalPartitionedThemeActive && !onDemandRasterThemeActive) {
|
||||||
tasks.push(onRunMapSelectionExtract(bbox, areaId))
|
tasks.push(onRunMapSelectionExtract(bbox, areaId))
|
||||||
}
|
}
|
||||||
if (analysisMode === 'evolution' && earlierDatasetId && laterDatasetId) {
|
if (analysisMode === 'evolution' && earlierDatasetId && laterDatasetId) {
|
||||||
@@ -1455,7 +1571,7 @@ export function MapWorkspace({
|
|||||||
<div className="geo-theme-list">
|
<div className="geo-theme-list">
|
||||||
{DATA_THEMES.map((theme) => {
|
{DATA_THEMES.map((theme) => {
|
||||||
const dataset = themeDatasetMap[theme.id]
|
const dataset = themeDatasetMap[theme.id]
|
||||||
const onDemandProduct = onDemandThematicProductMap.get(theme.id)
|
const onDemandProduct = onDemandRasterProductMap.get(theme.id)
|
||||||
const partitions = themePartitionMap[theme.id]
|
const partitions = themePartitionMap[theme.id]
|
||||||
const temporalGroups = themeTemporalSeriesMap[theme.id]
|
const temporalGroups = themeTemporalSeriesMap[theme.id]
|
||||||
const temporalGroup = temporalGroups[0]
|
const temporalGroup = temporalGroups[0]
|
||||||
@@ -1487,7 +1603,7 @@ export function MapWorkspace({
|
|||||||
: dataset
|
: dataset
|
||||||
? datasetAvailabilityLabel(dataset, partitions)
|
? datasetAvailabilityLabel(dataset, partitions)
|
||||||
: onDemandProduct
|
: onDemandProduct
|
||||||
? `${onDemandProduct.native_resolution_m} m · ${onDemandProduct.observation_year} · laad bij selectie`
|
? `${onDemandProduct.nativeResolutionM} m · ${onDemandProduct.referenceLabel} · laad bij selectie`
|
||||||
: 'Bron nog niet ingeladen'}
|
: 'Bron nog niet ingeladen'}
|
||||||
</small>
|
</small>
|
||||||
</span>
|
</span>
|
||||||
@@ -1512,7 +1628,7 @@ export function MapWorkspace({
|
|||||||
? 'VHA-dwarsprofielen Vlaanderen'
|
? 'VHA-dwarsprofielen Vlaanderen'
|
||||||
: activeThemeDataset
|
: activeThemeDataset
|
||||||
? getDatasetDisplayName(activeThemeDataset)
|
? getDatasetDisplayName(activeThemeDataset)
|
||||||
: activeOnDemandThematicProduct?.display_name ?? 'Geen databron beschikbaar'}
|
: activeOnDemandRasterProduct?.displayName ?? 'Geen databron beschikbaar'}
|
||||||
</strong>
|
</strong>
|
||||||
<small>
|
<small>
|
||||||
{analysisOverlayActive
|
{analysisOverlayActive
|
||||||
@@ -1525,19 +1641,71 @@ export function MapWorkspace({
|
|||||||
? `${activeThemePartitions.length} gecontroleerde gemeentepartities · selectie wordt ruimtelijk samengevoegd`
|
? `${activeThemePartitions.length} gecontroleerde gemeentepartities · selectie wordt ruimtelijk samengevoegd`
|
||||||
: activeThemeDataset
|
: activeThemeDataset
|
||||||
? `${getDatasetSourceDisplayName(activeThemeDataset)} · ${formatDatasetObservation(activeThemeDataset)}`
|
? `${getDatasetSourceDisplayName(activeThemeDataset)} · ${formatDatasetObservation(activeThemeDataset)}`
|
||||||
: activeOnDemandThematicProduct
|
: activeOnDemandRasterProduct
|
||||||
? `${activeOnDemandThematicProduct.attribution} · wordt alleen voor de gekozen selectie ingeladen`
|
? `${activeOnDemandRasterProduct.attribution} · wordt alleen voor de gekozen selectie ingeladen`
|
||||||
: activeTheme.description}
|
: activeTheme.description}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
{thematicRasterProductsLoading && flandersScopeSelected ? (
|
{officialRasterProductsLoading && flandersScopeSelected ? (
|
||||||
<p className="geo-data-notice">Beschikbare Vlaamse beleidsbronnen worden gecontroleerd…</p>
|
<p className="geo-data-notice">Beschikbare Vlaamse rasterbronnen worden gecontroleerd…</p>
|
||||||
) : null}
|
) : null}
|
||||||
{thematicRasterProductsError && flandersScopeSelected ? (
|
{officialRasterProductsError && flandersScopeSelected ? (
|
||||||
<p className="error">{thematicRasterProductsError}</p>
|
<p className="error">{officialRasterProductsError}</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{analysisMode === 'current' && activeTheme.id === 'flood_hazard' && floodHazardDatasets.length > 0 ? (
|
{analysisMode === 'current' && activeTheme.id === 'elevation' && officialRasterProducts.dhmv.length > 0 ? (
|
||||||
|
<label className="geo-scope-select">
|
||||||
|
Hoogtemodel
|
||||||
|
<select
|
||||||
|
aria-label="Hoogtemodel"
|
||||||
|
value={selectedDhmvProductKey}
|
||||||
|
onChange={(event) => {
|
||||||
|
const productKey = event.target.value as 'dtm_1m' | 'dsm_1m'
|
||||||
|
setSelectedDhmvProductKey(productKey)
|
||||||
|
clearThemeInsights()
|
||||||
|
const dataset = availableMapDatasets.find(
|
||||||
|
(item) =>
|
||||||
|
item.source_name === 'digitaal_vlaanderen_dhmv'
|
||||||
|
&& datasetProductKey(item) === productKey
|
||||||
|
&& datasetCoversSelectedArea(item, selectedMapAreaId, regionalScopeSelected),
|
||||||
|
)
|
||||||
|
if (dataset) {
|
||||||
|
onOpenDatasetInMap(dataset)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{officialRasterProducts.dhmv.map((product) => (
|
||||||
|
<option key={product.key} value={product.key}>{product.display_name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<small>DTM meet het maaiveld; DSM bevat ook gebouwen en vegetatie.</small>
|
||||||
|
</label>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{analysisMode === 'current' && activeTheme.id === 'flood_hazard' && officialRasterProducts.floodHazard.length > 0 ? (
|
||||||
|
<label className="geo-scope-select">
|
||||||
|
Overstromingsscenario
|
||||||
|
<select
|
||||||
|
aria-label="Overstromingsscenario"
|
||||||
|
value={selectedFloodHazardProductKey}
|
||||||
|
onChange={(event) => {
|
||||||
|
const productKey = event.target.value
|
||||||
|
const dataset = floodHazardDatasets.find((item) => datasetProductKey(item) === productKey)
|
||||||
|
setSelectedFloodHazardProductKey(productKey)
|
||||||
|
setSelectedFloodHazardDatasetId(dataset?.id ?? '')
|
||||||
|
clearThemeInsights()
|
||||||
|
if (dataset) {
|
||||||
|
onOpenDatasetInMap(dataset)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{officialRasterProducts.floodHazard.map((product) => (
|
||||||
|
<option key={product.key} value={product.key}>{product.display_name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<small>Gemodelleerde maximale waterdiepte voor de gekozen kans en klimaatprojectie; geen actuele waterstand.</small>
|
||||||
|
</label>
|
||||||
|
) : analysisMode === 'current' && activeTheme.id === 'flood_hazard' && floodHazardDatasets.length > 0 ? (
|
||||||
<label className="geo-scope-select">
|
<label className="geo-scope-select">
|
||||||
Overstromingsscenario
|
Overstromingsscenario
|
||||||
<select
|
<select
|
||||||
@@ -1633,7 +1801,7 @@ export function MapWorkspace({
|
|||||||
? 'Sleep nu een rechthoek op de kaart.'
|
? 'Sleep nu een rechthoek op de kaart.'
|
||||||
: regionalRasterThemeActive
|
: regionalRasterThemeActive
|
||||||
? 'Teken een rechthoek; de juiste gemeentelijke rasters worden automatisch gecombineerd.'
|
? 'Teken een rechthoek; de juiste gemeentelijke rasters worden automatisch gecombineerd.'
|
||||||
: onDemandThematicThemeActive
|
: onDemandRasterThemeActive
|
||||||
? 'Teken een rechthoek; officiële Vlaamse rasters worden begrensd opgehaald, bewaard en hergebruikt.'
|
? 'Teken een rechthoek; officiële Vlaamse rasters worden begrensd opgehaald, bewaard en hergebruikt.'
|
||||||
: regionalBathymetryThemeActive
|
: regionalBathymetryThemeActive
|
||||||
? 'Teken een rechthoek of analyseer Vlaanderen; alleen overlappende VHA-partities worden samengevoegd.'
|
? 'Teken een rechthoek of analyseer Vlaanderen; alleen overlappende VHA-partities worden samengevoegd.'
|
||||||
@@ -1652,16 +1820,16 @@ export function MapWorkspace({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="secondary-action"
|
className="secondary-action"
|
||||||
disabled={!activeThemeAvailable || regionalRasterThemeActive || regionalOnDemandThematicThemeActive || (analysisMode === 'evolution' && activeTemporalSeries.length < 2) || !selectedAreaBbox || mapSelectionLoading || themeResultsLoading}
|
disabled={!activeThemeAvailable || regionalRasterThemeActive || regionalOnDemandRasterThemeActive || (analysisMode === 'evolution' && activeTemporalSeries.length < 2) || !selectedAreaBbox || mapSelectionLoading || themeResultsLoading}
|
||||||
type="button"
|
type="button"
|
||||||
title={
|
title={
|
||||||
regionalRasterThemeActive || regionalOnDemandThematicThemeActive
|
regionalRasterThemeActive || regionalOnDemandRasterThemeActive
|
||||||
? 'Teken een begrensde rechthoek voor een regionale rasteranalyse.'
|
? 'Teken een begrensde rechthoek voor een regionale rasteranalyse.'
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
onClick={() => selectedAreaBbox && void analyzeSelection(selectedAreaBbox, selectedMapArea?.id)}
|
onClick={() => selectedAreaBbox && void analyzeSelection(selectedAreaBbox, selectedMapArea?.id)}
|
||||||
>
|
>
|
||||||
{regionalRasterThemeActive || regionalOnDemandThematicThemeActive ? 'Selecteer een deelgebied' : 'Volledig werkgebied'}
|
{regionalRasterThemeActive || regionalOnDemandRasterThemeActive ? 'Selecteer een deelgebied' : 'Volledig werkgebied'}
|
||||||
</button>
|
</button>
|
||||||
<button className="secondary-action" disabled={!mapSelectionBbox} type="button" onClick={clearAreaSelection}>
|
<button className="secondary-action" disabled={!mapSelectionBbox} type="button" onClick={clearAreaSelection}>
|
||||||
Wis selectie
|
Wis selectie
|
||||||
@@ -2030,7 +2198,7 @@ export function MapWorkspace({
|
|||||||
? `VHA-dwarsprofielen Vlaanderen · ${activeThemePartitions.length} gemeentepartities`
|
? `VHA-dwarsprofielen Vlaanderen · ${activeThemePartitions.length} gemeentepartities`
|
||||||
: activeThemeDataset
|
: activeThemeDataset
|
||||||
? getDatasetDisplayName(activeThemeDataset)
|
? getDatasetDisplayName(activeThemeDataset)
|
||||||
: activeOnDemandThematicProduct?.display_name
|
: activeOnDemandRasterProduct?.displayName
|
||||||
?? 'niet beschikbaar'}
|
?? 'niet beschikbaar'}
|
||||||
</span>
|
</span>
|
||||||
{usesDefaultOsmBasemap ? <span><strong>Ondergrond:</strong> OpenStreetMap</span> : null}
|
{usesDefaultOsmBasemap ? <span><strong>Ondergrond:</strong> OpenStreetMap</span> : null}
|
||||||
|
|||||||
@@ -6,18 +6,26 @@ import { terrainSelectionToMapSelection } from '../lib/terrainSelection'
|
|||||||
import { floodHazardSelectionToMapSelection } from '../lib/floodHazardSelection'
|
import { floodHazardSelectionToMapSelection } from '../lib/floodHazardSelection'
|
||||||
import { thematicRasterSelectionToMapSelection } from '../lib/thematicRaster'
|
import { thematicRasterSelectionToMapSelection } from '../lib/thematicRaster'
|
||||||
|
|
||||||
|
export type MapThemeAcquisitionKind = 'thematic_raster' | 'dhmv' | 'flood_hazard'
|
||||||
|
|
||||||
|
export interface MapThemeAcquisition {
|
||||||
|
kind: MapThemeAcquisitionKind
|
||||||
|
productKey: string
|
||||||
|
displayName: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface MapThemeQuery<TThemeId extends string> {
|
export interface MapThemeQuery<TThemeId extends string> {
|
||||||
themeId: TThemeId
|
themeId: TThemeId
|
||||||
dataset?: DatasetCreateResponse
|
dataset?: DatasetCreateResponse
|
||||||
partitioned?: boolean
|
partitioned?: boolean
|
||||||
thematicProductKey?: string
|
acquisition?: MapThemeAcquisition
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MapThemeInsight<TThemeId extends string> {
|
export interface MapThemeInsight<TThemeId extends string> {
|
||||||
themeId: TThemeId
|
themeId: TThemeId
|
||||||
dataset: DatasetCreateResponse
|
dataset: DatasetCreateResponse
|
||||||
partitioned?: boolean
|
partitioned?: boolean
|
||||||
thematicProductKey?: string
|
acquisition?: MapThemeAcquisition
|
||||||
result: VectorSelectionResponse
|
result: VectorSelectionResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,22 +69,36 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
|
|||||||
setThemeInsightsError(null)
|
setThemeInsightsError(null)
|
||||||
try {
|
try {
|
||||||
const settled = await Promise.allSettled(
|
const settled = await Promise.allSettled(
|
||||||
queries.map(async ({ themeId, dataset: existingDataset, partitioned, thematicProductKey }) => {
|
queries.map(async ({ themeId, dataset: existingDataset, partitioned, acquisition }) => {
|
||||||
let dataset = existingDataset
|
let dataset = existingDataset
|
||||||
if (thematicProductKey) {
|
if (acquisition) {
|
||||||
const acquisition = await datasetsApi.acquireThematicRaster(selectedProjectId, {
|
const acquisitionJob = acquisition.kind === 'thematic_raster'
|
||||||
bbox,
|
? await datasetsApi.acquireThematicRaster(selectedProjectId, {
|
||||||
area_id: areaId,
|
bbox,
|
||||||
product_key: thematicProductKey,
|
area_id: areaId,
|
||||||
force_refresh: false,
|
product_key: acquisition.productKey,
|
||||||
})
|
force_refresh: false,
|
||||||
if (acquisition.status !== 'success' || !acquisition.output_dataset_id) {
|
})
|
||||||
|
: acquisition.kind === 'dhmv'
|
||||||
|
? await datasetsApi.acquireDhmv(selectedProjectId, {
|
||||||
|
bbox,
|
||||||
|
area_id: areaId,
|
||||||
|
product_key: acquisition.productKey as 'dtm_1m' | 'dsm_1m',
|
||||||
|
force_refresh: false,
|
||||||
|
})
|
||||||
|
: await datasetsApi.acquireFloodHazard(selectedProjectId, {
|
||||||
|
bbox,
|
||||||
|
area_id: areaId,
|
||||||
|
product_key: acquisition.productKey,
|
||||||
|
force_refresh: false,
|
||||||
|
})
|
||||||
|
if (acquisitionJob.status !== 'success' || !acquisitionJob.output_dataset_id) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
acquisition.error_message
|
acquisitionJob.error_message
|
||||||
|| `De officiële rasterbron ${thematicProductKey} kon niet worden ingeladen.`,
|
|| `De officiële rasterbron ${acquisition.displayName} kon niet worden ingeladen.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
dataset = await datasetsApi.get(selectedProjectId, acquisition.output_dataset_id)
|
dataset = await datasetsApi.get(selectedProjectId, acquisitionJob.output_dataset_id)
|
||||||
}
|
}
|
||||||
if (!dataset) {
|
if (!dataset) {
|
||||||
throw new Error(`Geen persistente databron beschikbaar voor thema ${themeId}.`)
|
throw new Error(`Geen persistente databron beschikbaar voor thema ${themeId}.`)
|
||||||
@@ -85,7 +107,7 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
|
|||||||
themeId,
|
themeId,
|
||||||
dataset,
|
dataset,
|
||||||
partitioned,
|
partitioned,
|
||||||
thematicProductKey,
|
acquisition,
|
||||||
result: dataset.dataset_type === 'raster' && dataset.source_name === 'digitaal_vlaanderen_dhmv'
|
result: dataset.dataset_type === 'raster' && dataset.source_name === 'digitaal_vlaanderen_dhmv'
|
||||||
? terrainSelectionToMapSelection(
|
? terrainSelectionToMapSelection(
|
||||||
partitioned
|
partitioned
|
||||||
@@ -137,7 +159,7 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
|
|||||||
? [{
|
? [{
|
||||||
dataset:
|
dataset:
|
||||||
queries[index]?.dataset?.name
|
queries[index]?.dataset?.name
|
||||||
?? queries[index]?.thematicProductKey
|
?? queries[index]?.acquisition?.displayName
|
||||||
?? queries[index]?.themeId
|
?? queries[index]?.themeId
|
||||||
?? 'Onbekende bron',
|
?? 'Onbekende bron',
|
||||||
reason: formatError(item.reason, 'Bron kon niet worden bevraagd.'),
|
reason: formatError(item.reason, 'Bron kon niet worden bevraagd.'),
|
||||||
@@ -150,7 +172,7 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
|
|||||||
}
|
}
|
||||||
setThemeInsights(successful)
|
setThemeInsights(successful)
|
||||||
let refreshFailure: string | null = null
|
let refreshFailure: string | null = null
|
||||||
if (successful.some((item) => item.thematicProductKey) && onDatasetsChanged) {
|
if (successful.some((item) => item.acquisition) && onDatasetsChanged) {
|
||||||
try {
|
try {
|
||||||
await onDatasetsChanged()
|
await onDatasetsChanged()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { datasetsApi } from '../services/api'
|
||||||
|
import { formatError } from '../lib/formatError'
|
||||||
|
import type {
|
||||||
|
DhmvProductRead,
|
||||||
|
FloodHazardProductRead,
|
||||||
|
ThematicRasterProductRead,
|
||||||
|
} from '../types'
|
||||||
|
|
||||||
|
interface OfficialRasterProducts {
|
||||||
|
thematic: ThematicRasterProductRead[]
|
||||||
|
dhmv: DhmvProductRead[]
|
||||||
|
floodHazard: FloodHazardProductRead[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMPTY_PRODUCTS: OfficialRasterProducts = {
|
||||||
|
thematic: [],
|
||||||
|
dhmv: [],
|
||||||
|
floodHazard: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useOfficialRasterProducts(selectedProjectId: string | null) {
|
||||||
|
const [products, setProducts] = useState<OfficialRasterProducts>(EMPTY_PRODUCTS)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false
|
||||||
|
if (!selectedProjectId) {
|
||||||
|
setProducts(EMPTY_PRODUCTS)
|
||||||
|
setLoading(false)
|
||||||
|
setError(null)
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true)
|
||||||
|
setError(null)
|
||||||
|
void Promise.all([
|
||||||
|
datasetsApi.listThematicRasterProducts(selectedProjectId),
|
||||||
|
datasetsApi.listDhmvProducts(selectedProjectId),
|
||||||
|
datasetsApi.listFloodHazardProducts(selectedProjectId),
|
||||||
|
])
|
||||||
|
.then(([thematic, dhmv, floodHazard]) => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setProducts({
|
||||||
|
thematic: thematic.items,
|
||||||
|
dhmv: dhmv.items,
|
||||||
|
floodHazard: floodHazard.items,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((requestError) => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setProducts(EMPTY_PRODUCTS)
|
||||||
|
setError(formatError(requestError, 'De officiële Vlaamse rastercatalogi konden niet worden geladen.'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [selectedProjectId])
|
||||||
|
|
||||||
|
return { products, loading, error }
|
||||||
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { datasetsApi } from '../services/api'
|
|
||||||
import { formatError } from '../lib/formatError'
|
|
||||||
import type { ThematicRasterProductRead } from '../types'
|
|
||||||
|
|
||||||
export function useThematicRasterProducts(selectedProjectId: string | null) {
|
|
||||||
const [products, setProducts] = useState<ThematicRasterProductRead[]>([])
|
|
||||||
const [loading, setLoading] = useState(false)
|
|
||||||
const [error, setError] = useState<string | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let cancelled = false
|
|
||||||
if (!selectedProjectId) {
|
|
||||||
setProducts([])
|
|
||||||
setLoading(false)
|
|
||||||
setError(null)
|
|
||||||
return () => {
|
|
||||||
cancelled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(true)
|
|
||||||
setError(null)
|
|
||||||
void datasetsApi.listThematicRasterProducts(selectedProjectId)
|
|
||||||
.then((response) => {
|
|
||||||
if (!cancelled) {
|
|
||||||
setProducts(response.items)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((requestError) => {
|
|
||||||
if (!cancelled) {
|
|
||||||
setProducts([])
|
|
||||||
setError(formatError(requestError, 'De Vlaamse beleidsrasters konden niet worden geladen.'))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
if (!cancelled) {
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
cancelled = true
|
|
||||||
}
|
|
||||||
}, [selectedProjectId])
|
|
||||||
|
|
||||||
return { products, loading, error }
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user