Complete regional raster exploration
This commit is contained in:
+8
-6
@@ -519,12 +519,14 @@ GeoJSON export stays disabled. The UI never labels the maximum-depth area
|
||||
integral as current, permanent or concurrent water volume.
|
||||
|
||||
Regional VMM provisioning creates one scenario raster per municipality Area.
|
||||
The explorer therefore shows only the flood scenarios whose `area_id` matches
|
||||
the active work area. This avoids presenting a Mol scenario while the map is
|
||||
focused on another municipality. The region-wide Area remains the navigation
|
||||
context; municipality Areas are the analysis scope for flood rasters because
|
||||
the public WCS and raster cell limits make one monolithic Kempen raster
|
||||
operationally unsafe.
|
||||
For a municipality the explorer still uses only that exact Area-linked file.
|
||||
For the complete Kempen Area it presents the 28 VMM and DHMV partitions as one
|
||||
logical map layer, deduplicates VMM into twelve scenario choices and renders
|
||||
every matching MapLibre image partition. A drawn rectangle is sent to the
|
||||
partition endpoint, which opens only intersecting files and calculates exact
|
||||
combined cell statistics. A monolithic full-region 5 m calculation remains
|
||||
disabled because it exceeds the governed raster-cell limit; users draw a
|
||||
bounded rectangle without first choosing a municipality.
|
||||
|
||||
## Useful repository scripts
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ interface GeoMapProps {
|
||||
selectedFeature?: GeoJSON.Feature | null
|
||||
selectionData?: GeoJSON.FeatureCollection | null
|
||||
qaEvidenceData?: GeoJSON.FeatureCollection | null
|
||||
imageOverlay?: MapImageOverlay | null
|
||||
imageOverlays?: MapImageOverlay[]
|
||||
selectionBbox?: { min_x: number; min_y: number; max_x: number; max_y: number } | null
|
||||
bboxSelectionMode?: boolean
|
||||
visible?: boolean
|
||||
@@ -154,7 +154,7 @@ function GeoMap({
|
||||
selectedFeature = null,
|
||||
selectionData = null,
|
||||
qaEvidenceData = null,
|
||||
imageOverlay = null,
|
||||
imageOverlays = [],
|
||||
selectionBbox = null,
|
||||
bboxSelectionMode = false,
|
||||
visible = true,
|
||||
@@ -180,6 +180,7 @@ function GeoMap({
|
||||
const dataRef = useRef<GeoJSON.FeatureCollection | null>(data)
|
||||
const fitDataOnChangeRef = useRef(fitDataOnChange)
|
||||
const lastFittedAreaRef = useRef<GeoJSON.FeatureCollection | null>(null)
|
||||
const imageOverlayIdsRef = useRef<string[]>([])
|
||||
const [mapStyleReady, setMapStyleReady] = useState(false)
|
||||
|
||||
areaDataRef.current = areaData
|
||||
@@ -353,37 +354,38 @@ function GeoMap({
|
||||
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
|
||||
return
|
||||
}
|
||||
if (map.getLayer('bounded-orthophoto')) {
|
||||
map.removeLayer('bounded-orthophoto')
|
||||
for (const overlayId of [...imageOverlayIdsRef.current].reverse()) {
|
||||
if (map.getLayer(overlayId)) {
|
||||
map.removeLayer(overlayId)
|
||||
}
|
||||
if (map.getSource(overlayId)) {
|
||||
map.removeSource(overlayId)
|
||||
}
|
||||
}
|
||||
if (map.getSource('bounded-orthophoto')) {
|
||||
map.removeSource('bounded-orthophoto')
|
||||
}
|
||||
if (!imageOverlay) {
|
||||
return
|
||||
}
|
||||
const [minX, minY, maxX, maxY] = imageOverlay.bbox
|
||||
map.addSource('bounded-orthophoto', {
|
||||
type: 'image',
|
||||
url: imageOverlay.url,
|
||||
coordinates: [
|
||||
[minX, maxY],
|
||||
[maxX, maxY],
|
||||
[maxX, minY],
|
||||
[minX, minY],
|
||||
],
|
||||
})
|
||||
imageOverlayIdsRef.current = []
|
||||
const beforeLayer = ['area-fill', 'dataset-fill', 'selection-bbox-fill'].find((layerId) => map.getLayer(layerId))
|
||||
map.addLayer(
|
||||
{
|
||||
id: 'bounded-orthophoto',
|
||||
imageOverlays.forEach((imageOverlay, index) => {
|
||||
const overlayId = `bounded-raster-${index}`
|
||||
const [minX, minY, maxX, maxY] = imageOverlay.bbox
|
||||
map.addSource(overlayId, {
|
||||
type: 'image',
|
||||
url: imageOverlay.url,
|
||||
coordinates: [
|
||||
[minX, maxY],
|
||||
[maxX, maxY],
|
||||
[maxX, minY],
|
||||
[minX, minY],
|
||||
],
|
||||
})
|
||||
map.addLayer({
|
||||
id: overlayId,
|
||||
type: 'raster',
|
||||
source: 'bounded-orthophoto',
|
||||
source: overlayId,
|
||||
paint: { 'raster-opacity': imageOverlay.opacity ?? 0.88 },
|
||||
},
|
||||
beforeLayer,
|
||||
)
|
||||
}, [imageOverlay, mapStyleReady])
|
||||
}, beforeLayer)
|
||||
imageOverlayIdsRef.current.push(overlayId)
|
||||
})
|
||||
}, [imageOverlays, mapStyleReady])
|
||||
|
||||
useEffect(() => {
|
||||
const map = mapRef.current
|
||||
|
||||
@@ -158,14 +158,15 @@ const DATA_THEME_MAP_STYLES: Record<DataThemeId, { fill: string; line: string }>
|
||||
parcels: { fill: '#a7792f', line: '#7d571f' },
|
||||
}
|
||||
|
||||
function datasetAvailabilityLabel(dataset: DatasetCreateResponse): string {
|
||||
function datasetAvailabilityLabel(dataset: DatasetCreateResponse, partitionCount = 1): string {
|
||||
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'])
|
||||
return `${Number.isFinite(resolution) ? `${resolution.toLocaleString('nl-BE')} m` : 'Raster'} hoogtegrid beschikbaar`
|
||||
return `${Number.isFinite(resolution) ? `${resolution.toLocaleString('nl-BE')} m` : 'Raster'} hoogtegrid${regionalSuffix}`
|
||||
}
|
||||
if (dataset.dataset_type === 'raster' && dataset.source_name === 'vmm_flood_hazard') {
|
||||
const resolution = Number(dataset.source_metadata?.['analysis_resolution_m'])
|
||||
return `${Number.isFinite(resolution) ? `${resolution.toLocaleString('nl-BE')} m` : 'Raster'} overstromingsscenario`
|
||||
return `${Number.isFinite(resolution) ? `${resolution.toLocaleString('nl-BE')} m` : 'Raster'} overstromingsscenario${regionalSuffix}`
|
||||
}
|
||||
if (dataset.dataset_type === 'raster' && dataset.source_name === 'department_omgeving_thematic_raster') {
|
||||
const resolution = Number(dataset.source_metadata?.['analysis_resolution_m'])
|
||||
@@ -213,21 +214,69 @@ function datasetMatchesTheme(dataset: DatasetCreateResponse, theme: DataTheme):
|
||||
return theme.tokens.some((token) => searchText.includes(token))
|
||||
}
|
||||
|
||||
function datasetCoversSelectedArea(dataset: DatasetCreateResponse, selectedAreaId: string | null): boolean {
|
||||
function isMunicipalityAreaName(name: string | null | undefined): boolean {
|
||||
return /^Gemeente\s/i.test(name ?? '')
|
||||
}
|
||||
|
||||
function isPartitionedRaster(dataset: DatasetCreateResponse | null | undefined): boolean {
|
||||
return Boolean(
|
||||
dataset?.dataset_type === 'raster'
|
||||
&& ['digitaal_vlaanderen_dhmv', 'vmm_flood_hazard'].includes(dataset.source_name ?? ''),
|
||||
)
|
||||
}
|
||||
|
||||
function datasetProductKey(dataset: DatasetCreateResponse): string {
|
||||
return String(dataset.source_metadata?.['product_key'] ?? '')
|
||||
}
|
||||
|
||||
function datasetCoversSelectedArea(
|
||||
dataset: DatasetCreateResponse,
|
||||
selectedAreaId: string | null,
|
||||
regionalScope = false,
|
||||
): boolean {
|
||||
const coverageScope = String(dataset.source_metadata?.['coverage_scope'] ?? '')
|
||||
if (coverageScope !== 'municipality' || !dataset.area_id) {
|
||||
return true
|
||||
}
|
||||
if (regionalScope) {
|
||||
return true
|
||||
}
|
||||
return Boolean(selectedAreaId) && dataset.area_id === selectedAreaId
|
||||
}
|
||||
|
||||
function rasterPartitionsForDataset(
|
||||
datasets: DatasetCreateResponse[],
|
||||
representative: DatasetCreateResponse | null,
|
||||
selectedAreaId: string | null,
|
||||
regionalScope: boolean,
|
||||
): DatasetCreateResponse[] {
|
||||
if (!representative) {
|
||||
return []
|
||||
}
|
||||
if (!regionalScope || !isPartitionedRaster(representative)) {
|
||||
return [representative]
|
||||
}
|
||||
const productKey = datasetProductKey(representative)
|
||||
return datasets
|
||||
.filter(
|
||||
(dataset) =>
|
||||
dataset.source_name === representative.source_name
|
||||
&& datasetProductKey(dataset) === productKey
|
||||
&& datasetCoversSelectedArea(dataset, selectedAreaId, true),
|
||||
)
|
||||
.sort((left, right) => String(left.area_id ?? '').localeCompare(String(right.area_id ?? '')))
|
||||
}
|
||||
|
||||
function pickThemeDataset(
|
||||
datasets: DatasetCreateResponse[],
|
||||
theme: DataTheme,
|
||||
selectedAreaId: string | null,
|
||||
regionalScope = false,
|
||||
): DatasetCreateResponse | null {
|
||||
const candidates = datasets.filter(
|
||||
(dataset) => datasetMatchesTheme(dataset, theme) && datasetCoversSelectedArea(dataset, selectedAreaId),
|
||||
(dataset) =>
|
||||
datasetMatchesTheme(dataset, theme)
|
||||
&& datasetCoversSelectedArea(dataset, selectedAreaId, regionalScope),
|
||||
)
|
||||
candidates.sort((left, right) => {
|
||||
const score = (dataset: DatasetCreateResponse) =>
|
||||
@@ -745,6 +794,7 @@ export function MapWorkspace({
|
||||
const [fullWorkflowError, setFullWorkflowError] = useState<string | null>(null)
|
||||
const [fullWorkflowMode, setFullWorkflowMode] = useState<'new' | 'reuse'>('new')
|
||||
const selectedMapArea = areas.find((area) => area.id === selectedMapAreaId)
|
||||
const regionalScopeSelected = Boolean(selectedMapArea && !isMunicipalityAreaName(selectedMapArea.name))
|
||||
const featureProperties = selectedMapFeature?.properties ?? null
|
||||
const featureSummaryEntries = featureProperties
|
||||
? Object.entries(featureProperties)
|
||||
@@ -767,70 +817,138 @@ export function MapWorkspace({
|
||||
const selectedMapDataset = availableMapDatasets.find((dataset) => dataset.id === selectedMapDatasetId) ?? null
|
||||
const usesDefaultOsmBasemap = !import.meta.env.VITE_MAP_STYLE_URL
|
||||
const floodHazardDatasets = useMemo(
|
||||
() => availableMapDatasets
|
||||
.filter((dataset) => dataset.source_name === 'vmm_flood_hazard' && datasetCoversSelectedArea(dataset, selectedMapAreaId))
|
||||
.sort((left, right) => floodScenarioLabel(left).localeCompare(floodScenarioLabel(right), 'nl')),
|
||||
[availableMapDatasets, selectedMapAreaId],
|
||||
() => {
|
||||
const scoped = availableMapDatasets
|
||||
.filter(
|
||||
(dataset) =>
|
||||
dataset.source_name === 'vmm_flood_hazard'
|
||||
&& datasetCoversSelectedArea(dataset, selectedMapAreaId, regionalScopeSelected),
|
||||
)
|
||||
.sort((left, right) => floodScenarioLabel(left).localeCompare(floodScenarioLabel(right), 'nl'))
|
||||
if (!regionalScopeSelected) {
|
||||
return scoped
|
||||
}
|
||||
const products = new Map<string, DatasetCreateResponse>()
|
||||
for (const dataset of scoped) {
|
||||
const key = datasetProductKey(dataset)
|
||||
if (key && !products.has(key)) {
|
||||
products.set(key, dataset)
|
||||
}
|
||||
}
|
||||
return Array.from(products.values())
|
||||
},
|
||||
[availableMapDatasets, regionalScopeSelected, selectedMapAreaId],
|
||||
)
|
||||
const themeDatasetMap = useMemo(() => {
|
||||
const result = Object.fromEntries(
|
||||
DATA_THEMES.map((theme) => [theme.id, pickThemeDataset(availableMapDatasets, theme, selectedMapAreaId)]),
|
||||
DATA_THEMES.map((theme) => [
|
||||
theme.id,
|
||||
pickThemeDataset(availableMapDatasets, theme, selectedMapAreaId, regionalScopeSelected),
|
||||
]),
|
||||
) as Record<DataThemeId, DatasetCreateResponse | null>
|
||||
const selectedFloodHazard = floodHazardDatasets.find((dataset) => dataset.id === selectedFloodHazardDatasetId)
|
||||
if (selectedFloodHazard) {
|
||||
result.flood_hazard = selectedFloodHazard
|
||||
}
|
||||
return result
|
||||
}, [availableMapDatasets, floodHazardDatasets, selectedFloodHazardDatasetId, selectedMapAreaId])
|
||||
}, [availableMapDatasets, floodHazardDatasets, regionalScopeSelected, selectedFloodHazardDatasetId, selectedMapAreaId])
|
||||
const themePartitionMap = useMemo(
|
||||
() =>
|
||||
Object.fromEntries(
|
||||
DATA_THEMES.map((theme) => [
|
||||
theme.id,
|
||||
rasterPartitionsForDataset(
|
||||
availableMapDatasets,
|
||||
themeDatasetMap[theme.id],
|
||||
selectedMapAreaId,
|
||||
regionalScopeSelected,
|
||||
),
|
||||
]),
|
||||
) as Record<DataThemeId, DatasetCreateResponse[]>,
|
||||
[availableMapDatasets, regionalScopeSelected, selectedMapAreaId, themeDatasetMap],
|
||||
)
|
||||
const activeTheme = DATA_THEMES.find((theme) => theme.id === activeThemeId) ?? DATA_THEMES[0]
|
||||
const activeThemeMapStyle = DATA_THEME_MAP_STYLES[activeTheme.id]
|
||||
const analysisOverlayActive = mapContentMode === 'analysis' && analysisLayerAvailable && Boolean(mapFeatureCollection)
|
||||
const selectedOrthophotoProduct = orthophotoProducts.find((item) => item.key === selectedOrthophotoProductKey) ?? null
|
||||
const orthophotoImageOverlay = orthophotoResult && orthophotoImageUrl && orthophotoResult.bbox_epsg4326.length === 4
|
||||
? {
|
||||
url: orthophotoImageUrl,
|
||||
bbox: orthophotoResult.bbox_epsg4326 as [number, number, number, number],
|
||||
label: orthophotoResult.display_name,
|
||||
opacity: 0.9,
|
||||
}
|
||||
: null
|
||||
const orthophotoImageOverlay = useMemo(
|
||||
() => orthophotoResult && orthophotoImageUrl && orthophotoResult.bbox_epsg4326.length === 4
|
||||
? {
|
||||
url: orthophotoImageUrl,
|
||||
bbox: orthophotoResult.bbox_epsg4326 as [number, number, number, number],
|
||||
label: orthophotoResult.display_name,
|
||||
opacity: 0.9,
|
||||
}
|
||||
: null,
|
||||
[orthophotoImageUrl, orthophotoResult],
|
||||
)
|
||||
const activeThemeDataset = themeDatasetMap[activeTheme.id]
|
||||
const terrainBounds = activeThemeDataset?.source_name === 'digitaal_vlaanderen_dhmv'
|
||||
? activeThemeDataset.source_metadata?.['bbox_epsg4326']
|
||||
: null
|
||||
const terrainImageOverlay = activeTheme.id === 'elevation' && activeThemeDataset && selectedProjectId && Array.isArray(terrainBounds) && terrainBounds.length === 4
|
||||
? {
|
||||
url: terrainImageUrl(selectedProjectId, activeThemeDataset.id),
|
||||
bbox: terrainBounds.map(Number) as [number, number, number, number],
|
||||
label: getDatasetDisplayName(activeThemeDataset),
|
||||
opacity: 0.82,
|
||||
}
|
||||
: null
|
||||
const floodHazardBounds = activeThemeDataset?.source_name === 'vmm_flood_hazard'
|
||||
? activeThemeDataset.source_metadata?.['bbox_epsg4326']
|
||||
: null
|
||||
const floodHazardImageOverlay = activeTheme.id === 'flood_hazard' && activeThemeDataset && selectedProjectId && Array.isArray(floodHazardBounds) && floodHazardBounds.length === 4
|
||||
? {
|
||||
url: floodHazardImageUrl(selectedProjectId, activeThemeDataset.id),
|
||||
bbox: floodHazardBounds.map(Number) as [number, number, number, number],
|
||||
label: floodScenarioLabel(activeThemeDataset),
|
||||
opacity: 0.82,
|
||||
}
|
||||
: null
|
||||
const activeThemePartitions = themePartitionMap[activeTheme.id]
|
||||
const regionalRasterThemeActive = regionalScopeSelected && isPartitionedRaster(activeThemeDataset)
|
||||
const terrainImageOverlays = useMemo(
|
||||
() =>
|
||||
activeTheme.id === 'elevation' && selectedProjectId
|
||||
? activeThemePartitions.flatMap((dataset) => {
|
||||
const bounds = dataset.source_metadata?.['bbox_epsg4326']
|
||||
return dataset.source_name === 'digitaal_vlaanderen_dhmv'
|
||||
&& Array.isArray(bounds)
|
||||
&& bounds.length === 4
|
||||
? [{
|
||||
url: terrainImageUrl(selectedProjectId, dataset.id),
|
||||
bbox: bounds.map(Number) as [number, number, number, number],
|
||||
label: getDatasetDisplayName(dataset),
|
||||
opacity: 0.82,
|
||||
}]
|
||||
: []
|
||||
})
|
||||
: [],
|
||||
[activeTheme.id, activeThemePartitions, selectedProjectId],
|
||||
)
|
||||
const floodHazardImageOverlays = useMemo(
|
||||
() =>
|
||||
activeTheme.id === 'flood_hazard' && selectedProjectId
|
||||
? activeThemePartitions.flatMap((dataset) => {
|
||||
const bounds = dataset.source_metadata?.['bbox_epsg4326']
|
||||
return dataset.source_name === 'vmm_flood_hazard'
|
||||
&& Array.isArray(bounds)
|
||||
&& bounds.length === 4
|
||||
? [{
|
||||
url: floodHazardImageUrl(selectedProjectId, dataset.id),
|
||||
bbox: bounds.map(Number) as [number, number, number, number],
|
||||
label: floodScenarioLabel(dataset),
|
||||
opacity: 0.82,
|
||||
}]
|
||||
: []
|
||||
})
|
||||
: [],
|
||||
[activeTheme.id, activeThemePartitions, selectedProjectId],
|
||||
)
|
||||
const thematicRasterBounds = activeThemeDataset?.source_name === 'department_omgeving_thematic_raster'
|
||||
? activeThemeDataset.source_metadata?.['bbox_epsg4326']
|
||||
: null
|
||||
const thematicRasterImageOverlay = activeThemeDataset?.source_name === 'department_omgeving_thematic_raster' && selectedProjectId && Array.isArray(thematicRasterBounds) && thematicRasterBounds.length === 4
|
||||
? {
|
||||
url: thematicRasterImageUrl(selectedProjectId, activeThemeDataset.id),
|
||||
bbox: thematicRasterBounds.map(Number) as [number, number, number, number],
|
||||
label: getDatasetDisplayName(activeThemeDataset),
|
||||
opacity: 0.78,
|
||||
}
|
||||
: null
|
||||
const thematicRasterImageOverlays = useMemo(
|
||||
() => activeThemeDataset?.source_name === 'department_omgeving_thematic_raster' && selectedProjectId && Array.isArray(thematicRasterBounds) && thematicRasterBounds.length === 4
|
||||
? [{
|
||||
url: thematicRasterImageUrl(selectedProjectId, activeThemeDataset.id),
|
||||
bbox: thematicRasterBounds.map(Number) as [number, number, number, number],
|
||||
label: getDatasetDisplayName(activeThemeDataset),
|
||||
opacity: 0.78,
|
||||
}]
|
||||
: [],
|
||||
[activeThemeDataset, selectedProjectId, thematicRasterBounds],
|
||||
)
|
||||
const thematicLegendMin = String(activeThemeDataset?.source_metadata?.['legend_min_label'] ?? 'Lagere waarde')
|
||||
const thematicLegendMax = String(activeThemeDataset?.source_metadata?.['legend_max_label'] ?? 'Hogere waarde')
|
||||
const activeImageOverlay = thematicRasterImageOverlay ?? floodHazardImageOverlay ?? terrainImageOverlay ?? orthophotoImageOverlay
|
||||
const activeImageOverlays = useMemo(
|
||||
() => thematicRasterImageOverlays.length > 0
|
||||
? thematicRasterImageOverlays
|
||||
: floodHazardImageOverlays.length > 0
|
||||
? floodHazardImageOverlays
|
||||
: terrainImageOverlays.length > 0
|
||||
? terrainImageOverlays
|
||||
: orthophotoImageOverlay ? [orthophotoImageOverlay] : [],
|
||||
[floodHazardImageOverlays, orthophotoImageOverlay, terrainImageOverlays, thematicRasterImageOverlays],
|
||||
)
|
||||
const activeScopeProject = projects.find((project) => project.id === selectedProjectId) ?? null
|
||||
const activeScopeLabel = activeScopeProject ? operationalScopeProjectLabel(activeScopeProject) : 'Werkgebied'
|
||||
const municipalityAreaCount = areas.filter((area) => /^Gemeente\s/i.test(area.name)).length
|
||||
@@ -865,7 +983,7 @@ export function MapWorkspace({
|
||||
[themeInsights],
|
||||
)
|
||||
const activeSelectionResult = themeResults.find((item) => item.theme.id === activeThemeId)?.result
|
||||
?? (selectedMapDataset?.id === activeThemeDataset?.id ? mapSelectionResult : null)
|
||||
?? (!regionalRasterThemeActive && selectedMapDataset?.id === activeThemeDataset?.id ? mapSelectionResult : null)
|
||||
const selectedAreaSquareMetres = useMemo(
|
||||
() =>
|
||||
bboxesEqual(mapSelectionBbox, selectedAreaBbox) && selectedMapArea?.area_m2
|
||||
@@ -1106,14 +1224,23 @@ export function MapWorkspace({
|
||||
const loadAllThemeResults = async (bbox: VectorSelectionBBox, areaId?: string) => {
|
||||
const availableThemes = DATA_THEMES.flatMap((theme) => {
|
||||
const dataset = themeDatasetMap[theme.id]
|
||||
return dataset ? [{ themeId: theme.id, dataset }] : []
|
||||
return dataset
|
||||
? [{
|
||||
themeId: theme.id,
|
||||
dataset,
|
||||
partitioned: regionalScopeSelected && isPartitionedRaster(dataset),
|
||||
}]
|
||||
: []
|
||||
})
|
||||
await loadThemeInsights(bbox, availableThemes, areaId)
|
||||
}
|
||||
|
||||
const analyzeSelection = async (bbox: VectorSelectionBBox, areaId?: string) => {
|
||||
setSelectionBbox(bbox)
|
||||
const tasks: Array<Promise<unknown>> = [onRunMapSelectionExtract(bbox, areaId), loadAllThemeResults(bbox, areaId)]
|
||||
const tasks: Array<Promise<unknown>> = [loadAllThemeResults(bbox, areaId)]
|
||||
if (!regionalRasterThemeActive) {
|
||||
tasks.push(onRunMapSelectionExtract(bbox, areaId))
|
||||
}
|
||||
if (analysisMode === 'evolution' && earlierDatasetId && laterDatasetId) {
|
||||
tasks.push(compareTemporalSnapshots(earlierDatasetId, laterDatasetId, bbox, areaId))
|
||||
}
|
||||
@@ -1267,6 +1394,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 temporalGroups = themeTemporalSeriesMap[theme.id]
|
||||
const temporalGroup = temporalGroups[0]
|
||||
const evolutionAvailable = temporalGroups.some((group) => group.items.length >= 2)
|
||||
@@ -1296,7 +1424,7 @@ export function MapWorkspace({
|
||||
? 'Alleen huidige toestand'
|
||||
: 'Bron nog niet ingeladen'
|
||||
: dataset
|
||||
? datasetAvailabilityLabel(dataset)
|
||||
? datasetAvailabilityLabel(dataset, partitionCount)
|
||||
: 'Bron nog niet ingeladen'}
|
||||
</small>
|
||||
</span>
|
||||
@@ -1416,7 +1544,13 @@ export function MapWorkspace({
|
||||
<span>2</span>
|
||||
<div>
|
||||
<h3>Selecteer een gebied</h3>
|
||||
<p>{bboxSelectionMode ? 'Sleep nu een rechthoek op de kaart.' : 'Sleep een rechthoek of analyseer het volledige werkgebied.'}</p>
|
||||
<p>
|
||||
{bboxSelectionMode
|
||||
? 'Sleep nu een rechthoek op de kaart.'
|
||||
: regionalRasterThemeActive
|
||||
? 'Teken een rechthoek; de juiste gemeentelijke rasters worden automatisch gecombineerd.'
|
||||
: 'Sleep een rechthoek of analyseer het volledige werkgebied.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="geo-map-actions">
|
||||
@@ -1430,11 +1564,12 @@ export function MapWorkspace({
|
||||
</button>
|
||||
<button
|
||||
className="secondary-action"
|
||||
disabled={!activeThemeDataset || (analysisMode === 'evolution' && activeTemporalSeries.length < 2) || !selectedAreaBbox || mapSelectionLoading || themeResultsLoading}
|
||||
disabled={!activeThemeDataset || regionalRasterThemeActive || (analysisMode === 'evolution' && activeTemporalSeries.length < 2) || !selectedAreaBbox || mapSelectionLoading || themeResultsLoading}
|
||||
type="button"
|
||||
title={regionalRasterThemeActive ? 'Teken een begrensde rechthoek voor een regionale rasteranalyse.' : undefined}
|
||||
onClick={() => selectedAreaBbox && void analyzeSelection(selectedAreaBbox, selectedMapArea?.id)}
|
||||
>
|
||||
Volledig werkgebied
|
||||
{regionalRasterThemeActive ? 'Selecteer een deelgebied' : 'Volledig werkgebied'}
|
||||
</button>
|
||||
<button className="secondary-action" disabled={!mapSelectionBbox} type="button" onClick={clearAreaSelection}>
|
||||
Wis selectie
|
||||
@@ -1450,7 +1585,7 @@ export function MapWorkspace({
|
||||
areaData={areaFeatureCollection}
|
||||
selectedFeature={selectedFeature}
|
||||
selectionData={analysisMode === 'current' ? mapSelectionResult?.geojson ?? null : null}
|
||||
imageOverlay={activeImageOverlay}
|
||||
imageOverlays={activeImageOverlays}
|
||||
selectionBbox={mapSelectionBbox}
|
||||
bboxSelectionMode={bboxSelectionMode}
|
||||
visible={mapLayerVisible}
|
||||
@@ -1466,12 +1601,17 @@ export function MapWorkspace({
|
||||
/>
|
||||
<div className="geo-map-legend" aria-label="Kaartlegende">
|
||||
<span><i className="geo-legend-area" /> Werkgebied</span>
|
||||
{thematicRasterImageOverlay ? (
|
||||
{thematicRasterImageOverlays.length > 0 ? (
|
||||
<span className="geo-legend-thematic">
|
||||
<i className={`geo-legend-ramp geo-legend-ramp-${activeTheme.id}`} />
|
||||
<small>{thematicLegendMin} → {thematicLegendMax}</small>
|
||||
</span>
|
||||
) : activeImageOverlay ? <span><i className="geo-legend-imagery" /> {activeImageOverlay.label}</span> : null}
|
||||
) : activeImageOverlays.length > 0 ? (
|
||||
<span>
|
||||
<i className="geo-legend-imagery" /> {activeImageOverlays[0].label}
|
||||
{activeImageOverlays.length > 1 ? ` · ${activeImageOverlays.length} gemeenten` : ''}
|
||||
</span>
|
||||
) : null}
|
||||
{analysisOverlayActive ? (
|
||||
<>
|
||||
<span><i className="geo-legend-layer geo-legend-layer-buildings" /> AI-kandidaten</span>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { thematicRasterSelectionToMapSelection } from '../lib/thematicRaster'
|
||||
export interface MapThemeQuery<TThemeId extends string> {
|
||||
themeId: TThemeId
|
||||
dataset: DatasetCreateResponse
|
||||
partitioned?: boolean
|
||||
}
|
||||
|
||||
export interface MapThemeInsight<TThemeId extends string> extends MapThemeQuery<TThemeId> {
|
||||
@@ -54,19 +55,35 @@ export function useMapThemeSelectionInsights<TThemeId extends string>(
|
||||
setThemeInsightsError(null)
|
||||
try {
|
||||
const settled = await Promise.allSettled(
|
||||
queries.map(async ({ themeId, dataset }) => ({
|
||||
queries.map(async ({ themeId, dataset, partitioned }) => ({
|
||||
themeId,
|
||||
dataset,
|
||||
result: dataset.dataset_type === 'raster' && dataset.source_name === 'digitaal_vlaanderen_dhmv'
|
||||
? terrainSelectionToMapSelection(await datasetsApi.selectTerrain(selectedProjectId, dataset.id, {
|
||||
bbox,
|
||||
area_id: areaId,
|
||||
}))
|
||||
? 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(await datasetsApi.selectFloodHazard(selectedProjectId, dataset.id, {
|
||||
bbox,
|
||||
area_id: areaId,
|
||||
}))
|
||||
? 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,
|
||||
|
||||
@@ -134,6 +134,11 @@ export const datasetsApi = {
|
||||
payload: { bbox: VectorSelectionRequest['bbox']; area_id?: string },
|
||||
): Promise<TerrainSelectionResponse> =>
|
||||
apiPost<TerrainSelectionResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/terrain/select`, payload),
|
||||
selectTerrainPartitions: (
|
||||
projectId: string,
|
||||
payload: { bbox: VectorSelectionRequest['bbox']; area_id?: string; product_key: string },
|
||||
): Promise<TerrainSelectionResponse> =>
|
||||
apiPost<TerrainSelectionResponse>(`/api/v1/projects/${projectId}/datasets/raster/terrain/select`, payload),
|
||||
acquireFloodHazard: (projectId: string, payload: FloodHazardAcquireRequest): Promise<JobRead> =>
|
||||
apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/flood-hazard/acquire`, payload),
|
||||
listFloodHazardProducts: (projectId: string): Promise<{ items: FloodHazardProductRead[]; total: number }> =>
|
||||
@@ -144,6 +149,11 @@ export const datasetsApi = {
|
||||
payload: { bbox: VectorSelectionRequest['bbox']; area_id?: string },
|
||||
): Promise<FloodHazardSelectionResponse> =>
|
||||
apiPost<FloodHazardSelectionResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/flood-hazard/select`, payload),
|
||||
selectFloodHazardPartitions: (
|
||||
projectId: string,
|
||||
payload: { bbox: VectorSelectionRequest['bbox']; area_id?: string; product_key: string },
|
||||
): Promise<FloodHazardSelectionResponse> =>
|
||||
apiPost<FloodHazardSelectionResponse>(`/api/v1/projects/${projectId}/datasets/raster/flood-hazard/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 }> =>
|
||||
|
||||
@@ -360,6 +360,8 @@ export interface DhmvProductRead {
|
||||
|
||||
export interface TerrainSelectionResponse {
|
||||
dataset_id: string
|
||||
dataset_ids: string[]
|
||||
partition_count: number
|
||||
product_key: string
|
||||
surface_model: 'terrain' | 'surface'
|
||||
selection_bbox: VectorSelectionBBox
|
||||
@@ -410,6 +412,8 @@ export interface FloodHazardProductRead {
|
||||
|
||||
export interface FloodHazardSelectionResponse {
|
||||
dataset_id: string
|
||||
dataset_ids: string[]
|
||||
partition_count: number
|
||||
product_key: string
|
||||
mechanism: 'pluviaal' | 'fluviaal'
|
||||
climate_context: string
|
||||
|
||||
Reference in New Issue
Block a user