Aggregate regional bathymetry partitions
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-17 17:08:35 +02:00
parent 24247746c1
commit 4fd6c06f5c
17 changed files with 661 additions and 29 deletions
+7
View File
@@ -657,3 +657,10 @@ Municipality partitions remain hidden for a regional Area until their backend
manifest reports complete coverage. The Sources workspace lists MDK North Sea
as read-only probe-only and SPW Walloon bathymetry as planned until bounded
raster/download acquisition is operational.
For a complete regional manifest, the theme card totals all data-bearing
municipality partitions instead of displaying one representative partition.
A regional rectangle or full-Area analysis calls the partitioned backend
selection and draws only its bounded GeoJSON result. Switching to a
municipality automatically returns to the exact single-Area Dataset. Regional
downloads are recomputed server-side through the same manifest-aware path.
+67 -19
View File
@@ -186,7 +186,11 @@ const DATA_THEME_MAP_STYLES: Record<DataThemeId, { fill: string; line: string }>
parcels: { fill: '#a7792f', line: '#7d571f' },
}
function datasetAvailabilityLabel(dataset: DatasetCreateResponse, partitionCount = 1): string {
function datasetAvailabilityLabel(
dataset: DatasetCreateResponse,
partitions: DatasetCreateResponse[] = [dataset],
): string {
const partitionCount = partitions.length
const regionalSuffix = partitionCount > 1 ? ` · ${partitionCount} gemeenten` : ''
if (dataset.dataset_type === 'raster' && dataset.source_name === 'digitaal_vlaanderen_dhmv') {
const resolution = Number(dataset.source_metadata?.['analysis_resolution_m'])
@@ -197,9 +201,15 @@ function datasetAvailabilityLabel(dataset: DatasetCreateResponse, partitionCount
return `${Number.isFinite(resolution) ? `${resolution.toLocaleString('nl-BE')} m` : 'Raster'} overstromingsscenario${regionalSuffix}`
}
if (dataset.source_name === 'vmm_vha_bathymetry_profiles') {
const profiles = dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0
const documents = Number(dataset.source_metadata?.['document_count'] ?? 0)
return `${profiles.toLocaleString('nl-BE')} profielen · ${documents.toLocaleString('nl-BE')} bronbladen`
const profiles = partitions.reduce(
(total, item) => total + (item.feature_count ?? item.vector_summary?.feature_count ?? 0),
0,
)
const documents = partitions.reduce(
(total, item) => total + Number(item.source_metadata?.['document_count'] ?? 0),
0,
)
return `${profiles.toLocaleString('nl-BE')} profielen · ${documents.toLocaleString('nl-BE')} bronbladen${regionalSuffix}`
}
if (dataset.dataset_type === 'raster' && dataset.source_name === 'department_omgeving_thematic_raster') {
const resolution = Number(dataset.source_metadata?.['analysis_resolution_m'])
@@ -261,6 +271,13 @@ function isPartitionedRaster(dataset: DatasetCreateResponse | null | undefined):
)
}
function isPartitionedBathymetry(dataset: DatasetCreateResponse | null | undefined): boolean {
return Boolean(
dataset?.source_name === 'vmm_vha_bathymetry_profiles'
&& dataset.source_metadata?.['regional_partitions_complete'] === true,
)
}
function datasetProductKey(dataset: DatasetCreateResponse): string {
return String(dataset.source_metadata?.['product_key'] ?? '')
}
@@ -271,12 +288,14 @@ function datasetCoversSelectedArea(
regionalScope = false,
): boolean {
const coverageScope = String(dataset.source_metadata?.['coverage_scope'] ?? '')
if (isPartitionedBathymetry(dataset)) {
return regionalScope
? true
: Boolean(selectedAreaId) && dataset.area_id === selectedAreaId
}
if (coverageScope !== 'municipality' || !dataset.area_id) {
return true
}
if (regionalScope && dataset.source_name === 'vmm_vha_bathymetry_profiles') {
return dataset.source_metadata?.['regional_partitions_complete'] === true
}
if (regionalScope) {
return true
}
@@ -292,15 +311,20 @@ function rasterPartitionsForDataset(
if (!representative) {
return []
}
if (!regionalScope || !isPartitionedRaster(representative)) {
if (!regionalScope || (!isPartitionedRaster(representative) && !isPartitionedBathymetry(representative))) {
return [representative]
}
const productKey = datasetProductKey(representative)
const manifestSha256 = String(representative.source_metadata?.['partition_manifest_sha256'] ?? '')
return datasets
.filter(
(dataset) =>
dataset.source_name === representative.source_name
&& datasetProductKey(dataset) === productKey
&& (
isPartitionedBathymetry(representative)
? String(dataset.source_metadata?.['partition_manifest_sha256'] ?? '') === manifestSha256
: datasetProductKey(dataset) === productKey
)
&& datasetCoversSelectedArea(dataset, selectedAreaId, true),
)
.sort((left, right) => String(left.area_id ?? '').localeCompare(String(right.area_id ?? '')))
@@ -740,6 +764,8 @@ export function MapWorkspace({
const activeThemeDataset = themeDatasetMap[activeTheme.id]
const activeThemePartitions = themePartitionMap[activeTheme.id]
const regionalRasterThemeActive = regionalScopeSelected && isPartitionedRaster(activeThemeDataset)
const regionalBathymetryThemeActive = regionalScopeSelected && isPartitionedBathymetry(activeThemeDataset)
const regionalPartitionedThemeActive = regionalRasterThemeActive || regionalBathymetryThemeActive
const terrainImageOverlays = useMemo(
() =>
activeTheme.id === 'elevation' && selectedProjectId
@@ -842,7 +868,15 @@ export function MapWorkspace({
[themeInsights],
)
const activeSelectionResult = themeResults.find((item) => item.theme.id === activeThemeId)?.result
?? (!regionalRasterThemeActive && selectedMapDataset?.id === activeThemeDataset?.id ? mapSelectionResult : null)
?? (!regionalPartitionedThemeActive && selectedMapDataset?.id === activeThemeDataset?.id ? mapSelectionResult : null)
const explorerMapFeatureCollection = regionalBathymetryThemeActive
? null
: analysisMode === 'evolution' && temporalComparison?.geojson.features.length
? temporalComparison.geojson
: mapFeatureCollection
const explorerSelectionFeatureCollection = analysisMode === 'current'
? activeSelectionResult?.geojson ?? null
: null
const selectedAreaSquareMetres = useMemo(
() =>
bboxesEqual(mapSelectionBbox, selectedAreaBbox) && selectedMapArea?.area_m2
@@ -1083,8 +1117,13 @@ export function MapWorkspace({
bbox: { ...mapSelectionBbox, crs: 'EPSG:4326' },
dataset_id: activeThemeDataset.id,
area_id: areaId,
partitioned: regionalRasterThemeActive,
product_key: String(activeThemeDataset.source_metadata?.['product_key'] ?? '') || undefined,
partitioned: regionalPartitionedThemeActive,
product_key: regionalRasterThemeActive
? String(activeThemeDataset.source_metadata?.['product_key'] ?? '') || undefined
: undefined,
partition_scope_key: regionalBathymetryThemeActive
? String(activeThemeDataset.source_metadata?.['partition_scope_key'] ?? 'flanders')
: undefined,
theme_id: activeTheme.id,
name: `${activeTheme.id}-analysis`,
}
@@ -1157,7 +1196,8 @@ export function MapWorkspace({
? [{
themeId: theme.id,
dataset,
partitioned: regionalScopeSelected && isPartitionedRaster(dataset),
partitioned: regionalScopeSelected
&& (isPartitionedRaster(dataset) || isPartitionedBathymetry(dataset)),
}]
: []
})
@@ -1167,7 +1207,7 @@ export function MapWorkspace({
const analyzeSelection = async (bbox: VectorSelectionBBox, areaId?: string) => {
setSelectionBbox(bbox)
const tasks: Array<Promise<unknown>> = [loadAllThemeResults(bbox, areaId)]
if (!regionalRasterThemeActive) {
if (!regionalPartitionedThemeActive) {
tasks.push(onRunMapSelectionExtract(bbox, areaId))
}
if (analysisMode === 'evolution' && earlierDatasetId && laterDatasetId) {
@@ -1328,7 +1368,7 @@ export function MapWorkspace({
<div className="geo-theme-list">
{DATA_THEMES.map((theme) => {
const dataset = themeDatasetMap[theme.id]
const partitionCount = themePartitionMap[theme.id].length
const partitions = themePartitionMap[theme.id]
const temporalGroups = themeTemporalSeriesMap[theme.id]
const temporalGroup = temporalGroups[0]
const evolutionAvailable = temporalGroups.some((group) => group.items.length >= 2)
@@ -1355,7 +1395,7 @@ export function MapWorkspace({
? 'Alleen huidige toestand'
: 'Bron nog niet ingeladen'
: dataset
? datasetAvailabilityLabel(dataset, partitionCount)
? datasetAvailabilityLabel(dataset, partitions)
: 'Bron nog niet ingeladen'}
</small>
</span>
@@ -1376,7 +1416,9 @@ export function MapWorkspace({
? mapLayerLabel
: analysisMode === 'evolution'
? activeTemporalSeriesGroup?.label ?? 'Nog geen historische reeks ingeladen'
: activeThemeDataset ? getDatasetDisplayName(activeThemeDataset) : 'Geen databron beschikbaar'}
: regionalBathymetryThemeActive
? 'VHA-dwarsprofielen Vlaanderen'
: activeThemeDataset ? getDatasetDisplayName(activeThemeDataset) : 'Geen databron beschikbaar'}
</strong>
<small>
{analysisOverlayActive
@@ -1385,6 +1427,8 @@ export function MapWorkspace({
? activeTemporalSeries.length >= 2
? `${activeTemporalSeries.length} officiële meetmomenten · ${formatObservationDate(activeTemporalSeries[0].observed_at)} tot ${formatObservationDate(activeTemporalSeries[activeTemporalSeries.length - 1].observed_at)}`
: 'Voor dit thema is nog geen tweede officieel meetmoment beschikbaar.'
: regionalBathymetryThemeActive
? `${activeThemePartitions.length} gecontroleerde gemeentepartities · selectie wordt ruimtelijk samengevoegd`
: activeThemeDataset
? `${getDatasetSourceDisplayName(activeThemeDataset)} · ${formatDatasetObservation(activeThemeDataset)}`
: activeTheme.description}
@@ -1487,6 +1531,8 @@ export function MapWorkspace({
? 'Sleep nu een rechthoek op de kaart.'
: regionalRasterThemeActive
? 'Teken een rechthoek; de juiste gemeentelijke rasters worden automatisch gecombineerd.'
: regionalBathymetryThemeActive
? 'Teken een rechthoek of analyseer Vlaanderen; alleen overlappende VHA-partities worden samengevoegd.'
: 'Sleep een rechthoek of analyseer het volledige werkgebied.'}
</p>
</div>
@@ -1517,12 +1563,12 @@ export function MapWorkspace({
<div className={bboxSelectionMode ? 'geo-map-canvas geo-map-canvas-drawing' : 'geo-map-canvas'}>
<GeoMap
data={analysisMode === 'evolution' && temporalComparison?.geojson.features.length ? temporalComparison.geojson : mapFeatureCollection}
data={explorerMapFeatureCollection}
dataFillColor={activeThemeMapStyle.fill}
dataLineColor={activeThemeMapStyle.line}
areaData={areaFeatureCollection}
selectedFeature={selectedFeature}
selectionData={analysisMode === 'current' ? mapSelectionResult?.geojson ?? null : null}
selectionData={explorerSelectionFeatureCollection}
imageOverlays={activeImageOverlays}
selectionBbox={mapSelectionBbox}
bboxSelectionMode={bboxSelectionMode}
@@ -1872,6 +1918,8 @@ export function MapWorkspace({
? `${mapLayerLabel} · ${mapLayerSourceLabel}`
: analysisMode === 'evolution'
? activeTemporalSeriesGroup?.label ?? 'geen vergelijkbare tijdreeks'
: regionalBathymetryThemeActive
? `VHA-dwarsprofielen Vlaanderen · ${activeThemePartitions.length} gemeentepartities`
: activeThemeDataset
? getDatasetDisplayName(activeThemeDataset)
: 'niet beschikbaar'}
@@ -89,6 +89,12 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
bbox,
area_id: areaId,
}))
: dataset.source_name === 'vmm_vha_bathymetry_profiles' && partitioned
? await datasetsApi.selectBathymetryProfilePartitions(selectedProjectId, {
bbox,
area_id: areaId,
limit: 1000,
})
: await datasetsApi.selectVectorFeatures(selectedProjectId, dataset.id, {
bbox,
area_id: areaId,
+8
View File
@@ -180,6 +180,14 @@ export const datasetsApi = {
),
acquireBathymetryProfiles: (projectId: string, payload: BathymetryProfileAcquireRequest): Promise<JobRead> =>
apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/bathymetry/profiles/acquire`, payload),
selectBathymetryProfilePartitions: (
projectId: string,
payload: VectorSelectionRequest,
): Promise<VectorSelectionResponse> =>
apiPost<VectorSelectionResponse>(
`/api/v1/projects/${projectId}/datasets/bathymetry/profiles/partitions/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 }> =>
+6
View File
@@ -575,6 +575,11 @@ export interface VectorSelectionResponse {
truncated: boolean
geojson: GeoJSON.FeatureCollection
summary?: VectorSelectionSummary | null
partition_count?: number | null
available_partition_count?: number | null
partition_scope_key?: string | null
source_name?: string | null
dataset_ids?: string[]
}
export interface VectorSelectionSummary {
@@ -1457,6 +1462,7 @@ export interface MapResultExportRequest {
area_id?: string
partitioned?: boolean
product_key?: string
partition_scope_key?: string
theme_id?: string
name?: string
}