feat: add official Kempen operating scope
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-14 18:32:32 +02:00
parent e9c75b2fd6
commit bb7310e015
17 changed files with 1020 additions and 9 deletions
+2
View File
@@ -911,6 +911,7 @@ function App(): JSX.Element {
{activeWorkspace === 'map' ? (
<MapWorkspace
selectedProjectId={selectedProjectId}
projects={projects}
areas={areas}
selectedMapAreaId={selectedMapAreaId}
areaFeatureCollection={areaFeatureCollection}
@@ -957,6 +958,7 @@ function App(): JSX.Element {
selectedMapDatasetId={selectedDataset && isVectorDatasetType(selectedDataset.dataset_type) ? selectedDataset.id : ''}
selectedFeature={selectedMapFeature}
onSelectMapArea={setSelectedMapAreaId}
onSelectProject={selectProject}
onOpenDatasetInMap={openDatasetInMap}
onSetAreaLayerVisible={setAreaLayerVisible}
onSetAreaLayerOpacity={setAreaLayerOpacity}
+43 -4
View File
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from 'react'
import GeoMap from '../GeoMap'
import type { AreaRead, DatasetCreateResponse, MapViewportState, QaComparisonResult, VectorSelectionBBox, VectorSelectionResponse } from '../../types'
import type { AreaRead, DatasetCreateResponse, MapViewportState, ProjectRead, QaComparisonResult, VectorSelectionBBox, VectorSelectionResponse } from '../../types'
import { featureCollectionBounds } from '../../lib/geojsonBounds'
import { useMapThemeSelectionInsights } from '../../hooks/useMapThemeSelectionInsights'
import { useTemporalComparison } from '../../hooks/useTemporalComparison'
@@ -8,6 +8,8 @@ import { useTemporalComparison } from '../../hooks/useTemporalComparison'
const DEFAULT_SELECTED_FEATURE_FILENAME = 'selected-feature.geojson'
const DEFAULT_AREA_SELECTION_FILENAME = 'area-selection.geojson'
const EMPTY_TEMPORAL_SERIES: DatasetCreateResponse[] = []
const MOL_PROJECT_NAME = 'Mol Municipality Workbench'
const KEMPEN_PROJECT_NAME = 'Kempen Regional Workbench'
type DataThemeId = 'buildings' | 'population' | 'forest' | 'water' | 'roads' | 'parcels'
@@ -163,6 +165,16 @@ function formatObservationDate(value: string | null | undefined): string {
return new Intl.DateTimeFormat('nl-BE', { year: 'numeric', month: 'short', day: 'numeric' }).format(new Date(value))
}
function operationalScopeProjectLabel(project: ProjectRead): string {
if (project.name === MOL_PROJECT_NAME) {
return 'Mol'
}
if (project.name === KEMPEN_PROJECT_NAME) {
return 'Kempen (28 gemeenten)'
}
return project.name
}
function selectionAreaSquareMetres(bbox: VectorSelectionBBox | null): number | null {
if (!bbox) {
return null
@@ -384,6 +396,7 @@ function downloadJsonFile(filename: string, payload: unknown): void {
interface MapWorkspaceProps {
selectedProjectId: string | null
projects: ProjectRead[]
areas: AreaRead[]
selectedMapAreaId: string
areaFeatureCollection: GeoJSON.FeatureCollection | null
@@ -430,6 +443,7 @@ interface MapWorkspaceProps {
availableMapDatasets: DatasetCreateResponse[]
selectedMapDatasetId: string
onSelectMapArea: (areaId: string) => void
onSelectProject: (projectId: string) => void
onOpenDatasetInMap: (dataset: DatasetCreateResponse) => void
onSetAreaLayerVisible: (visible: boolean) => void
onSetAreaLayerOpacity: (opacity: number) => void
@@ -451,6 +465,7 @@ interface MapWorkspaceProps {
export function MapWorkspace({
selectedProjectId,
projects,
areas,
selectedMapAreaId,
areaFeatureCollection,
@@ -497,6 +512,7 @@ export function MapWorkspace({
availableMapDatasets,
selectedMapDatasetId,
onSelectMapArea,
onSelectProject,
onOpenDatasetInMap,
onSetAreaLayerVisible,
onSetAreaLayerOpacity,
@@ -574,6 +590,16 @@ export function MapWorkspace({
const activeTheme = DATA_THEMES.find((theme) => theme.id === activeThemeId) ?? DATA_THEMES[0]
const activeThemeMapStyle = DATA_THEME_MAP_STYLES[activeTheme.id]
const activeThemeDataset = themeDatasetMap[activeTheme.id]
const operationalScopeProjects = useMemo(() => {
const scoped = projects.filter((project) => project.name === MOL_PROJECT_NAME || project.name === KEMPEN_PROJECT_NAME)
const selected = projects.find((project) => project.id === selectedProjectId)
if (selected && !scoped.some((project) => project.id === selected.id)) {
return [selected, ...scoped]
}
return scoped
}, [projects, selectedProjectId])
const activeScopeProject = projects.find((project) => project.id === selectedProjectId) ?? null
const activeScopeLabel = activeScopeProject ? operationalScopeProjectLabel(activeScopeProject) : 'Werkgebied'
const activeTemporalSeriesGroups = useMemo(
() => listThemeTemporalSeries(availableMapDatasets, activeTheme),
[activeTheme, availableMapDatasets],
@@ -892,10 +918,10 @@ export function MapWorkspace({
if (!advancedMode) {
return (
<section className="geo-explorer" data-testid="map-workspace" aria-label="Gebiedsverkenner Mol">
<section className="geo-explorer" data-testid="map-workspace" aria-label={`Gebiedsverkenner ${activeScopeLabel}`}>
<header className="geo-explorer-header">
<div>
<p className="eyebrow">Mol · geografische verkenner</p>
<p className="eyebrow">{activeScopeLabel} · geografische verkenner</p>
<h2>Wat bevindt zich in dit gebied?</h2>
<p>Kies een datathema, teken een rechthoek en lees de beschikbare gegevens meteen uit.</p>
</div>
@@ -933,6 +959,19 @@ export function MapWorkspace({
<p>Dit zijn databronnen, geen AI-modellen.</p>
</div>
</div>
<label className="geo-project-scope-select">
Regio
<select
aria-label="Regio"
value={selectedProjectId ?? ''}
onChange={(event) => onSelectProject(event.target.value)}
disabled={operationalScopeProjects.length < 2}
>
{operationalScopeProjects.map((project) => (
<option key={project.id} value={project.id}>{operationalScopeProjectLabel(project)}</option>
))}
</select>
</label>
<div className="geo-theme-list">
{DATA_THEMES.map((theme) => {
const dataset = themeDatasetMap[theme.id]
@@ -1055,7 +1094,7 @@ export function MapWorkspace({
type="button"
onClick={() => selectedAreaBbox && void analyzeSelection(selectedAreaBbox)}
>
Volledige gemeente
Volledig werkgebied
</button>
<button className="secondary-action" disabled={!mapSelectionBbox} type="button" onClick={clearAreaSelection}>
Wis selectie
+14
View File
@@ -5542,6 +5542,20 @@ section {
gap: 0.36rem;
}
.geo-project-scope-select {
display: grid;
gap: 0.24rem;
color: #4c5b56;
font-size: 0.66rem;
font-weight: 800;
}
.geo-project-scope-select select {
min-height: 2.25rem;
padding: 0.35rem 0.45rem;
font-size: 0.72rem;
}
.geo-theme-option {
display: grid;
grid-template-columns: 0.72rem minmax(0, 1fr) auto;