226 lines
8.7 KiB
TypeScript
226 lines
8.7 KiB
TypeScript
import { useEffect, useRef, useState } from 'react'
|
|
import { formatError } from '../lib/formatError'
|
|
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 type MapThemeAcquisitionKind = 'thematic_raster' | 'dhmv' | 'flood_hazard' | 'grb' | 'official_vector'
|
|
|
|
export interface MapThemeAcquisition {
|
|
kind: MapThemeAcquisitionKind
|
|
productKey: string
|
|
displayName: string
|
|
}
|
|
|
|
export interface MapThemeQuery<TThemeId extends string> {
|
|
themeId: TThemeId
|
|
dataset?: DatasetCreateResponse
|
|
partitioned?: boolean
|
|
acquisition?: MapThemeAcquisition
|
|
}
|
|
|
|
export interface MapThemeInsight<TThemeId extends string> {
|
|
themeId: TThemeId
|
|
dataset: DatasetCreateResponse
|
|
partitioned?: boolean
|
|
acquisition?: MapThemeAcquisition
|
|
result: VectorSelectionResponse
|
|
}
|
|
|
|
export function useMapThemeSelectionInsights<TThemeId extends string>(
|
|
selectedProjectId: string | null,
|
|
onDatasetsChanged?: () => Promise<unknown>,
|
|
) {
|
|
const [themeInsights, setThemeInsights] = useState<Array<MapThemeInsight<TThemeId>>>([])
|
|
const [themeInsightsLoading, setThemeInsightsLoading] = useState(false)
|
|
const [themeInsightsError, setThemeInsightsError] = useState<string | null>(null)
|
|
const requestSequence = useRef(0)
|
|
|
|
useEffect(() => {
|
|
requestSequence.current += 1
|
|
setThemeInsights([])
|
|
setThemeInsightsError(null)
|
|
setThemeInsightsLoading(false)
|
|
}, [selectedProjectId])
|
|
|
|
const clearThemeInsights = () => {
|
|
requestSequence.current += 1
|
|
setThemeInsights([])
|
|
setThemeInsightsError(null)
|
|
setThemeInsightsLoading(false)
|
|
}
|
|
|
|
const loadThemeInsights = async (
|
|
bbox: VectorSelectionBBox,
|
|
queries: Array<MapThemeQuery<TThemeId>>,
|
|
areaId?: string,
|
|
): Promise<Array<MapThemeInsight<TThemeId>>> => {
|
|
if (!selectedProjectId) {
|
|
setThemeInsights([])
|
|
setThemeInsightsError('Open eerst een project om de selectie te analyseren.')
|
|
return []
|
|
}
|
|
|
|
const sequence = requestSequence.current + 1
|
|
requestSequence.current = sequence
|
|
setThemeInsightsLoading(true)
|
|
setThemeInsightsError(null)
|
|
try {
|
|
const settled = await Promise.allSettled(
|
|
queries.map(async ({ themeId, dataset: existingDataset, partitioned, acquisition }) => {
|
|
let dataset = existingDataset
|
|
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, {
|
|
...commonPayload,
|
|
product_key: acquisition.productKey as 'dtm_1m' | 'dsm_1m',
|
|
})
|
|
: acquisition.kind === 'flood_hazard'
|
|
? await datasetsApi.acquireFloodHazard(selectedProjectId, {
|
|
...commonPayload,
|
|
product_key: acquisition.productKey,
|
|
})
|
|
: acquisition.kind === 'grb'
|
|
? await datasetsApi.acquireGrb(selectedProjectId, {
|
|
...commonPayload,
|
|
product_key: acquisition.productKey as 'buildings' | 'roads' | 'water' | 'parcels',
|
|
})
|
|
: 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.`,
|
|
)
|
|
}
|
|
dataset = await datasetsApi.get(selectedProjectId, acquisitionJob.output_dataset_id)
|
|
}
|
|
if (!dataset) {
|
|
throw new Error(`Geen persistente databron beschikbaar voor thema ${themeId}.`)
|
|
}
|
|
return {
|
|
themeId,
|
|
dataset,
|
|
partitioned,
|
|
acquisition,
|
|
result: dataset.dataset_type === 'raster' && dataset.source_name === 'digitaal_vlaanderen_dhmv'
|
|
? terrainSelectionToMapSelection(
|
|
partitioned
|
|
? await datasetsApi.selectTerrainPartitions(selectedProjectId, {
|
|
bbox,
|
|
area_id: areaId,
|
|
product_key: String(dataset.source_metadata?.['product_key'] ?? 'dtm_1m'),
|
|
})
|
|
: await datasetsApi.selectTerrain(selectedProjectId, dataset.id, {
|
|
bbox,
|
|
area_id: areaId,
|
|
}),
|
|
)
|
|
: dataset.dataset_type === 'raster' && dataset.source_name === 'vmm_flood_hazard'
|
|
? floodHazardSelectionToMapSelection(
|
|
partitioned
|
|
? await datasetsApi.selectFloodHazardPartitions(selectedProjectId, {
|
|
bbox,
|
|
area_id: areaId,
|
|
product_key: String(dataset.source_metadata?.['product_key'] ?? 'pluviaal_current_t100'),
|
|
})
|
|
: await datasetsApi.selectFloodHazard(selectedProjectId, dataset.id, {
|
|
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,
|
|
}))
|
|
: 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,
|
|
limit: 1000,
|
|
}),
|
|
}
|
|
}),
|
|
)
|
|
const successful = settled.flatMap((item) => (item.status === 'fulfilled' ? [item.value] : []))
|
|
const failures = settled.flatMap((item, index) => (
|
|
item.status === 'rejected'
|
|
? [{
|
|
dataset:
|
|
queries[index]?.dataset?.name
|
|
?? queries[index]?.acquisition?.displayName
|
|
?? queries[index]?.themeId
|
|
?? 'Onbekende bron',
|
|
reason: formatError(item.reason, 'Bron kon niet worden bevraagd.'),
|
|
}]
|
|
: []
|
|
))
|
|
const failureCount = failures.length
|
|
if (requestSequence.current !== sequence) {
|
|
return []
|
|
}
|
|
setThemeInsights(successful)
|
|
let refreshFailure: string | null = null
|
|
if (successful.some((item) => item.acquisition) && onDatasetsChanged) {
|
|
try {
|
|
await onDatasetsChanged()
|
|
} catch (error) {
|
|
refreshFailure = formatError(error, 'De datasetlijst kon na de analyse niet worden vernieuwd.')
|
|
}
|
|
}
|
|
if (failureCount > 0) {
|
|
const details = failures
|
|
.slice(0, 4)
|
|
.map((failure) => `${failure.dataset}: ${failure.reason}`)
|
|
.join(' · ')
|
|
const remainder = failureCount > 4 ? ` · en ${failureCount - 4} andere` : ''
|
|
setThemeInsightsError(
|
|
`${failureCount} beschikbare databron${failureCount === 1 ? '' : 'nen'} kon niet worden bevraagd. ${details}${remainder}`,
|
|
)
|
|
} else if (refreshFailure) {
|
|
setThemeInsightsError(refreshFailure)
|
|
}
|
|
return successful
|
|
} catch (error) {
|
|
if (requestSequence.current !== sequence) {
|
|
return []
|
|
}
|
|
setThemeInsights([])
|
|
setThemeInsightsError(formatError(error, 'De gebiedsanalyse is mislukt.'))
|
|
return []
|
|
} finally {
|
|
if (requestSequence.current === sequence) {
|
|
setThemeInsightsLoading(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
themeInsights,
|
|
themeInsightsLoading,
|
|
themeInsightsError,
|
|
loadThemeInsights,
|
|
clearThemeInsights,
|
|
}
|
|
}
|