feat: complete governed Walloon coverage sources
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-22 03:39:08 +02:00
parent 17d4442e60
commit c2101ea8f9
37 changed files with 1813 additions and 45 deletions
+68 -4
View File
@@ -9,7 +9,7 @@ import { getDatasetDisplayName, getDatasetSourceDisplayName } from '../../lib/da
import { TemporalTrendChart } from './TemporalTrendChart'
import { terrainImageUrl } from '../../lib/terrainImage'
import { floodHazardImageUrl } from '../../lib/floodHazardImage'
import { thematicRasterImageUrl } from '../../lib/thematicRaster'
import { thematicRasterImageUrl, walousRasterImageUrl } from '../../lib/thematicRaster'
import { bathymetryRasterImageUrl } from '../../lib/bathymetryRaster'
import { FLANDERS_WORKSPACE_PROJECT_NAME } from '../../config/primaryFocus'
import {
@@ -59,6 +59,7 @@ const EMPTY_TEMPORAL_SERIES: DatasetCreateResponse[] = []
type DataThemeId =
| 'administrative'
| 'buildings'
| 'land_cover'
| 'space_occupation'
| 'open_space'
| 'population'
@@ -110,7 +111,7 @@ function productSupportsSelection(product: OnDemandMapProduct, bbox: VectorSelec
if (product.kind === 'dhmv' || product.kind === 'flood_hazard') {
return dimensions.areaSquareMetres <= 280_000_000
}
if (product.kind === 'thematic_raster') {
if (product.kind === 'thematic_raster' || product.kind === 'walous') {
return dimensions.widthMetres <= 50_000
&& dimensions.heightMetres <= 50_000
&& dimensions.areaSquareMetres <= 2_800_000_000
@@ -133,6 +134,13 @@ const DATA_THEMES: DataTheme[] = [
description: 'Gebouwen en gebouwcontouren uit GRB of een andere persistente bron.',
tokens: ['buildings', 'building', 'gebouwen', 'gebouw', 'bebouwing', 'gbg'],
},
{
id: 'land_cover',
label: 'Landbedekking',
shortLabel: 'Landbedekking',
description: 'Fysieke en biologische bodembedekking uit een officieel regionaal classificatieraster.',
tokens: ['land_cover', 'land_cover_use', 'landbedekking', 'walous', 'occupation du sol'],
},
{
id: 'space_occupation',
label: 'Ruimtebeslag',
@@ -257,6 +265,7 @@ const DATA_THEMES: DataTheme[] = [
const COVERAGE_THEME_BY_MAP_THEME: Record<DataThemeId, string> = {
administrative: 'admin',
buildings: 'buildings',
land_cover: 'land_cover_use',
space_occupation: 'land_cover_use',
open_space: 'land_cover_use',
population: 'population',
@@ -303,6 +312,7 @@ function coverageZoneLabel(zone: string): string {
const DATA_THEME_MAP_STYLES: Record<DataThemeId, { fill: string; line: string }> = {
administrative: { fill: '#5f6f7f', line: '#344554' },
buildings: { fill: '#d45f3d', line: '#9f3e24' },
land_cover: { fill: '#4f7b4f', line: '#315c39' },
space_occupation: { fill: '#be3e33', line: '#8f2c24' },
open_space: { fill: '#267a46', line: '#175c32' },
population: { fill: '#7559a6', line: '#5b3f88' },
@@ -358,6 +368,12 @@ function datasetAvailabilityLabel(
const resolutionLabel = Number.isFinite(resolution) ? `${resolution.toLocaleString('nl-BE')} m` : 'Raster'
return `${resolutionLabel} officiële bron${Number.isFinite(year) ? ` · ${year}` : ''}`
}
if (dataset.dataset_type === 'raster' && dataset.source_name === 'spw_walous_land_cover') {
const resolution = Number(dataset.source_metadata?.['analysis_resolution_m'])
const year = Number(dataset.source_metadata?.['observation_year'])
const resolutionLabel = Number.isFinite(resolution) ? `${resolution.toLocaleString('nl-BE')} m` : 'Raster'
return `${resolutionLabel} WALOUS-landbedekking${Number.isFinite(year) ? ` · ${year}` : ''}`
}
if (dataset.source_name === 'ngi_adminvector') {
return `${(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0).toLocaleString('nl-BE')} officiële bestuursgebieden`
}
@@ -406,6 +422,9 @@ function datasetMatchesTheme(dataset: DatasetCreateResponse, theme: DataTheme):
if (dataset.source_name === 'department_omgeving_thematic_raster') {
return dataset.source_metadata?.['theme'] === theme.id
}
if (dataset.source_name === 'spw_walous_land_cover') {
return theme.id === 'land_cover'
}
if (dataset.source_name === 'dov_soil_map') {
return theme.id === 'soil'
}
@@ -522,6 +541,7 @@ function pickThemeDataset(
(dataset.source_name === 'inbo_bwk_natura2000' ? 95_000 : 0) +
(dataset.source_name === 'agentschap_landbouw_zeevisserij_agricultural_parcels' ? 98_000 : 0) +
(dataset.source_name === 'department_omgeving_thematic_raster' ? 5_000_000 : 0) +
(dataset.source_name === 'spw_walous_land_cover' ? 5_000_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) +
@@ -636,6 +656,10 @@ function formatDatasetObservation(dataset: DatasetCreateResponse): string {
return `referentiejaar ${observationYear}`
}
}
if (dataset.source_name === 'spw_walous_land_cover') {
const observationYear = Number(dataset.source_metadata?.['observation_year'])
return Number.isFinite(observationYear) ? `WALOUS referentiejaar ${observationYear}` : 'WALOUS landbedekking'
}
const period = dataset.source_metadata?.['acquisition_period']
if (typeof period === 'string' && period.trim()) {
return `opnameperiode ${period}`
@@ -1085,6 +1109,29 @@ export function MapWorkspace({
})
}
}
const latestWalous = officialMapProducts.walous
.filter((product) => product.configured && productCoversZones(product.coverage_zones, effectiveZones))
.sort((left, right) => right.observation_year - left.observation_year)[0]
if (latestWalous) {
result.push({
kind: 'walous',
productKey: latestWalous.key,
historyProductKeys: officialMapProducts.walous
.filter(
(product) =>
product.configured
&& product.key !== latestWalous.key
&& productCoversZones(product.coverage_zones, effectiveZones),
)
.map((product) => product.key),
displayName: latestWalous.display_name,
theme: 'land_cover',
availabilityLabel: `${latestWalous.analysis_resolution_m ?? latestWalous.native_resolution_m} m analyse · ${latestWalous.observation_year} · automatisch bij selectie`,
attribution: latestWalous.attribution,
limitationMessage: latestWalous.limitation_message,
coverageZones: latestWalous.coverage_zones,
})
}
for (const product of officialMapProducts.officialVector.filter((item) =>
productCoversZones(item.coverage_zones, effectiveZones),
)) {
@@ -1319,6 +1366,20 @@ export function MapWorkspace({
: [],
[activeThemeDataset, selectedProjectId, thematicRasterBounds],
)
const walousRasterBounds = activeThemeDataset?.source_name === 'spw_walous_land_cover'
? activeThemeDataset.source_metadata?.['bbox_epsg4326']
: null
const walousRasterImageOverlays = useMemo(
() => activeThemeDataset?.source_name === 'spw_walous_land_cover' && selectedProjectId && Array.isArray(walousRasterBounds) && walousRasterBounds.length === 4
? [{
url: walousRasterImageUrl(selectedProjectId, activeThemeDataset.id),
bbox: walousRasterBounds.map(Number) as [number, number, number, number],
label: getDatasetDisplayName(activeThemeDataset),
opacity: 0.82,
}]
: [],
[activeThemeDataset, selectedProjectId, walousRasterBounds],
)
const bathymetryRasterBounds = activeThemeDataset?.source_name === 'spw_bathymetry'
? activeThemeDataset.source_metadata?.['bbox_epsg4326']
: null
@@ -1338,6 +1399,8 @@ export function MapWorkspace({
const activeImageOverlays = useMemo(
() => bathymetryRasterImageOverlays.length > 0
? bathymetryRasterImageOverlays
: walousRasterImageOverlays.length > 0
? walousRasterImageOverlays
: thematicRasterImageOverlays.length > 0
? thematicRasterImageOverlays
: floodHazardImageOverlays.length > 0
@@ -1345,7 +1408,7 @@ export function MapWorkspace({
: terrainImageOverlays.length > 0
? terrainImageOverlays
: orthophotoImageOverlay ? [orthophotoImageOverlay] : [],
[bathymetryRasterImageOverlays, floodHazardImageOverlays, orthophotoImageOverlay, terrainImageOverlays, thematicRasterImageOverlays],
[bathymetryRasterImageOverlays, floodHazardImageOverlays, orthophotoImageOverlay, terrainImageOverlays, thematicRasterImageOverlays, walousRasterImageOverlays],
)
const municipalityAreaCount = areas.filter((area) => /^Gemeente\s/i.test(area.name)).length
const themeTemporalSeriesMap = useMemo(
@@ -1908,6 +1971,7 @@ export function MapWorkspace({
kind: onDemandProduct.kind,
productKey: onDemandProduct.productKey,
displayName: onDemandProduct.displayName,
historyProductKeys: onDemandProduct.historyProductKeys,
},
acquisitionBboxes: onDemandProduct.acquisitionBboxes,
featureLimit: resultFeatureLimit,
@@ -2471,7 +2535,7 @@ export function MapWorkspace({
/>
<div className="geo-map-legend" aria-label="Kaartlegende">
<span><i className="geo-legend-area" /> Werkgebied</span>
{thematicRasterImageOverlays.length > 0 ? (
{thematicRasterImageOverlays.length > 0 || walousRasterImageOverlays.length > 0 ? (
<span className="geo-legend-thematic">
<i className={`geo-legend-ramp geo-legend-ramp-${activeTheme.id}`} />
<small>{thematicLegendMin} → {thematicLegendMax}</small>