Add governed VMM flood hazard scenarios
This commit is contained in:
@@ -500,6 +500,16 @@ TAW and the 2013-2015 source period visible. Raster cells are not presented as
|
||||
objects. GeoJSON export is disabled for this raster-only result, and the UI
|
||||
explicitly states that water depth and volume cannot be derived from DHMV.
|
||||
|
||||
When governed VMM scenarios are present, the Map explorer adds a separate
|
||||
`Overstroming` theme. It is deliberately not merged into `Water`: the latter
|
||||
describes persisted water surfaces and watercourse lengths, while the former
|
||||
is modeled flood hazard. A scenario selector keeps pluvial/fluvial,
|
||||
T10/T100/T1000 and current/2050 conditions visible before analysis. The
|
||||
persisted depth raster is shown through the existing MapLibre image-overlay
|
||||
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.
|
||||
|
||||
## Useful repository scripts
|
||||
|
||||
- `bash scripts/frontend_install.sh`
|
||||
|
||||
@@ -187,7 +187,10 @@ function App(): JSX.Element {
|
||||
const terrain = datasets.filter(
|
||||
(dataset) => dataset.dataset_type === 'raster' && dataset.source_name === 'digitaal_vlaanderen_dhmv' && dataset.status === 'ready',
|
||||
)
|
||||
return [...vectors, ...terrain]
|
||||
const floodHazards = datasets.filter(
|
||||
(dataset) => dataset.dataset_type === 'raster' && dataset.source_name === 'vmm_flood_hazard' && dataset.status === 'ready',
|
||||
)
|
||||
return [...vectors, ...terrain, ...floodHazards]
|
||||
},
|
||||
[datasets],
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ const THEME_LABELS: Record<string, string> = {
|
||||
nature_value: 'Natuurwaarde',
|
||||
agriculture: 'Landbouw',
|
||||
water: 'Water',
|
||||
flood_hazard: 'Overstromingsgevaar',
|
||||
elevation: 'Hoogte en reliëf',
|
||||
roads: 'Wegen en transport',
|
||||
parcels: 'Percelen',
|
||||
@@ -66,6 +67,14 @@ const AVAILABLE_SOURCES = [
|
||||
value: 'Hydrologische toestand; geen gebiedsdekkend watervolume zonder bodemprofiel',
|
||||
url: 'https://waterinfo.vlaanderen.be/',
|
||||
},
|
||||
{
|
||||
key: 'flood_hazard',
|
||||
name: 'Overstromingsgevaarkaarten waterdiepte',
|
||||
owner: 'Vlaamse Milieumaatschappij',
|
||||
coverage: 'Fluviaal/pluviaal, T10/T100/T1000, huidig klimaat en projectie 2050',
|
||||
value: 'Gemodelleerd overstroomd oppervlak en maximale waterdiepte per vast scenario; geen bathymetrie',
|
||||
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/publieke-inspire-coverage-service-van-ogrk',
|
||||
},
|
||||
]
|
||||
|
||||
function datasetTheme(dataset: DatasetCreateResponse): string | null {
|
||||
@@ -113,6 +122,7 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
|
||||
(dataset) => dataset.source_name === 'digitaal_vlaanderen_buildings_addresses_register',
|
||||
)
|
||||
const dhmvDatasets = ready.filter((dataset) => dataset.source_name === 'digitaal_vlaanderen_dhmv')
|
||||
const floodHazardDatasets = ready.filter((dataset) => dataset.source_name === 'vmm_flood_hazard')
|
||||
const latestBuildingsRegister = [...buildingsRegisterDatasets].sort(
|
||||
(left, right) => new Date(right.observed_at ?? 0).getTime() - new Date(left.observed_at ?? 0).getTime(),
|
||||
)[0]
|
||||
@@ -125,6 +135,7 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
|
||||
if (source.key === 'agriculture') return agricultureDatasets.length === 0
|
||||
if (source.key === 'buildings_register') return buildingsRegisterDatasets.length === 0
|
||||
if (source.key === 'elevation') return dhmvDatasets.length === 0
|
||||
if (source.key === 'flood_hazard') return floodHazardDatasets.length === 0
|
||||
return true
|
||||
})
|
||||
const themes = Object.keys(THEME_LABELS).map((theme) => {
|
||||
@@ -184,7 +195,7 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
|
||||
))}
|
||||
</div>
|
||||
|
||||
{waterinfoDatasets.length > 0 || historicalOrthophotos.length > 0 || bwkDatasets.length > 0 || agricultureDatasets.length > 0 || buildingsRegisterDatasets.length > 0 || dhmvDatasets.length > 0 ? (
|
||||
{waterinfoDatasets.length > 0 || historicalOrthophotos.length > 0 || bwkDatasets.length > 0 || agricultureDatasets.length > 0 || buildingsRegisterDatasets.length > 0 || dhmvDatasets.length > 0 || floodHazardDatasets.length > 0 ? (
|
||||
<div className="source-catalog-loaded" aria-label="Aanvullende ingeladen bronnen">
|
||||
{waterinfoDatasets.length > 0 ? (
|
||||
<article>
|
||||
@@ -232,6 +243,13 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
|
||||
<p>Hoogte in TAW, reliëf en helling uit opnameperiode 2013-2015. Geen waterdiepte of watervolume.</p>
|
||||
</article>
|
||||
) : null}
|
||||
{floodHazardDatasets.length > 0 ? (
|
||||
<article>
|
||||
<strong>VMM-overstromingsgevaarkaarten</strong>
|
||||
<span>{floodHazardDatasets.length} scenario's · fluviaal en pluviaal · huidig en 2050</span>
|
||||
<p>Gemodelleerde maximumdiepte per kansscenario. Geen actuele waterstand, bathymetrie of permanent watervolume.</p>
|
||||
</article>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useTemporalComparison } from '../../hooks/useTemporalComparison'
|
||||
import { getDatasetDisplayName, getDatasetSourceDisplayName } from '../../lib/datasetDisplay'
|
||||
import { TemporalTrendChart } from './TemporalTrendChart'
|
||||
import { terrainImageUrl } from '../../lib/terrainImage'
|
||||
import { floodHazardImageUrl } from '../../lib/floodHazardImage'
|
||||
|
||||
const DEFAULT_SELECTED_FEATURE_FILENAME = 'selected-feature.geojson'
|
||||
const DEFAULT_AREA_SELECTION_FILENAME = 'area-selection.geojson'
|
||||
@@ -14,7 +15,7 @@ const EMPTY_TEMPORAL_SERIES: DatasetCreateResponse[] = []
|
||||
const MOL_PROJECT_NAME = 'Mol Municipality Workbench'
|
||||
const KEMPEN_PROJECT_NAME = 'Kempen Regional Workbench'
|
||||
|
||||
type DataThemeId = 'buildings' | 'population' | 'forest' | 'nature_value' | 'agriculture' | 'water' | 'elevation' | 'roads' | 'parcels'
|
||||
type DataThemeId = 'buildings' | 'population' | 'forest' | 'nature_value' | 'agriculture' | 'water' | 'flood_hazard' | 'elevation' | 'roads' | 'parcels'
|
||||
|
||||
interface DataTheme {
|
||||
id: DataThemeId
|
||||
@@ -73,6 +74,13 @@ const DATA_THEMES: DataTheme[] = [
|
||||
description: 'Waterlopen, grachten, kanalen en wateroppervlakken.',
|
||||
tokens: ['waterways', 'waterway', 'water', 'hydro', 'river', 'stream', 'canal', 'waterloop'],
|
||||
},
|
||||
{
|
||||
id: 'flood_hazard',
|
||||
label: 'Overstroming',
|
||||
shortLabel: 'Overstroomd oppervlak',
|
||||
description: 'Gemodelleerde maximale waterdiepte per VMM-kans- en klimaatscenario.',
|
||||
tokens: ['flood_hazard', 'flood depth', 'flood_depth', 'overstroming', 'waterdiepte'],
|
||||
},
|
||||
{
|
||||
id: 'elevation',
|
||||
label: 'Hoogte & reliëf',
|
||||
@@ -103,6 +111,7 @@ const DATA_THEME_MAP_STYLES: Record<DataThemeId, { fill: string; line: string }>
|
||||
nature_value: { fill: '#9a4f64', line: '#74364a' },
|
||||
agriculture: { fill: '#7b8f32', line: '#53671d' },
|
||||
water: { fill: '#2676a8', line: '#155b85' },
|
||||
flood_hazard: { fill: '#1597c2', line: '#075985' },
|
||||
elevation: { fill: '#a57a4b', line: '#315f59' },
|
||||
roads: { fill: '#6b7280', line: '#4b5563' },
|
||||
parcels: { fill: '#a7792f', line: '#7d571f' },
|
||||
@@ -113,6 +122,10 @@ function datasetAvailabilityLabel(dataset: DatasetCreateResponse): string {
|
||||
const resolution = Number(dataset.source_metadata?.['analysis_resolution_m'])
|
||||
return `${Number.isFinite(resolution) ? `${resolution.toLocaleString('nl-BE')} m` : 'Raster'} hoogtegrid beschikbaar`
|
||||
}
|
||||
if (dataset.dataset_type === 'raster' && dataset.source_name === 'vmm_flood_hazard') {
|
||||
const resolution = Number(dataset.source_metadata?.['analysis_resolution_m'])
|
||||
return `${Number.isFinite(resolution) ? `${resolution.toLocaleString('nl-BE')} m` : 'Raster'} overstromingsscenario`
|
||||
}
|
||||
return `${(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0).toLocaleString('nl-BE')} objecten beschikbaar`
|
||||
}
|
||||
|
||||
@@ -126,6 +139,7 @@ function datasetSearchText(dataset: DatasetCreateResponse): string {
|
||||
dataset.metadata_json?.['layer_name'],
|
||||
dataset.source_metadata?.['layer_name'],
|
||||
dataset.source_metadata?.['theme'],
|
||||
dataset.source_metadata?.['product_display_name'],
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
@@ -162,7 +176,9 @@ function pickThemeDataset(
|
||||
(dataset.source_name === 'agentschap_landbouw_zeevisserij_agricultural_parcels' ? 98_000 : 0) +
|
||||
(dataset.source_name === 'digitaal_vlaanderen_buildings_addresses_register' ? 5_000_000 : 0) +
|
||||
(dataset.source_name === 'digitaal_vlaanderen_dhmv' ? 5_000_000 : 0) +
|
||||
(dataset.source_name === 'vmm_flood_hazard' ? 5_000_000 : 0) +
|
||||
(dataset.source_metadata?.['product_key'] === 'dtm_1m' ? 1_000_000 : 0) +
|
||||
(dataset.source_metadata?.['product_key'] === 'pluviaal_current_t100' ? 1_000_000 : 0) +
|
||||
(dataset.dataset_role === 'reference' ? 10_000 : 0) +
|
||||
(dataset.observed_at ? new Date(dataset.observed_at).getTime() / 100_000_000 : 0) +
|
||||
(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0)
|
||||
@@ -220,6 +236,9 @@ function formatObservationDate(value: string | null | undefined): string {
|
||||
}
|
||||
|
||||
function formatDatasetObservation(dataset: DatasetCreateResponse): string {
|
||||
if (dataset.source_name === 'vmm_flood_hazard') {
|
||||
return `scenario ${String(dataset.source_metadata?.['climate_context'] ?? '')} · ${String(dataset.source_metadata?.['probability_class'] ?? '')}`
|
||||
}
|
||||
const period = dataset.source_metadata?.['acquisition_period']
|
||||
if (typeof period === 'string' && period.trim()) {
|
||||
return `opnameperiode ${period}`
|
||||
@@ -227,6 +246,11 @@ function formatDatasetObservation(dataset: DatasetCreateResponse): string {
|
||||
return formatObservationDate(dataset.observed_at)
|
||||
}
|
||||
|
||||
function floodScenarioLabel(dataset: DatasetCreateResponse): string {
|
||||
const configured = dataset.source_metadata?.['product_display_name']
|
||||
return typeof configured === 'string' && configured.trim() ? configured : getDatasetDisplayName(dataset)
|
||||
}
|
||||
|
||||
function operationalScopeProjectLabel(project: ProjectRead): string {
|
||||
if (project.name === MOL_PROJECT_NAME) {
|
||||
return 'Mol'
|
||||
@@ -643,6 +667,7 @@ export function MapWorkspace({
|
||||
clearTemporalComparison,
|
||||
} = useTemporalComparison(selectedProjectId)
|
||||
const [analysisMode, setAnalysisMode] = useState<'current' | 'evolution'>('current')
|
||||
const [selectedFloodHazardDatasetId, setSelectedFloodHazardDatasetId] = useState('')
|
||||
const [selectedTemporalSeriesKey, setSelectedTemporalSeriesKey] = useState('')
|
||||
const [earlierDatasetId, setEarlierDatasetId] = useState('')
|
||||
const [laterDatasetId, setLaterDatasetId] = useState('')
|
||||
@@ -675,13 +700,22 @@ export function MapWorkspace({
|
||||
const selectedFeatureFilename = selectedFeatureStem === 'selected-feature' ? DEFAULT_SELECTED_FEATURE_FILENAME : `${selectedFeatureStem}.geojson`
|
||||
const selectedMapDataset = availableMapDatasets.find((dataset) => dataset.id === selectedMapDatasetId) ?? null
|
||||
const usesDefaultOsmBasemap = !import.meta.env.VITE_MAP_STYLE_URL
|
||||
const themeDatasetMap = useMemo(
|
||||
() =>
|
||||
Object.fromEntries(
|
||||
DATA_THEMES.map((theme) => [theme.id, pickThemeDataset(availableMapDatasets, theme, selectedMapAreaId)]),
|
||||
) as Record<DataThemeId, DatasetCreateResponse | null>,
|
||||
const floodHazardDatasets = useMemo(
|
||||
() => availableMapDatasets
|
||||
.filter((dataset) => dataset.source_name === 'vmm_flood_hazard' && datasetCoversSelectedArea(dataset, selectedMapAreaId))
|
||||
.sort((left, right) => floodScenarioLabel(left).localeCompare(floodScenarioLabel(right), 'nl')),
|
||||
[availableMapDatasets, selectedMapAreaId],
|
||||
)
|
||||
const themeDatasetMap = useMemo(() => {
|
||||
const result = Object.fromEntries(
|
||||
DATA_THEMES.map((theme) => [theme.id, pickThemeDataset(availableMapDatasets, theme, selectedMapAreaId)]),
|
||||
) as Record<DataThemeId, DatasetCreateResponse | null>
|
||||
const selectedFloodHazard = floodHazardDatasets.find((dataset) => dataset.id === selectedFloodHazardDatasetId)
|
||||
if (selectedFloodHazard) {
|
||||
result.flood_hazard = selectedFloodHazard
|
||||
}
|
||||
return result
|
||||
}, [availableMapDatasets, floodHazardDatasets, selectedFloodHazardDatasetId, selectedMapAreaId])
|
||||
const activeTheme = DATA_THEMES.find((theme) => theme.id === activeThemeId) ?? DATA_THEMES[0]
|
||||
const activeThemeMapStyle = DATA_THEME_MAP_STYLES[activeTheme.id]
|
||||
const analysisOverlayActive = mapContentMode === 'analysis' && analysisLayerAvailable && Boolean(mapFeatureCollection)
|
||||
@@ -706,7 +740,18 @@ export function MapWorkspace({
|
||||
opacity: 0.82,
|
||||
}
|
||||
: null
|
||||
const activeImageOverlay = terrainImageOverlay ?? orthophotoImageOverlay
|
||||
const floodHazardBounds = activeThemeDataset?.source_name === 'vmm_flood_hazard'
|
||||
? activeThemeDataset.source_metadata?.['bbox_epsg4326']
|
||||
: null
|
||||
const floodHazardImageOverlay = activeTheme.id === 'flood_hazard' && activeThemeDataset && selectedProjectId && Array.isArray(floodHazardBounds) && floodHazardBounds.length === 4
|
||||
? {
|
||||
url: floodHazardImageUrl(selectedProjectId, activeThemeDataset.id),
|
||||
bbox: floodHazardBounds.map(Number) as [number, number, number, number],
|
||||
label: floodScenarioLabel(activeThemeDataset),
|
||||
opacity: 0.82,
|
||||
}
|
||||
: null
|
||||
const activeImageOverlay = floodHazardImageOverlay ?? terrainImageOverlay ?? orthophotoImageOverlay
|
||||
const activeScopeProject = projects.find((project) => project.id === selectedProjectId) ?? null
|
||||
const activeScopeLabel = activeScopeProject ? operationalScopeProjectLabel(activeScopeProject) : 'Werkgebied'
|
||||
const municipalityAreaCount = areas.filter((area) => /^Gemeente\s/i.test(area.name)).length
|
||||
@@ -730,7 +775,8 @@ export function MapWorkspace({
|
||||
}),
|
||||
[themeInsights],
|
||||
)
|
||||
const activeSelectionResult = themeResults.find((item) => item.theme.id === activeThemeId)?.result ?? mapSelectionResult
|
||||
const activeSelectionResult = themeResults.find((item) => item.theme.id === activeThemeId)?.result
|
||||
?? (selectedMapDataset?.id === activeThemeDataset?.id ? mapSelectionResult : null)
|
||||
const selectedAreaSquareMetres = useMemo(
|
||||
() =>
|
||||
bboxesEqual(mapSelectionBbox, selectedAreaBbox) && selectedMapArea?.area_m2
|
||||
@@ -788,6 +834,16 @@ export function MapWorkspace({
|
||||
)
|
||||
}, [activeTemporalSeriesGroups])
|
||||
|
||||
useEffect(() => {
|
||||
if (floodHazardDatasets.some((dataset) => dataset.id === selectedFloodHazardDatasetId)) {
|
||||
return
|
||||
}
|
||||
const preferred = floodHazardDatasets.find(
|
||||
(dataset) => dataset.source_metadata?.['product_key'] === 'pluviaal_current_t100',
|
||||
) ?? floodHazardDatasets[0]
|
||||
setSelectedFloodHazardDatasetId(preferred?.id ?? '')
|
||||
}, [floodHazardDatasets, selectedFloodHazardDatasetId])
|
||||
|
||||
useEffect(() => {
|
||||
const first = activeTemporalSeries[0]
|
||||
const last = activeTemporalSeries[activeTemporalSeries.length - 1]
|
||||
@@ -1172,6 +1228,29 @@ export function MapWorkspace({
|
||||
</small>
|
||||
</div>
|
||||
|
||||
{analysisMode === 'current' && activeTheme.id === 'flood_hazard' && floodHazardDatasets.length > 0 ? (
|
||||
<label className="geo-scope-select">
|
||||
Overstromingsscenario
|
||||
<select
|
||||
aria-label="Overstromingsscenario"
|
||||
value={activeThemeDataset?.id ?? ''}
|
||||
onChange={(event) => {
|
||||
const dataset = floodHazardDatasets.find((item) => item.id === event.target.value)
|
||||
setSelectedFloodHazardDatasetId(event.target.value)
|
||||
clearThemeInsights()
|
||||
if (dataset) {
|
||||
onOpenDatasetInMap(dataset)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{floodHazardDatasets.map((dataset) => (
|
||||
<option key={dataset.id} value={dataset.id}>{floodScenarioLabel(dataset)}</option>
|
||||
))}
|
||||
</select>
|
||||
<small>Elke meting blijft gekoppeld aan deze kans en klimaatprojectie.</small>
|
||||
</label>
|
||||
) : null}
|
||||
|
||||
{analysisMode === 'evolution' ? (
|
||||
<div className="geo-time-controls" aria-label="Meetmomenten vergelijken">
|
||||
{activeTemporalSeriesGroups.length > 1 ? (
|
||||
@@ -1559,7 +1638,7 @@ export function MapWorkspace({
|
||||
|
||||
{analysisMode === 'current' ? (
|
||||
<div className="geo-result-actions">
|
||||
<button className="secondary-action" disabled={!activeSelectionResult || activeTheme.id === 'elevation'} type="button" onClick={downloadActiveThemeSelection}>Download GeoJSON</button>
|
||||
<button className="secondary-action" disabled={!activeSelectionResult || activeTheme.id === 'elevation' || activeTheme.id === 'flood_hazard'} type="button" onClick={downloadActiveThemeSelection}>Download GeoJSON</button>
|
||||
<button className="secondary-action" disabled={!activeSelectionResult} type="button" onClick={copyActiveThemeSelection}>Kopieer gegevens</button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { datasetsApi } from '../services/api'
|
||||
import { formatError } from '../lib/formatError'
|
||||
import type { DatasetCreateResponse, VectorSelectionBBox, VectorSelectionResponse } from '../types'
|
||||
import { terrainSelectionToMapSelection } from '../lib/terrainSelection'
|
||||
import { floodHazardSelectionToMapSelection } from '../lib/floodHazardSelection'
|
||||
|
||||
interface MapSelectionExtractOptions {
|
||||
selectedProjectId: string | null
|
||||
@@ -40,8 +41,9 @@ export function useMapSelectionExtract({
|
||||
return null
|
||||
}
|
||||
const terrainDataset = selectedDataset.dataset_type === 'raster' && selectedDataset.source_name === 'digitaal_vlaanderen_dhmv'
|
||||
if (!isVectorDatasetType(selectedDataset.dataset_type) && !terrainDataset) {
|
||||
setMapSelectionError('Gebiedsanalyse ondersteunt een vectorlaag of een beheerd DHMV-hoogtemodel.')
|
||||
const floodHazardDataset = selectedDataset.dataset_type === 'raster' && selectedDataset.source_name === 'vmm_flood_hazard'
|
||||
if (!isVectorDatasetType(selectedDataset.dataset_type) && !terrainDataset && !floodHazardDataset) {
|
||||
setMapSelectionError('Gebiedsanalyse ondersteunt een vectorlaag, DHMV-hoogtemodel of beheerd VMM-overstromingsscenario.')
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -56,6 +58,11 @@ export function useMapSelectionExtract({
|
||||
bbox: { ...bbox, crs: 'EPSG:4326' },
|
||||
area_id: areaId,
|
||||
}))
|
||||
: floodHazardDataset
|
||||
? floodHazardSelectionToMapSelection(await datasetsApi.selectFloodHazard(selectedProjectId, selectedDataset.id, {
|
||||
bbox: { ...bbox, crs: 'EPSG:4326' },
|
||||
area_id: areaId,
|
||||
}))
|
||||
: await datasetsApi.selectVectorFeatures(selectedProjectId, selectedDataset.id, {
|
||||
bbox: { ...bbox, crs: 'EPSG:4326' },
|
||||
area_id: areaId,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { formatError } from '../lib/formatError'
|
||||
import { datasetsApi } from '../services/api/datasets'
|
||||
import type { DatasetCreateResponse, VectorSelectionBBox, VectorSelectionResponse } from '../types'
|
||||
import { terrainSelectionToMapSelection } from '../lib/terrainSelection'
|
||||
import { floodHazardSelectionToMapSelection } from '../lib/floodHazardSelection'
|
||||
|
||||
export interface MapThemeQuery<TThemeId extends string> {
|
||||
themeId: TThemeId
|
||||
@@ -60,6 +61,11 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
|
||||
bbox,
|
||||
area_id: areaId,
|
||||
}))
|
||||
: dataset.dataset_type === 'raster' && dataset.source_name === 'vmm_flood_hazard'
|
||||
? floodHazardSelectionToMapSelection(await datasetsApi.selectFloodHazard(selectedProjectId, dataset.id, {
|
||||
bbox,
|
||||
area_id: areaId,
|
||||
}))
|
||||
: await datasetsApi.selectVectorFeatures(selectedProjectId, dataset.id, {
|
||||
bbox,
|
||||
area_id: areaId,
|
||||
|
||||
@@ -9,6 +9,7 @@ const DATASET_LABEL_BY_LAYER: Record<string, string> = {
|
||||
forest: 'Bos en groen',
|
||||
nature_value: 'Natuurwaarde',
|
||||
agriculture: 'Landbouwgebruikspercelen',
|
||||
flood_hazard: 'Overstromingsgevaar',
|
||||
elevation: 'Hoogte en reliëf',
|
||||
building_registry: 'Gebouwenregister',
|
||||
regional_boundary: 'Grens vervoerregio Kempen',
|
||||
@@ -27,6 +28,7 @@ const DATASET_SOURCE_LABELS: Record<string, string> = {
|
||||
statbel: 'Statbel',
|
||||
vrbg: 'Digitaal Vlaanderen',
|
||||
waterinfo: 'Waterinfo Vlaanderen',
|
||||
vmm_flood_hazard: 'Vlaamse Milieumaatschappij',
|
||||
}
|
||||
|
||||
export function getDatasetSourceDisplayName(dataset: DatasetCreateResponse): string {
|
||||
@@ -39,6 +41,10 @@ export function getDatasetDisplayName(dataset: DatasetCreateResponse): string {
|
||||
const productName = dataset.source_metadata?.['product_display_name']
|
||||
return typeof productName === 'string' && productName.trim() ? productName : 'DHMV II hoogtemodel'
|
||||
}
|
||||
if (dataset.source_name === 'vmm_flood_hazard') {
|
||||
const productName = dataset.source_metadata?.['product_display_name']
|
||||
return typeof productName === 'string' && productName.trim() ? productName : 'VMM-overstromingsscenario'
|
||||
}
|
||||
const layer = (dataset.reference_layer_name ?? dataset.source_metadata?.layer_name ?? dataset.source_metadata?.layer_type ?? '')
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export function floodHazardImageUrl(projectId: string, datasetId: string): string {
|
||||
return `/api/v1/projects/${projectId}/datasets/${datasetId}/raster/flood-hazard/image`
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { FloodHazardSelectionResponse, VectorSelectionResponse } from '../types'
|
||||
|
||||
export function floodHazardSelectionToMapSelection(result: FloodHazardSelectionResponse): VectorSelectionResponse {
|
||||
return {
|
||||
selection_bbox: result.selection_bbox,
|
||||
selection_area_id: result.selection_area_id,
|
||||
feature_count: result.inundated_cell_count,
|
||||
total_feature_count: result.inundated_cell_count,
|
||||
limit: 0,
|
||||
truncated: false,
|
||||
geojson: { type: 'FeatureCollection', features: [] },
|
||||
summary: {
|
||||
...result.summary,
|
||||
feature_count: result.inundated_cell_count,
|
||||
is_estimate: false,
|
||||
warning: result.limitation_message,
|
||||
metrics: result.summary.metrics.map((metric) => ({ ...metric, is_estimate: false })),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,9 @@ import type {
|
||||
OrthophotoAcquireRequest,
|
||||
OrthophotoProductRead,
|
||||
DhmvAcquireRequest,
|
||||
FloodHazardAcquireRequest,
|
||||
FloodHazardProductRead,
|
||||
FloodHazardSelectionResponse,
|
||||
DhmvProductRead,
|
||||
TerrainSelectionResponse,
|
||||
} from '../../types'
|
||||
@@ -128,6 +131,16 @@ export const datasetsApi = {
|
||||
payload: { bbox: VectorSelectionRequest['bbox']; area_id?: string },
|
||||
): Promise<TerrainSelectionResponse> =>
|
||||
apiPost<TerrainSelectionResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/terrain/select`, payload),
|
||||
acquireFloodHazard: (projectId: string, payload: FloodHazardAcquireRequest): Promise<JobRead> =>
|
||||
apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/flood-hazard/acquire`, payload),
|
||||
listFloodHazardProducts: (projectId: string): Promise<{ items: FloodHazardProductRead[]; total: number }> =>
|
||||
apiGet<{ items: FloodHazardProductRead[]; total: number }>(`/api/v1/projects/${projectId}/datasets/flood-hazard/products`),
|
||||
selectFloodHazard: (
|
||||
projectId: string,
|
||||
datasetId: string,
|
||||
payload: { bbox: VectorSelectionRequest['bbox']; area_id?: string },
|
||||
): Promise<FloodHazardSelectionResponse> =>
|
||||
apiPost<FloodHazardSelectionResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/flood-hazard/select`, payload),
|
||||
refreshMetadata: (projectId: string, datasetId: string): Promise<DatasetCreateResponse> =>
|
||||
apiPost<DatasetCreateResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/metadata/refresh`, {}),
|
||||
inspectRaster: (projectId: string, datasetId: string): Promise<RasterInspectResponse> =>
|
||||
|
||||
@@ -5756,6 +5756,7 @@ section {
|
||||
.geo-theme-symbol-nature_value { background: #9a4f64; }
|
||||
.geo-theme-symbol-agriculture { background: #7b8f32; }
|
||||
.geo-theme-symbol-water { background: #2676a8; }
|
||||
.geo-theme-symbol-flood_hazard { background: #1597c2; }
|
||||
.geo-theme-symbol-elevation { background: #a57a4b; }
|
||||
.geo-theme-symbol-roads { background: #6b7280; }
|
||||
.geo-theme-symbol-parcels { background: #a7792f; }
|
||||
@@ -5941,6 +5942,11 @@ section {
|
||||
background: rgba(38, 118, 168, 0.24);
|
||||
}
|
||||
|
||||
.geo-map-legend .geo-legend-layer-flood_hazard {
|
||||
border-color: #075985;
|
||||
background: rgba(21, 151, 194, 0.28);
|
||||
}
|
||||
|
||||
.geo-map-legend .geo-legend-layer-elevation {
|
||||
border-color: #315f59;
|
||||
background: rgba(165, 122, 75, 0.26);
|
||||
|
||||
@@ -382,6 +382,58 @@ export interface TerrainSelectionResponse {
|
||||
generated_at: string
|
||||
}
|
||||
|
||||
export interface FloodHazardAcquireRequest {
|
||||
bbox: VectorSelectionBBox
|
||||
area_id?: string | null
|
||||
product_key?: string
|
||||
resolution_m?: number | null
|
||||
force_refresh?: boolean
|
||||
}
|
||||
|
||||
export interface FloodHazardProductRead {
|
||||
key: string
|
||||
display_name: string
|
||||
mechanism: 'pluviaal' | 'fluviaal'
|
||||
climate_context: string
|
||||
probability_class: string
|
||||
return_period_years: number
|
||||
coverage_id: string
|
||||
native_resolution_m: number
|
||||
source_crs: string
|
||||
source_value_unit: 'cm'
|
||||
normalized_value_unit: 'm'
|
||||
published_on: string
|
||||
catalog_url: string
|
||||
attribution: string
|
||||
limitation_message: string
|
||||
}
|
||||
|
||||
export interface FloodHazardSelectionResponse {
|
||||
dataset_id: string
|
||||
product_key: string
|
||||
mechanism: 'pluviaal' | 'fluviaal'
|
||||
climate_context: string
|
||||
probability_class: string
|
||||
return_period_years: number
|
||||
selection_bbox: VectorSelectionBBox
|
||||
selection_area_id?: string | null
|
||||
selected_cell_count: number
|
||||
inundated_cell_count: number
|
||||
inundated_fraction: number
|
||||
resolution_m: number
|
||||
summary: {
|
||||
metric_label: string
|
||||
metric_value: number
|
||||
metric_unit: string
|
||||
aggregation_method: string
|
||||
primary_metric_key: string
|
||||
metrics: VectorSelectionMetric[]
|
||||
}
|
||||
unsupported_metrics: string[]
|
||||
limitation_message: string
|
||||
generated_at: string
|
||||
}
|
||||
|
||||
export interface MapImageOverlay {
|
||||
url: string
|
||||
bbox: [number, number, number, number]
|
||||
|
||||
Reference in New Issue
Block a user