feat: add cross-domain Mol data profile
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-16 03:06:36 +02:00
parent 4aa0d6da24
commit 035ec3b233
39 changed files with 3049 additions and 22 deletions
+8
View File
@@ -555,6 +555,14 @@ provider technology: space/buildings, nature/agriculture, soil/relief,
mobility/accessibility, population/services and climate/living environment.
The central definitions live in `src/lib/sourcePortfolio.ts`.
The current Map explorer also recognizes the governed `Ruimtebeslag`, `Open
ruimte`, `Bevolking`, `Bereikbaarheid`, `Voorzieningen` and `Bodem` Datasets.
The first five render persisted raster PNGs with product-specific legends and
return cell-based semantic metrics; `Bodem` renders the persisted DOV polygons
and exposes soil attributes on feature selection. A single official snapshot
never activates Evolution by itself. Evolution is enabled only when at least
two comparable observations exist.
The domain cards count only matching `ready` Datasets as operational. Audited
official sources that have not passed acquisition, persistence and metric
validation remain inside the collapsed follow-up list with an explicit
+4 -1
View File
@@ -191,7 +191,10 @@ function App(): JSX.Element {
const floodHazards = datasets.filter(
(dataset) => dataset.dataset_type === 'raster' && dataset.source_name === 'vmm_flood_hazard' && dataset.status === 'ready',
)
return [...vectors, ...terrain, ...floodHazards]
const thematicRasters = datasets.filter(
(dataset) => dataset.dataset_type === 'raster' && dataset.source_name === 'department_omgeving_thematic_raster' && dataset.status === 'ready',
)
return [...vectors, ...terrain, ...floodHazards, ...thematicRasters]
},
[datasets],
)
+98 -9
View File
@@ -8,6 +8,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'
const DEFAULT_SELECTED_FEATURE_FILENAME = 'selected-feature.geojson'
const DEFAULT_AREA_SELECTION_FILENAME = 'area-selection.geojson'
@@ -15,7 +16,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' | 'flood_hazard' | 'elevation' | 'roads' | 'parcels'
type DataThemeId = 'buildings' | 'space_occupation' | 'open_space' | 'population' | 'forest' | 'nature_value' | 'agriculture' | 'soil' | 'water' | 'flood_hazard' | 'elevation' | 'accessibility' | 'services' | 'roads' | 'parcels'
interface DataTheme {
id: DataThemeId
@@ -39,6 +40,20 @@ const DATA_THEMES: DataTheme[] = [
description: 'Gebouwen en gebouwcontouren uit GRB of een andere persistente bron.',
tokens: ['buildings', 'building', 'gebouwen', 'gebouw', 'bebouwing', 'gbg'],
},
{
id: 'space_occupation',
label: 'Ruimtebeslag',
shortLabel: 'Ruimtebeslag',
description: 'Officiële 10 m-beleidskaart van ruimte ingenomen door wonen, economie, infrastructuur en recreatie.',
tokens: ['space_occupation', 'ruimtebeslag', 'ruibes'],
},
{
id: 'open_space',
label: 'Open ruimte',
shortLabel: 'Open ruimte',
description: 'Officiële 10 m-beleidskaart van open ruimte buiten kernen en ruimtebeslag.',
tokens: ['open_space', 'open ruimte', 'openruimte'],
},
{
id: 'population',
label: 'Bevolking',
@@ -67,6 +82,13 @@ const DATA_THEMES: DataTheme[] = [
description: 'Jaarlijkse officiële landbouwgebruikspercelen en hoofdteeltgroepen.',
tokens: ['agriculture', 'agricultural', 'landbouw', 'landbouwgebruik', 'agpa'],
},
{
id: 'soil',
label: 'Bodem',
shortLabel: 'Bodemkaart',
description: 'Historische DOV-bodemkartering met bodemtype, textuur en drainageklasse voor Mol.',
tokens: ['soil', 'bodem', 'bodemkaart', 'bodemtype', 'dov_soil_map'],
},
{
id: 'water',
label: 'Water',
@@ -88,6 +110,20 @@ const DATA_THEMES: DataTheme[] = [
description: 'Maaiveld- of oppervlaktehoogte, reliëf en helling uit DHMV II.',
tokens: ['dhmv', 'elevation', 'height', 'hoogte', 'terrain', 'surface', 'dtm', 'dsm', 'reliëf'],
},
{
id: 'accessibility',
label: 'Bereikbaarheid',
shortLabel: 'Knooppuntwaarde',
description: 'Knooppuntwaarde van collectief vervoer per hectare voor referentiejaar 2022.',
tokens: ['accessibility', 'bereikbaarheid', 'knooppuntwaarde', 'knptw'],
},
{
id: 'services',
label: 'Voorzieningen',
shortLabel: 'Voorzieningenniveau',
description: 'Genormaliseerde nabijheid van basis-, regionale en metropolitane voorzieningen in 2022.',
tokens: ['services', 'voorzieningen', 'voorzieningenniveau', 'totvznv'],
},
{
id: 'roads',
label: 'Wegen',
@@ -106,13 +142,18 @@ const DATA_THEMES: DataTheme[] = [
const DATA_THEME_MAP_STYLES: Record<DataThemeId, { fill: string; line: string }> = {
buildings: { fill: '#d45f3d', line: '#9f3e24' },
space_occupation: { fill: '#be3e33', line: '#8f2c24' },
open_space: { fill: '#267a46', line: '#175c32' },
population: { fill: '#7559a6', line: '#5b3f88' },
forest: { fill: '#347950', line: '#225f3b' },
nature_value: { fill: '#9a4f64', line: '#74364a' },
agriculture: { fill: '#7b8f32', line: '#53671d' },
soil: { fill: '#9a7040', line: '#6f4c27' },
water: { fill: '#2676a8', line: '#155b85' },
flood_hazard: { fill: '#1597c2', line: '#075985' },
elevation: { fill: '#a57a4b', line: '#315f59' },
accessibility: { fill: '#0f766e', line: '#115e59' },
services: { fill: '#b66d16', line: '#854d0e' },
roads: { fill: '#6b7280', line: '#4b5563' },
parcels: { fill: '#a7792f', line: '#7d571f' },
}
@@ -126,6 +167,12 @@ function datasetAvailabilityLabel(dataset: DatasetCreateResponse): string {
const resolution = Number(dataset.source_metadata?.['analysis_resolution_m'])
return `${Number.isFinite(resolution) ? `${resolution.toLocaleString('nl-BE')} m` : 'Raster'} overstromingsscenario`
}
if (dataset.dataset_type === 'raster' && dataset.source_name === 'department_omgeving_thematic_raster') {
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} officiële bron${Number.isFinite(year) ? ` · ${year}` : ''}`
}
return `${(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0).toLocaleString('nl-BE')} objecten beschikbaar`
}
@@ -156,6 +203,12 @@ function datasetMatchesTheme(dataset: DatasetCreateResponse, theme: DataTheme):
if (dataset.source_name === 'digitaal_vlaanderen_dhmv') {
return theme.id === 'elevation'
}
if (dataset.source_name === 'department_omgeving_thematic_raster') {
return dataset.source_metadata?.['theme'] === theme.id
}
if (dataset.source_name === 'dov_soil_map') {
return theme.id === 'soil'
}
const searchText = datasetSearchText(dataset)
return theme.tokens.some((token) => searchText.includes(token))
}
@@ -184,6 +237,7 @@ function pickThemeDataset(
(dataset.source_name === 'department_omgeving_land_use' ? 90_000 : 0) +
(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 === '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) +
@@ -763,7 +817,20 @@ export function MapWorkspace({
opacity: 0.82,
}
: null
const activeImageOverlay = floodHazardImageOverlay ?? terrainImageOverlay ?? orthophotoImageOverlay
const thematicRasterBounds = activeThemeDataset?.source_name === 'department_omgeving_thematic_raster'
? activeThemeDataset.source_metadata?.['bbox_epsg4326']
: null
const thematicRasterImageOverlay = activeThemeDataset?.source_name === 'department_omgeving_thematic_raster' && selectedProjectId && Array.isArray(thematicRasterBounds) && thematicRasterBounds.length === 4
? {
url: thematicRasterImageUrl(selectedProjectId, activeThemeDataset.id),
bbox: thematicRasterBounds.map(Number) as [number, number, number, number],
label: getDatasetDisplayName(activeThemeDataset),
opacity: 0.78,
}
: null
const thematicLegendMin = String(activeThemeDataset?.source_metadata?.['legend_min_label'] ?? 'Lagere waarde')
const thematicLegendMax = String(activeThemeDataset?.source_metadata?.['legend_max_label'] ?? 'Hogere waarde')
const activeImageOverlay = thematicRasterImageOverlay ?? 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
@@ -775,7 +842,9 @@ export function MapWorkspace({
[availableMapDatasets],
)
const activeTemporalSeriesGroups = themeTemporalSeriesMap[activeTheme.id]
const availableEvolutionThemes = DATA_THEMES.filter((theme) => themeTemporalSeriesMap[theme.id].length > 0)
const availableEvolutionThemes = DATA_THEMES.filter((theme) =>
themeTemporalSeriesMap[theme.id].some((group) => group.items.length >= 2),
)
const activeTemporalSeriesGroup = activeTemporalSeriesGroups.find((group) => group.key === selectedTemporalSeriesKey)
?? activeTemporalSeriesGroups[0]
const activeTemporalSeries = activeTemporalSeriesGroup?.items ?? EMPTY_TEMPORAL_SERIES
@@ -815,13 +884,28 @@ export function MapWorkspace({
(metric) => metric.metric_key !== activeSelectionResult?.summary?.primary_metric_key,
)
const terrainReliefMetric = activeSupportingMetrics.find((metric) => metric.metric_key === 'relief_m')
const populationDensityMetric = activeSupportingMetrics.find((metric) => metric.metric_key === 'population_density_mean_per_ha')
const scoreMedianMetric = activeSupportingMetrics.find((metric) => metric.metric_key.endsWith('_median'))
const activeSecondaryMetric = activeMetricUnit === 'm TAW'
? terrainReliefMetric ? `${terrainReliefMetric.metric_value.toLocaleString('nl-BE', { maximumFractionDigits: 2 })} m reliëf` : null
: selectedAreaSquareMetres && selectedAreaSquareMetres > 0
? activeMetricUnit === 'ha'
: activeMetricUnit === 'inwoners'
? populationDensityMetric ? selectionMetricLabel(populationDensityMetric) : null
: activeMetricUnit.startsWith('score')
? scoreMedianMetric ? selectionMetricLabel(scoreMedianMetric) : null
: selectedAreaSquareMetres && selectedAreaSquareMetres > 0
? activeMetricUnit === 'ha'
? `${((activeMetricValue * 10_000) / selectedAreaSquareMetres * 100).toLocaleString('nl-BE', { maximumFractionDigits: 1 })}% dekking`
: `${(activeMetricValue / (selectedAreaSquareMetres / 1_000_000)).toLocaleString('nl-BE', { maximumFractionDigits: 1 })} ${activeMetricUnit} / km2`
: null
const activeSecondaryLabel = activeMetricUnit === 'ha'
? 'Aandeel selectie'
: activeMetricUnit === 'm TAW'
? 'Reliëf'
: activeMetricUnit === 'inwoners'
? 'Gemiddelde dichtheid'
: activeMetricUnit.startsWith('score')
? 'Mediaan'
: 'Dichtheid'
const selectedResultProperties = useMemo(() => {
const keys = new Map<string, Set<string>>()
for (const feature of activeSelectionResult?.geojson.features ?? []) {
@@ -1185,7 +1269,7 @@ export function MapWorkspace({
const dataset = themeDatasetMap[theme.id]
const temporalGroups = themeTemporalSeriesMap[theme.id]
const temporalGroup = temporalGroups[0]
const evolutionAvailable = temporalGroups.length > 0
const evolutionAvailable = temporalGroups.some((group) => group.items.length >= 2)
const available = Boolean(dataset) && (analysisMode === 'current' || evolutionAvailable)
const active = activeThemeId === theme.id
const firstObservation = temporalGroup?.items[0]?.observed_at
@@ -1382,7 +1466,12 @@ export function MapWorkspace({
/>
<div className="geo-map-legend" aria-label="Kaartlegende">
<span><i className="geo-legend-area" /> Werkgebied</span>
{activeImageOverlay ? <span><i className="geo-legend-imagery" /> {activeImageOverlay.label}</span> : null}
{thematicRasterImageOverlay ? (
<span className="geo-legend-thematic">
<i className={`geo-legend-ramp geo-legend-ramp-${activeTheme.id}`} />
<small>{thematicLegendMin} {thematicLegendMax}</small>
</span>
) : activeImageOverlay ? <span><i className="geo-legend-imagery" /> {activeImageOverlay.label}</span> : null}
{analysisOverlayActive ? (
<>
<span><i className="geo-legend-layer geo-legend-layer-buildings" /> AI-kandidaten</span>
@@ -1585,7 +1674,7 @@ export function MapWorkspace({
<strong>{activeSelectionResult ? resultMetricLabel(activeSelectionResult) : 'Geen resultaat'}</strong>
</div>
<div>
<span>{activeMetricUnit === 'ha' ? 'Aandeel selectie' : activeMetricUnit === 'm TAW' ? 'Reliëf' : 'Dichtheid'}</span>
<span>{activeSecondaryLabel}</span>
<strong>{activeSecondaryMetric ?? (selectedDensity === null ? 'n.v.t.' : `${selectedDensity.toLocaleString('nl-BE', { maximumFractionDigits: 1 })} / km2`)}</strong>
</div>
</div>
@@ -1658,7 +1747,7 @@ export function MapWorkspace({
{analysisMode === 'current' ? (
<div className="geo-result-actions">
<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 || activeThemeDataset?.dataset_type === 'raster'} type="button" onClick={downloadActiveThemeSelection}>Download GeoJSON</button>
<button className="secondary-action" disabled={!activeSelectionResult} type="button" onClick={copyActiveThemeSelection}>Kopieer gegevens</button>
</div>
) : null}
+9 -2
View File
@@ -4,6 +4,7 @@ import { formatError } from '../lib/formatError'
import type { DatasetCreateResponse, VectorSelectionBBox, VectorSelectionResponse } from '../types'
import { terrainSelectionToMapSelection } from '../lib/terrainSelection'
import { floodHazardSelectionToMapSelection } from '../lib/floodHazardSelection'
import { thematicRasterSelectionToMapSelection } from '../lib/thematicRaster'
interface MapSelectionExtractOptions {
selectedProjectId: string | null
@@ -42,8 +43,9 @@ export function useMapSelectionExtract({
}
const terrainDataset = selectedDataset.dataset_type === 'raster' && selectedDataset.source_name === 'digitaal_vlaanderen_dhmv'
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.')
const thematicRasterDataset = selectedDataset.dataset_type === 'raster' && selectedDataset.source_name === 'department_omgeving_thematic_raster'
if (!isVectorDatasetType(selectedDataset.dataset_type) && !terrainDataset && !floodHazardDataset && !thematicRasterDataset) {
setMapSelectionError('Gebiedsanalyse ondersteunt een vectorlaag of een beheerd thematisch raster.')
return null
}
@@ -63,6 +65,11 @@ export function useMapSelectionExtract({
bbox: { ...bbox, crs: 'EPSG:4326' },
area_id: areaId,
}))
: thematicRasterDataset
? thematicRasterSelectionToMapSelection(await datasetsApi.selectThematicRaster(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,
@@ -4,6 +4,7 @@ import { datasetsApi } from '../services/api/datasets'
import type { DatasetCreateResponse, VectorSelectionBBox, VectorSelectionResponse } from '../types'
import { terrainSelectionToMapSelection } from '../lib/terrainSelection'
import { floodHazardSelectionToMapSelection } from '../lib/floodHazardSelection'
import { thematicRasterSelectionToMapSelection } from '../lib/thematicRaster'
export interface MapThemeQuery<TThemeId extends string> {
themeId: TThemeId
@@ -66,6 +67,11 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
bbox,
area_id: areaId,
}))
: dataset.dataset_type === 'raster' && dataset.source_name === 'department_omgeving_thematic_raster'
? thematicRasterSelectionToMapSelection(await datasetsApi.selectThematicRaster(selectedProjectId, dataset.id, {
bbox,
area_id: areaId,
}))
: await datasetsApi.selectVectorFeatures(selectedProjectId, dataset.id, {
bbox,
area_id: areaId,
+11
View File
@@ -9,8 +9,13 @@ const DATASET_LABEL_BY_LAYER: Record<string, string> = {
forest: 'Bos en groen',
nature_value: 'Natuurwaarde',
agriculture: 'Landbouwgebruikspercelen',
soil: 'Digitale bodemkaart',
flood_hazard: 'Overstromingsgevaar',
elevation: 'Hoogte en reliëf',
space_occupation: 'Ruimtebeslag',
open_space: 'Open ruimte',
accessibility: 'Knooppuntwaarde',
services: 'Voorzieningenniveau',
building_registry: 'Gebouwenregister',
regional_boundary: 'Grens vervoerregio Kempen',
municipality_boundaries: 'Gemeentegrenzen Kempen',
@@ -29,6 +34,8 @@ const DATASET_SOURCE_LABELS: Record<string, string> = {
vrbg: 'Digitaal Vlaanderen',
waterinfo: 'Waterinfo Vlaanderen',
vmm_flood_hazard: 'Vlaamse Milieumaatschappij',
department_omgeving_thematic_raster: 'Departement Omgeving',
dov_soil_map: 'Databank Ondergrond Vlaanderen',
}
export function getDatasetSourceDisplayName(dataset: DatasetCreateResponse): string {
@@ -45,6 +52,10 @@ export function getDatasetDisplayName(dataset: DatasetCreateResponse): string {
const productName = dataset.source_metadata?.['product_display_name']
return typeof productName === 'string' && productName.trim() ? productName : 'VMM-overstromingsscenario'
}
if (dataset.source_name === 'department_omgeving_thematic_raster') {
const productName = dataset.source_metadata?.['product_display_name']
return typeof productName === 'string' && productName.trim() ? productName : 'Officieel Vlaams themaraster'
}
const layer = (dataset.reference_layer_name ?? dataset.source_metadata?.layer_name ?? dataset.source_metadata?.layer_type ?? '')
.toString()
.toLowerCase()
+11 -8
View File
@@ -35,6 +35,9 @@ const sourceNameIs = (dataset: DatasetCreateResponse, sourceName: string): boole
const referenceLayerIs = (dataset: DatasetCreateResponse, layerName: string): boolean =>
String(dataset.reference_layer_name ?? dataset.source_metadata?.['theme'] ?? '').toLowerCase() === layerName
const thematicProductIs = (dataset: DatasetCreateResponse, productKey: string): boolean =>
sourceNameIs(dataset, 'department_omgeving_thematic_raster') && dataset.source_metadata?.['product_key'] === productKey
export const SOURCE_DOMAINS: SourceDomainDefinition[] = [
{
key: 'space',
@@ -127,7 +130,7 @@ export const OFFICIAL_SOURCE_PORTFOLIO: OfficialSourceDefinition[] = [
metricExamples: 'hectare ruimtebeslag, aandeel, vergelijking met open ruimte',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/ruimtebeslag-vlaanderen-toestand-2025',
matches: (dataset) => sourceNameIs(dataset, 'department_omgeving_space_occupation'),
matches: (dataset) => thematicProductIs(dataset, 'space_occupation_2025'),
},
{
key: 'settlement_typology',
@@ -137,7 +140,7 @@ export const OFFICIAL_SOURCE_PORTFOLIO: OfficialSourceDefinition[] = [
coverage: 'Toestand 2022',
value: 'Begrijpelijke morfologie voor kern, lint en verspreide bebouwing.',
metricExamples: 'oppervlakte en aandeel per morfologisch type',
priority: 'planned',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/kernen-linten-verspreide-bebouwing-in-vlaanderen-kernen-toestand-2022',
matches: (dataset) => sourceNameIs(dataset, 'department_omgeving_settlement_typology'),
},
@@ -187,7 +190,7 @@ export const OFFICIAL_SOURCE_PORTFOLIO: OfficialSourceDefinition[] = [
metricExamples: 'hectare open ruimte, aandeel en fragmentatie',
priority: 'planned',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/open-ruimte-vlaanderen-toestand-2022',
matches: (dataset) => sourceNameIs(dataset, 'department_omgeving_open_space'),
matches: (dataset) => thematicProductIs(dataset, 'open_space_2022'),
},
{
key: 'dhmv',
@@ -244,10 +247,10 @@ export const OFFICIAL_SOURCE_PORTFOLIO: OfficialSourceDefinition[] = [
owner: 'Departement Omgeving',
coverage: '1 ha raster, toestand 2022',
value: 'Modelmatige bereikbaarheid via collectief vervoer.',
metricExamples: 'gemiddelde score en aandeel per bereikbaarheidsklasse',
metricExamples: 'gemiddelde, mediaan en percentielen van de bronindex',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/knooppuntwaarde-per-ha-toestand-2022',
matches: (dataset) => sourceNameIs(dataset, 'department_omgeving_node_value'),
matches: (dataset) => thematicProductIs(dataset, 'node_value_2022'),
},
{
key: 'hoppin',
@@ -295,7 +298,7 @@ export const OFFICIAL_SOURCE_PORTFOLIO: OfficialSourceDefinition[] = [
metricExamples: 'geschat aantal inwoners en inwoners per hectare',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/inwonersdichtheid-per-ha-vlaanderen-toestand-2019',
matches: (dataset) => sourceNameIs(dataset, 'department_omgeving_population_density'),
matches: (dataset) => thematicProductIs(dataset, 'population_density_2019'),
},
{
key: 'service_level',
@@ -304,10 +307,10 @@ export const OFFICIAL_SOURCE_PORTFOLIO: OfficialSourceDefinition[] = [
owner: 'Departement Omgeving',
coverage: '1 ha raster, toestand 2022',
value: 'Nabijheid van dagelijkse voorzieningen in één brongetrouwe score.',
metricExamples: 'gemiddelde score en aandeel per voorzieningsklasse',
metricExamples: 'gemiddelde, mediaan en percentielen van de 0-1-score',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/totaal-voorzieningenniveau-toestand-2022',
matches: (dataset) => sourceNameIs(dataset, 'department_omgeving_service_level'),
matches: (dataset) => thematicProductIs(dataset, 'service_level_2022'),
},
{
key: 'municipality_indicators',
+27
View File
@@ -0,0 +1,27 @@
import type { ThematicRasterSelectionResponse, VectorSelectionResponse } from '../types'
export function thematicRasterImageUrl(projectId: string, datasetId: string): string {
return `/api/v1/projects/${projectId}/datasets/${datasetId}/raster/thematic/image`
}
export function thematicRasterSelectionToMapSelection(result: ThematicRasterSelectionResponse): VectorSelectionResponse {
return {
selection_bbox: result.selection_bbox,
selection_area_id: result.selection_area_id,
feature_count: result.valid_cell_count,
total_feature_count: result.valid_cell_count,
limit: 0,
truncated: false,
geojson: { type: 'FeatureCollection', features: [] },
summary: {
...result.summary,
feature_count: result.valid_cell_count,
is_estimate: true,
warning: result.limitation_message,
metrics: result.summary.metrics.map((metric) => ({
...metric,
is_estimate: metric.is_estimate ?? true,
})),
},
}
}
+13
View File
@@ -24,6 +24,9 @@ import type {
FloodHazardSelectionResponse,
DhmvProductRead,
TerrainSelectionResponse,
ThematicRasterAcquireRequest,
ThematicRasterProductRead,
ThematicRasterSelectionResponse,
} from '../../types'
const DATASET_PAGE_SIZE = 200
@@ -141,6 +144,16 @@ export const datasetsApi = {
payload: { bbox: VectorSelectionRequest['bbox']; area_id?: string },
): Promise<FloodHazardSelectionResponse> =>
apiPost<FloodHazardSelectionResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/flood-hazard/select`, payload),
acquireThematicRaster: (projectId: string, payload: ThematicRasterAcquireRequest): Promise<JobRead> =>
apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/thematic-raster/acquire`, payload),
listThematicRasterProducts: (projectId: string): Promise<{ items: ThematicRasterProductRead[]; total: number }> =>
apiGet<{ items: ThematicRasterProductRead[]; total: number }>(`/api/v1/projects/${projectId}/datasets/thematic-raster/products`),
selectThematicRaster: (
projectId: string,
datasetId: string,
payload: { bbox: VectorSelectionRequest['bbox']; area_id?: string },
): Promise<ThematicRasterSelectionResponse> =>
apiPost<ThematicRasterSelectionResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/thematic/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> =>
+56
View File
@@ -5751,13 +5751,18 @@ section {
}
.geo-theme-symbol-buildings { background: #d45f3d; }
.geo-theme-symbol-space_occupation { background: #be3e33; }
.geo-theme-symbol-open_space { background: #267a46; }
.geo-theme-symbol-population { background: #7559a6; }
.geo-theme-symbol-forest { background: #347950; }
.geo-theme-symbol-nature_value { background: #9a4f64; }
.geo-theme-symbol-agriculture { background: #7b8f32; }
.geo-theme-symbol-soil { background: #9a7040; }
.geo-theme-symbol-water { background: #2676a8; }
.geo-theme-symbol-flood_hazard { background: #1597c2; }
.geo-theme-symbol-elevation { background: #a57a4b; }
.geo-theme-symbol-accessibility { background: #0f766e; }
.geo-theme-symbol-services { background: #b66d16; }
.geo-theme-symbol-roads { background: #6b7280; }
.geo-theme-symbol-parcels { background: #a7792f; }
@@ -5932,6 +5937,16 @@ section {
background: rgba(117, 89, 166, 0.24);
}
.geo-map-legend .geo-legend-layer-space_occupation {
border-color: #8f2c24;
background: rgba(190, 62, 51, 0.28);
}
.geo-map-legend .geo-legend-layer-open_space {
border-color: #175c32;
background: rgba(38, 122, 70, 0.26);
}
.geo-map-legend .geo-legend-layer-forest {
border-color: #225f3b;
background: rgba(52, 121, 80, 0.24);
@@ -5942,6 +5957,11 @@ section {
background: rgba(38, 118, 168, 0.24);
}
.geo-map-legend .geo-legend-layer-soil {
border-color: #6f4c27;
background: rgba(154, 112, 64, 0.24);
}
.geo-map-legend .geo-legend-layer-flood_hazard {
border-color: #075985;
background: rgba(21, 151, 194, 0.28);
@@ -5952,6 +5972,16 @@ section {
background: rgba(165, 122, 75, 0.26);
}
.geo-map-legend .geo-legend-layer-accessibility {
border-color: #115e59;
background: rgba(15, 118, 110, 0.26);
}
.geo-map-legend .geo-legend-layer-services {
border-color: #854d0e;
background: rgba(182, 109, 22, 0.26);
}
.geo-map-legend .geo-legend-layer-roads {
border-color: #4b5563;
background: rgba(107, 114, 128, 0.24);
@@ -5972,6 +6002,32 @@ section {
background: linear-gradient(135deg, #7a9b68 0 33%, #d1b37a 33% 66%, #8eb5cb 66%);
}
.geo-map-legend .geo-legend-thematic {
display: inline-flex;
align-items: center;
gap: 0.42rem;
}
.geo-map-legend .geo-legend-thematic small {
color: #42504c;
font-size: 0.66rem;
font-weight: 700;
}
.geo-map-legend .geo-legend-ramp {
display: block;
width: 3.4rem;
height: 0.62rem;
border: 1px solid rgba(23, 39, 34, 0.25);
border-radius: 2px;
}
.geo-legend-ramp-space_occupation { background: linear-gradient(90deg, #fbe7d3, #be3e33); }
.geo-legend-ramp-open_space { background: linear-gradient(90deg, #ddeedb, #267a46); }
.geo-legend-ramp-population { background: linear-gradient(90deg, #eee7f6, #673a97); }
.geo-legend-ramp-accessibility { background: linear-gradient(90deg, #e9f1f4, #0f766e); }
.geo-legend-ramp-services { background: linear-gradient(90deg, #fff4bf, #b66d16); }
.geo-map-legend .geo-legend-added {
border-color: #15803d;
background: rgba(22, 163, 74, 0.2);
+51
View File
@@ -434,6 +434,57 @@ export interface FloodHazardSelectionResponse {
generated_at: string
}
export interface ThematicRasterAcquireRequest {
bbox: VectorSelectionBBox
area_id?: string | null
product_key: string
force_refresh?: boolean
}
export interface ThematicRasterProductRead {
key: string
display_name: string
theme: 'space_occupation' | 'open_space' | 'population' | 'accessibility' | 'services'
metric_kind: 'binary_area' | 'population_density' | 'index_score' | 'normalized_score'
coverage_id: string
native_resolution_m: number
source_crs: 'EPSG:31370'
source_value_unit: string
observation_year: number
source_version: string
catalog_url: string
attribution: string
license_note: string
legend_min_label: string
legend_max_label: string
limitation_message: string
}
export interface ThematicRasterSelectionResponse {
dataset_id: string
product_key: string
theme: ThematicRasterProductRead['theme']
metric_kind: ThematicRasterProductRead['metric_kind']
selection_bbox: VectorSelectionBBox
selection_area_id?: string | null
selected_cell_count: number
valid_cell_count: number
coverage_ratio: number
resolution_m: number
observation_year: number
summary: {
metric_label: string
metric_value: number
metric_unit: string
aggregation_method: string
primary_metric_key: string
metrics: Array<VectorSelectionMetric & { is_estimate: boolean }>
}
unsupported_metrics: string[]
limitation_message: string
generated_at: string
}
export interface MapImageOverlay {
url: string
bbox: [number, number, number, number]