fix(map): make area analysis scale-aware
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-21 21:59:56 +02:00
parent 80631607f7
commit 20829f1a24
20 changed files with 619 additions and 56 deletions
@@ -26,6 +26,7 @@ export interface MapThemeQuery<TThemeId extends string> {
dataset?: DatasetCreateResponse
partitioned?: boolean
acquisition?: MapThemeAcquisition
acquisitionBboxes?: VectorSelectionBBox[]
}
export interface MapThemeInsight<TThemeId extends string> {
@@ -103,51 +104,63 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
const settled = await settleWithConcurrency(
queries,
3,
async ({ themeId, dataset: existingDataset, partitioned, acquisition }) => {
async ({ themeId, dataset: existingDataset, partitioned, acquisition, acquisitionBboxes }) => {
let dataset = existingDataset
let acquiredDatasets: DatasetCreateResponse[] = []
if (acquisition) {
const commonPayload = {
bbox,
area_id: areaId,
force_refresh: false,
}
const acquisitionJob = acquisition.kind === 'thematic_raster'
? await datasetsApi.acquireThematicRaster(selectedProjectId, {
...commonPayload,
product_key: acquisition.productKey,
})
: acquisition.kind === 'dhmv'
? await datasetsApi.acquireDhmv(selectedProjectId, {
const requestedBboxes = acquisitionBboxes?.length ? acquisitionBboxes : [bbox]
const acquisitionResults = await settleWithConcurrency(requestedBboxes, 1, async (acquisitionBbox) => {
const commonPayload = {
bbox: acquisitionBbox,
area_id: areaId,
force_refresh: false,
}
const acquisitionJob = acquisition.kind === 'thematic_raster'
? await datasetsApi.acquireThematicRaster(selectedProjectId, {
...commonPayload,
product_key: acquisition.productKey as 'dtm_1m' | 'dsm_1m',
product_key: acquisition.productKey,
})
: acquisition.kind === 'flood_hazard'
? await datasetsApi.acquireFloodHazard(selectedProjectId, {
: acquisition.kind === 'dhmv'
? await datasetsApi.acquireDhmv(selectedProjectId, {
...commonPayload,
product_key: acquisition.productKey,
product_key: acquisition.productKey as 'dtm_1m' | 'dsm_1m',
})
: acquisition.kind === 'grb'
? await datasetsApi.acquireGrb(selectedProjectId, {
: acquisition.kind === 'flood_hazard'
? await datasetsApi.acquireFloodHazard(selectedProjectId, {
...commonPayload,
product_key: acquisition.productKey as 'buildings' | 'roads' | 'water' | 'parcels',
product_key: acquisition.productKey,
})
: acquisition.kind === 'bathymetry_profiles'
? await datasetsApi.acquireBathymetryProfiles(selectedProjectId, commonPayload)
: await datasetsApi.acquireOfficialVector(selectedProjectId, {
: acquisition.kind === 'grb'
? await datasetsApi.acquireGrb(selectedProjectId, {
...commonPayload,
product_key: acquisition.productKey,
product_key: acquisition.productKey as 'buildings' | 'roads' | 'water' | 'parcels',
})
if (acquisitionJob.status !== 'success' || !acquisitionJob.output_dataset_id) {
throw new Error(
acquisitionJob.error_message
|| `De officiële kaartbron ${acquisition.displayName} kon niet worden ingeladen.`,
)
: acquisition.kind === 'bathymetry_profiles'
? await datasetsApi.acquireBathymetryProfiles(selectedProjectId, commonPayload)
: await datasetsApi.acquireOfficialVector(selectedProjectId, {
...commonPayload,
product_key: acquisition.productKey,
})
if (acquisitionJob.status !== 'success' || !acquisitionJob.output_dataset_id) {
throw new Error(
acquisitionJob.error_message
|| `De officiële kaartbron ${acquisition.displayName} kon niet worden ingeladen.`,
)
}
return datasetsApi.get(selectedProjectId, acquisitionJob.output_dataset_id)
})
const failedAcquisition = acquisitionResults.find((item) => item.status === 'rejected')
if (failedAcquisition?.status === 'rejected') {
throw failedAcquisition.reason
}
dataset = await datasetsApi.get(selectedProjectId, acquisitionJob.output_dataset_id)
acquiredDatasets = acquisitionResults.flatMap((item) => item.status === 'fulfilled' ? [item.value] : [])
dataset = acquiredDatasets[0]
}
if (!dataset) {
throw new Error(`Geen persistente databron beschikbaar voor thema ${themeId}.`)
}
const acquiredDatasetIds = acquiredDatasets.map((item) => item.id)
const acquiredAsPartitions = acquiredDatasetIds.length > 1
return {
themeId,
dataset,
@@ -155,11 +168,12 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
acquisition,
result: dataset.dataset_type === 'raster' && dataset.source_name === 'digitaal_vlaanderen_dhmv'
? terrainSelectionToMapSelection(
partitioned
partitioned || acquiredAsPartitions
? await datasetsApi.selectTerrainPartitions(selectedProjectId, {
bbox,
area_id: areaId,
product_key: String(dataset.source_metadata?.['product_key'] ?? 'dtm_1m'),
...(acquiredAsPartitions ? { dataset_ids: acquiredDatasetIds } : {}),
})
: await datasetsApi.selectTerrain(selectedProjectId, dataset.id, {
bbox,
@@ -168,11 +182,12 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
)
: dataset.dataset_type === 'raster' && dataset.source_name === 'vmm_flood_hazard'
? floodHazardSelectionToMapSelection(
partitioned
partitioned || acquiredAsPartitions
? await datasetsApi.selectFloodHazardPartitions(selectedProjectId, {
bbox,
area_id: areaId,
product_key: String(dataset.source_metadata?.['product_key'] ?? 'pluviaal_current_t100'),
...(acquiredAsPartitions ? { dataset_ids: acquiredDatasetIds } : {}),
})
: await datasetsApi.selectFloodHazard(selectedProjectId, dataset.id, {
bbox,
@@ -189,6 +204,13 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
bbox,
area_id: areaId,
}))
: acquiredAsPartitions && dataset.dataset_type !== 'raster'
? await datasetsApi.selectVectorFeaturePartitions(selectedProjectId, {
dataset_ids: acquiredDatasetIds,
bbox,
area_id: areaId,
limit: 1000,
})
: dataset.source_name === 'vmm_vha_bathymetry_profiles' && partitioned
? await datasetsApi.selectBathymetryProfilePartitions(selectedProjectId, {
bbox,
+45 -1
View File
@@ -3,11 +3,13 @@ import { datasetsApi, externalApi } from '../services/api'
import { formatError } from '../lib/formatError'
import type {
BathymetrySourceRead,
CoverageResolveResponse,
DhmvProductRead,
FloodHazardProductRead,
GrbProductRead,
OfficialVectorProductRead,
ThematicRasterProductRead,
VectorSelectionBBox,
} from '../types'
export interface OfficialMapProducts {
@@ -104,5 +106,47 @@ export function useOfficialMapProducts(selectedProjectId: string | null) {
[selectedProjectId],
)
return { products, loading, error, resolveCoverage }
const resolveCoveragePartitions = useCallback(
async (bboxes: VectorSelectionBBox[]): Promise<Array<{
bbox: VectorSelectionBBox
coverage: CoverageResolveResponse
}> | null> => {
if (!selectedProjectId) {
setError('Selecteer eerst een werkruimte.')
return null
}
try {
const resolved: Array<{
bbox: VectorSelectionBBox
coverage: CoverageResolveResponse
}> = []
for (let offset = 0; offset < bboxes.length; offset += 4) {
const batch = bboxes.slice(offset, offset + 4)
resolved.push(...await Promise.all(batch.map(async (bbox) => ({
bbox,
coverage: await externalApi.resolveCoverage({
projectId: selectedProjectId,
bbox: {
minx: bbox.min_x,
miny: bbox.min_y,
maxx: bbox.max_x,
maxy: bbox.max_y,
},
}),
}))))
}
setError(null)
return resolved
} catch (requestError) {
setError(formatError(
requestError,
'De regionale dekking kon niet voor alle bronpartities worden bepaald.',
))
return null
}
},
[selectedProjectId],
)
return { products, loading, error, resolveCoverage, resolveCoveragePartitions }
}