Add bounded terrain and flood analysis
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 20:36:45 +02:00
parent 972c3f0e1b
commit 47cde21e17
12 changed files with 441 additions and 125 deletions
@@ -6,18 +6,26 @@ import { terrainSelectionToMapSelection } from '../lib/terrainSelection'
import { floodHazardSelectionToMapSelection } from '../lib/floodHazardSelection'
import { thematicRasterSelectionToMapSelection } from '../lib/thematicRaster'
export type MapThemeAcquisitionKind = 'thematic_raster' | 'dhmv' | 'flood_hazard'
export interface MapThemeAcquisition {
kind: MapThemeAcquisitionKind
productKey: string
displayName: string
}
export interface MapThemeQuery<TThemeId extends string> {
themeId: TThemeId
dataset?: DatasetCreateResponse
partitioned?: boolean
thematicProductKey?: string
acquisition?: MapThemeAcquisition
}
export interface MapThemeInsight<TThemeId extends string> {
themeId: TThemeId
dataset: DatasetCreateResponse
partitioned?: boolean
thematicProductKey?: string
acquisition?: MapThemeAcquisition
result: VectorSelectionResponse
}
@@ -61,22 +69,36 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
setThemeInsightsError(null)
try {
const settled = await Promise.allSettled(
queries.map(async ({ themeId, dataset: existingDataset, partitioned, thematicProductKey }) => {
queries.map(async ({ themeId, dataset: existingDataset, partitioned, acquisition }) => {
let dataset = existingDataset
if (thematicProductKey) {
const acquisition = await datasetsApi.acquireThematicRaster(selectedProjectId, {
bbox,
area_id: areaId,
product_key: thematicProductKey,
force_refresh: false,
})
if (acquisition.status !== 'success' || !acquisition.output_dataset_id) {
if (acquisition) {
const acquisitionJob = acquisition.kind === 'thematic_raster'
? await datasetsApi.acquireThematicRaster(selectedProjectId, {
bbox,
area_id: areaId,
product_key: acquisition.productKey,
force_refresh: false,
})
: acquisition.kind === 'dhmv'
? await datasetsApi.acquireDhmv(selectedProjectId, {
bbox,
area_id: areaId,
product_key: acquisition.productKey as 'dtm_1m' | 'dsm_1m',
force_refresh: false,
})
: await datasetsApi.acquireFloodHazard(selectedProjectId, {
bbox,
area_id: areaId,
product_key: acquisition.productKey,
force_refresh: false,
})
if (acquisitionJob.status !== 'success' || !acquisitionJob.output_dataset_id) {
throw new Error(
acquisition.error_message
|| `De officiële rasterbron ${thematicProductKey} kon niet worden ingeladen.`,
acquisitionJob.error_message
|| `De officiële rasterbron ${acquisition.displayName} kon niet worden ingeladen.`,
)
}
dataset = await datasetsApi.get(selectedProjectId, acquisition.output_dataset_id)
dataset = await datasetsApi.get(selectedProjectId, acquisitionJob.output_dataset_id)
}
if (!dataset) {
throw new Error(`Geen persistente databron beschikbaar voor thema ${themeId}.`)
@@ -85,7 +107,7 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
themeId,
dataset,
partitioned,
thematicProductKey,
acquisition,
result: dataset.dataset_type === 'raster' && dataset.source_name === 'digitaal_vlaanderen_dhmv'
? terrainSelectionToMapSelection(
partitioned
@@ -137,7 +159,7 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
? [{
dataset:
queries[index]?.dataset?.name
?? queries[index]?.thematicProductKey
?? queries[index]?.acquisition?.displayName
?? queries[index]?.themeId
?? 'Onbekende bron',
reason: formatError(item.reason, 'Bron kon niet worden bevraagd.'),
@@ -150,7 +172,7 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
}
setThemeInsights(successful)
let refreshFailure: string | null = null
if (successful.some((item) => item.thematicProductKey) && onDatasetsChanged) {
if (successful.some((item) => item.acquisition) && onDatasetsChanged) {
try {
await onDatasetsChanged()
} catch (error) {
@@ -0,0 +1,72 @@
import { useEffect, useState } from 'react'
import { datasetsApi } from '../services/api'
import { formatError } from '../lib/formatError'
import type {
DhmvProductRead,
FloodHazardProductRead,
ThematicRasterProductRead,
} from '../types'
interface OfficialRasterProducts {
thematic: ThematicRasterProductRead[]
dhmv: DhmvProductRead[]
floodHazard: FloodHazardProductRead[]
}
const EMPTY_PRODUCTS: OfficialRasterProducts = {
thematic: [],
dhmv: [],
floodHazard: [],
}
export function useOfficialRasterProducts(selectedProjectId: string | null) {
const [products, setProducts] = useState<OfficialRasterProducts>(EMPTY_PRODUCTS)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
let cancelled = false
if (!selectedProjectId) {
setProducts(EMPTY_PRODUCTS)
setLoading(false)
setError(null)
return () => {
cancelled = true
}
}
setLoading(true)
setError(null)
void Promise.all([
datasetsApi.listThematicRasterProducts(selectedProjectId),
datasetsApi.listDhmvProducts(selectedProjectId),
datasetsApi.listFloodHazardProducts(selectedProjectId),
])
.then(([thematic, dhmv, floodHazard]) => {
if (!cancelled) {
setProducts({
thematic: thematic.items,
dhmv: dhmv.items,
floodHazard: floodHazard.items,
})
}
})
.catch((requestError) => {
if (!cancelled) {
setProducts(EMPTY_PRODUCTS)
setError(formatError(requestError, 'De officiële Vlaamse rastercatalogi konden niet worden geladen.'))
}
})
.finally(() => {
if (!cancelled) {
setLoading(false)
}
})
return () => {
cancelled = true
}
}, [selectedProjectId])
return { products, loading, error }
}
@@ -1,48 +0,0 @@
import { useEffect, useState } from 'react'
import { datasetsApi } from '../services/api'
import { formatError } from '../lib/formatError'
import type { ThematicRasterProductRead } from '../types'
export function useThematicRasterProducts(selectedProjectId: string | null) {
const [products, setProducts] = useState<ThematicRasterProductRead[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
let cancelled = false
if (!selectedProjectId) {
setProducts([])
setLoading(false)
setError(null)
return () => {
cancelled = true
}
}
setLoading(true)
setError(null)
void datasetsApi.listThematicRasterProducts(selectedProjectId)
.then((response) => {
if (!cancelled) {
setProducts(response.items)
}
})
.catch((requestError) => {
if (!cancelled) {
setProducts([])
setError(formatError(requestError, 'De Vlaamse beleidsrasters konden niet worden geladen.'))
}
})
.finally(() => {
if (!cancelled) {
setLoading(false)
}
})
return () => {
cancelled = true
}
}, [selectedProjectId])
return { products, loading, error }
}