|
|
|
@@ -1,4 +1,4 @@
|
|
|
|
|
import { useEffect, useMemo, useRef, useState, type KeyboardEvent } from 'react'
|
|
|
|
|
import { useCallback, useEffect, useMemo, useRef, useState, type KeyboardEvent } from 'react'
|
|
|
|
|
import GeoMap from '../GeoMap'
|
|
|
|
|
import type { AreaRead, CoverageResolveResponse, CoverageStatus, DatasetCreateResponse, DetectionQaResult, MapResultExportRequest, MapViewportState, OrthophotoAcquisitionResult, OrthophotoProductRead, ProjectRead, QaComparisonResult, VectorSelectionBBox, VectorSelectionMetric, VectorSelectionResponse } from '../../types'
|
|
|
|
|
import { useMapThemeSelectionInsights, type MapThemeAcquisition, type MapThemeQuery } from '../../hooks/useMapThemeSelectionInsights'
|
|
|
|
@@ -30,10 +30,12 @@ import {
|
|
|
|
|
normalizeBboxFromCorners,
|
|
|
|
|
operationalScopeProjectLabel,
|
|
|
|
|
parseBboxInput,
|
|
|
|
|
productCoversZones,
|
|
|
|
|
readablePropertyName,
|
|
|
|
|
resultCountLabel,
|
|
|
|
|
resultMetricLabel,
|
|
|
|
|
safeFileStem,
|
|
|
|
|
selectedAreaCoverageZones,
|
|
|
|
|
selectedFeatureCollection,
|
|
|
|
|
selectionAreaSquareMetres,
|
|
|
|
|
selectionMetricLabel,
|
|
|
|
@@ -43,7 +45,26 @@ const DEFAULT_SELECTED_FEATURE_FILENAME = 'selected-feature.geojson'
|
|
|
|
|
const DEFAULT_AREA_SELECTION_FILENAME = 'area-selection.geojson'
|
|
|
|
|
const EMPTY_TEMPORAL_SERIES: DatasetCreateResponse[] = []
|
|
|
|
|
|
|
|
|
|
type DataThemeId = 'buildings' | 'space_occupation' | 'open_space' | 'population' | 'forest' | 'nature_value' | 'agriculture' | 'soil' | 'water' | 'bathymetry' | 'flood_hazard' | 'elevation' | 'accessibility' | 'services' | 'roads' | 'parcels'
|
|
|
|
|
type DataThemeId =
|
|
|
|
|
| 'administrative'
|
|
|
|
|
| 'buildings'
|
|
|
|
|
| 'space_occupation'
|
|
|
|
|
| 'open_space'
|
|
|
|
|
| 'population'
|
|
|
|
|
| 'forest'
|
|
|
|
|
| 'nature_value'
|
|
|
|
|
| 'agriculture'
|
|
|
|
|
| 'soil'
|
|
|
|
|
| 'water'
|
|
|
|
|
| 'bathymetry'
|
|
|
|
|
| 'flood_hazard'
|
|
|
|
|
| 'elevation'
|
|
|
|
|
| 'accessibility'
|
|
|
|
|
| 'services'
|
|
|
|
|
| 'roads'
|
|
|
|
|
| 'parcels'
|
|
|
|
|
| 'maritime_planning'
|
|
|
|
|
| 'marine_environment'
|
|
|
|
|
|
|
|
|
|
interface DataTheme {
|
|
|
|
|
id: DataThemeId
|
|
|
|
@@ -67,6 +88,13 @@ interface OnDemandMapProduct extends MapThemeAcquisition {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const DATA_THEMES: DataTheme[] = [
|
|
|
|
|
{
|
|
|
|
|
id: 'administrative',
|
|
|
|
|
label: 'Bestuurlijke indeling',
|
|
|
|
|
shortLabel: 'Bestuursgebieden',
|
|
|
|
|
description: 'Officiële lands-, gewest-, provincie- en gemeentegrenzen van het NGI.',
|
|
|
|
|
tokens: ['administrative', 'adminvector', 'belgium_land_boundary', 'belgium_regions', 'belgium_provinces', 'belgium_municipalities'],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'buildings',
|
|
|
|
|
label: 'Bebouwing',
|
|
|
|
@@ -179,9 +207,24 @@ const DATA_THEMES: DataTheme[] = [
|
|
|
|
|
description: 'Kadastrale of administratieve perceelcontouren.',
|
|
|
|
|
tokens: ['parcels', 'parcel', 'percelen', 'perceel', 'cadastre', 'kadaster'],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'maritime_planning',
|
|
|
|
|
label: 'Maritieme planning',
|
|
|
|
|
shortLabel: 'Plan- en gebruikszones',
|
|
|
|
|
description: 'Officiële gebruiks- en beschermingszones uit het Belgisch Marien Ruimtelijk Plan 2026-2034.',
|
|
|
|
|
tokens: ['maritime_planning', 'marine_spatial_plan', 'rbins_msp', 'bmsp', 'imsp26'],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'marine_environment',
|
|
|
|
|
label: 'Mariene rapportagezones',
|
|
|
|
|
shortLabel: 'Zeegebieden',
|
|
|
|
|
description: 'Officiële juridische en mariene rapportagegebieden voor het Belgische deel van de Noordzee.',
|
|
|
|
|
tokens: ['marine_environment', 'marine_legal_scopes', 'marine_reporting_units', 'rbins_marine_reporting'],
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
const COVERAGE_THEME_BY_MAP_THEME: Record<DataThemeId, string> = {
|
|
|
|
|
administrative: 'admin',
|
|
|
|
|
buildings: 'buildings',
|
|
|
|
|
space_occupation: 'land_cover_use',
|
|
|
|
|
open_space: 'land_cover_use',
|
|
|
|
@@ -198,6 +241,8 @@ const COVERAGE_THEME_BY_MAP_THEME: Record<DataThemeId, string> = {
|
|
|
|
|
services: 'population',
|
|
|
|
|
roads: 'roads',
|
|
|
|
|
parcels: 'parcels',
|
|
|
|
|
maritime_planning: 'maritime_planning',
|
|
|
|
|
marine_environment: 'marine_environment',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function coverageStatusLabel(status: CoverageStatus): string {
|
|
|
|
@@ -225,6 +270,7 @@ function coverageZoneLabel(zone: string): string {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const DATA_THEME_MAP_STYLES: Record<DataThemeId, { fill: string; line: string }> = {
|
|
|
|
|
administrative: { fill: '#5f6f7f', line: '#344554' },
|
|
|
|
|
buildings: { fill: '#d45f3d', line: '#9f3e24' },
|
|
|
|
|
space_occupation: { fill: '#be3e33', line: '#8f2c24' },
|
|
|
|
|
open_space: { fill: '#267a46', line: '#175c32' },
|
|
|
|
@@ -241,6 +287,8 @@ const DATA_THEME_MAP_STYLES: Record<DataThemeId, { fill: string; line: string }>
|
|
|
|
|
services: { fill: '#b66d16', line: '#854d0e' },
|
|
|
|
|
roads: { fill: '#6b7280', line: '#4b5563' },
|
|
|
|
|
parcels: { fill: '#a7792f', line: '#7d571f' },
|
|
|
|
|
maritime_planning: { fill: '#2f7f8f', line: '#145d6a' },
|
|
|
|
|
marine_environment: { fill: '#3475a3', line: '#1c557d' },
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function datasetAvailabilityLabel(
|
|
|
|
@@ -274,6 +322,15 @@ function datasetAvailabilityLabel(
|
|
|
|
|
const resolutionLabel = Number.isFinite(resolution) ? `${resolution.toLocaleString('nl-BE')} m` : 'Raster'
|
|
|
|
|
return `${resolutionLabel} officiële bron${Number.isFinite(year) ? ` · ${year}` : ''}`
|
|
|
|
|
}
|
|
|
|
|
if (dataset.source_name === 'ngi_adminvector') {
|
|
|
|
|
return `${(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0).toLocaleString('nl-BE')} officiële bestuursgebieden`
|
|
|
|
|
}
|
|
|
|
|
if (dataset.source_name === 'rbins_msp_2026') {
|
|
|
|
|
return `${(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0).toLocaleString('nl-BE')} officiële planobjecten · 2026-2034`
|
|
|
|
|
}
|
|
|
|
|
if (dataset.source_name === 'rbins_marine_reporting_units') {
|
|
|
|
|
return `${(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0).toLocaleString('nl-BE')} officiële zeegebieden`
|
|
|
|
|
}
|
|
|
|
|
return `${(dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0).toLocaleString('nl-BE')} objecten beschikbaar`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -313,6 +370,15 @@ function datasetMatchesTheme(dataset: DatasetCreateResponse, theme: DataTheme):
|
|
|
|
|
if (dataset.source_name === 'dov_soil_map') {
|
|
|
|
|
return theme.id === 'soil'
|
|
|
|
|
}
|
|
|
|
|
if (dataset.source_name === 'ngi_adminvector') {
|
|
|
|
|
return theme.id === 'administrative'
|
|
|
|
|
}
|
|
|
|
|
if (dataset.source_name === 'rbins_msp_2026') {
|
|
|
|
|
return theme.id === 'maritime_planning'
|
|
|
|
|
}
|
|
|
|
|
if (dataset.source_name === 'rbins_marine_reporting_units') {
|
|
|
|
|
return theme.id === 'marine_environment'
|
|
|
|
|
}
|
|
|
|
|
const searchText = datasetSearchText(dataset)
|
|
|
|
|
return theme.tokens.some((token) => searchText.includes(token))
|
|
|
|
|
}
|
|
|
|
@@ -342,8 +408,17 @@ function datasetProductKey(dataset: DatasetCreateResponse): string {
|
|
|
|
|
function datasetCoversSelectedArea(
|
|
|
|
|
dataset: DatasetCreateResponse,
|
|
|
|
|
selectedAreaId: string | null,
|
|
|
|
|
selectedAreaName: string | null | undefined,
|
|
|
|
|
regionalScope = false,
|
|
|
|
|
): boolean {
|
|
|
|
|
const selectedZones = selectedAreaCoverageZones(selectedAreaName)
|
|
|
|
|
const configuredZones = dataset.source_metadata?.['coverage_zones']
|
|
|
|
|
if (selectedZones && Array.isArray(configuredZones) && configuredZones.length > 0) {
|
|
|
|
|
const datasetZones = configuredZones.map((zone) => String(zone))
|
|
|
|
|
if (!selectedZones.some((zone) => datasetZones.includes(zone))) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const coverageScope = String(dataset.source_metadata?.['coverage_scope'] ?? '')
|
|
|
|
|
if (isPartitionedBathymetry(dataset)) {
|
|
|
|
|
return regionalScope
|
|
|
|
@@ -363,6 +438,7 @@ function rasterPartitionsForDataset(
|
|
|
|
|
datasets: DatasetCreateResponse[],
|
|
|
|
|
representative: DatasetCreateResponse | null,
|
|
|
|
|
selectedAreaId: string | null,
|
|
|
|
|
selectedAreaName: string | null | undefined,
|
|
|
|
|
regionalScope: boolean,
|
|
|
|
|
): DatasetCreateResponse[] {
|
|
|
|
|
if (!representative) {
|
|
|
|
@@ -382,7 +458,7 @@ function rasterPartitionsForDataset(
|
|
|
|
|
? String(dataset.source_metadata?.['partition_manifest_sha256'] ?? '') === manifestSha256
|
|
|
|
|
: datasetProductKey(dataset) === productKey
|
|
|
|
|
)
|
|
|
|
|
&& datasetCoversSelectedArea(dataset, selectedAreaId, true),
|
|
|
|
|
&& datasetCoversSelectedArea(dataset, selectedAreaId, selectedAreaName, true),
|
|
|
|
|
)
|
|
|
|
|
.sort((left, right) => String(left.area_id ?? '').localeCompare(String(right.area_id ?? '')))
|
|
|
|
|
}
|
|
|
|
@@ -391,12 +467,13 @@ function pickThemeDataset(
|
|
|
|
|
datasets: DatasetCreateResponse[],
|
|
|
|
|
theme: DataTheme,
|
|
|
|
|
selectedAreaId: string | null,
|
|
|
|
|
selectedAreaName: string | null | undefined,
|
|
|
|
|
regionalScope = false,
|
|
|
|
|
): DatasetCreateResponse | null {
|
|
|
|
|
const candidates = datasets.filter(
|
|
|
|
|
(dataset) =>
|
|
|
|
|
datasetMatchesTheme(dataset, theme)
|
|
|
|
|
&& datasetCoversSelectedArea(dataset, selectedAreaId, regionalScope),
|
|
|
|
|
&& datasetCoversSelectedArea(dataset, selectedAreaId, selectedAreaName, regionalScope),
|
|
|
|
|
)
|
|
|
|
|
candidates.sort((left, right) => {
|
|
|
|
|
const priorityScore = (dataset: DatasetCreateResponse) =>
|
|
|
|
@@ -723,7 +800,15 @@ export function MapWorkspace({
|
|
|
|
|
})
|
|
|
|
|
const activeScopeProject = projects.find((project) => project.id === selectedProjectId) ?? null
|
|
|
|
|
const activeScopeLabel = activeScopeProject ? operationalScopeProjectLabel(activeScopeProject) : 'Werkgebied'
|
|
|
|
|
const flandersScopeSelected = activeScopeProject?.name === FLANDERS_WORKSPACE_PROJECT_NAME
|
|
|
|
|
const selectedMapArea = areas.find((area) => area.id === selectedMapAreaId)
|
|
|
|
|
const selectedCoverageZones = useMemo(
|
|
|
|
|
() => selectedAreaCoverageZones(selectedMapArea?.name),
|
|
|
|
|
[selectedMapArea?.name],
|
|
|
|
|
)
|
|
|
|
|
const flandersScopeSelected = Boolean(
|
|
|
|
|
selectedCoverageZones?.includes('flanders')
|
|
|
|
|
|| (!selectedCoverageZones && activeScopeProject?.name === FLANDERS_WORKSPACE_PROJECT_NAME),
|
|
|
|
|
)
|
|
|
|
|
const {
|
|
|
|
|
themeInsights,
|
|
|
|
|
themeInsightsLoading: themeResultsLoading,
|
|
|
|
@@ -735,7 +820,8 @@ export function MapWorkspace({
|
|
|
|
|
products: officialMapProducts,
|
|
|
|
|
loading: officialMapProductsLoading,
|
|
|
|
|
error: officialMapProductsError,
|
|
|
|
|
} = useOfficialMapProducts(flandersScopeSelected ? selectedProjectId : null)
|
|
|
|
|
resolveCoverage,
|
|
|
|
|
} = useOfficialMapProducts(selectedProjectId)
|
|
|
|
|
const {
|
|
|
|
|
temporalComparison,
|
|
|
|
|
temporalComparisonLoading,
|
|
|
|
@@ -759,7 +845,6 @@ export function MapWorkspace({
|
|
|
|
|
const [fullWorkflowMode, setFullWorkflowMode] = useState<'new' | 'reuse'>('new')
|
|
|
|
|
const [mapAnalysisDurationMs, setMapAnalysisDurationMs] = useState<number | null>(null)
|
|
|
|
|
const mapAnalysisRequestSequence = useRef(0)
|
|
|
|
|
const selectedMapArea = areas.find((area) => area.id === selectedMapAreaId)
|
|
|
|
|
const regionalScopeSelected = Boolean(selectedMapArea && !isMunicipalityAreaName(selectedMapArea.name))
|
|
|
|
|
const featureProperties = selectedMapFeature?.properties ?? null
|
|
|
|
|
const isBathymetryProfile = featureProperties?.['provider'] === 'vmm_vha_bathymetry_profiles'
|
|
|
|
@@ -794,7 +879,7 @@ export function MapWorkspace({
|
|
|
|
|
.filter(
|
|
|
|
|
(dataset) =>
|
|
|
|
|
dataset.source_name === 'vmm_flood_hazard'
|
|
|
|
|
&& datasetCoversSelectedArea(dataset, selectedMapAreaId, regionalScopeSelected),
|
|
|
|
|
&& datasetCoversSelectedArea(dataset, selectedMapAreaId, selectedMapArea?.name, regionalScopeSelected),
|
|
|
|
|
)
|
|
|
|
|
.sort((left, right) => floodScenarioLabel(left).localeCompare(floodScenarioLabel(right), 'nl'))
|
|
|
|
|
if (!regionalScopeSelected) {
|
|
|
|
@@ -809,13 +894,19 @@ export function MapWorkspace({
|
|
|
|
|
}
|
|
|
|
|
return Array.from(products.values())
|
|
|
|
|
},
|
|
|
|
|
[availableMapDatasets, regionalScopeSelected, selectedMapAreaId],
|
|
|
|
|
[availableMapDatasets, regionalScopeSelected, selectedMapArea?.name, selectedMapAreaId],
|
|
|
|
|
)
|
|
|
|
|
const themeDatasetMap = useMemo(() => {
|
|
|
|
|
const result = Object.fromEntries(
|
|
|
|
|
DATA_THEMES.map((theme) => [
|
|
|
|
|
theme.id,
|
|
|
|
|
pickThemeDataset(availableMapDatasets, theme, selectedMapAreaId, regionalScopeSelected),
|
|
|
|
|
pickThemeDataset(
|
|
|
|
|
availableMapDatasets,
|
|
|
|
|
theme,
|
|
|
|
|
selectedMapAreaId,
|
|
|
|
|
selectedMapArea?.name,
|
|
|
|
|
regionalScopeSelected,
|
|
|
|
|
),
|
|
|
|
|
]),
|
|
|
|
|
) as Record<DataThemeId, DatasetCreateResponse | null>
|
|
|
|
|
const selectedFloodHazard = floodHazardDatasets.find(
|
|
|
|
@@ -833,7 +924,7 @@ export function MapWorkspace({
|
|
|
|
|
(dataset) =>
|
|
|
|
|
dataset.source_name === 'digitaal_vlaanderen_dhmv'
|
|
|
|
|
&& datasetProductKey(dataset) === selectedDhmvProductKey
|
|
|
|
|
&& datasetCoversSelectedArea(dataset, selectedMapAreaId, regionalScopeSelected),
|
|
|
|
|
&& datasetCoversSelectedArea(dataset, selectedMapAreaId, selectedMapArea?.name, regionalScopeSelected),
|
|
|
|
|
) ?? null
|
|
|
|
|
}
|
|
|
|
|
if (flandersScopeSelected && officialMapProducts.thematic.length > 0) {
|
|
|
|
@@ -846,8 +937,10 @@ export function MapWorkspace({
|
|
|
|
|
result[product.key] = null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (flandersScopeSelected && officialMapProducts.officialVector.length > 0) {
|
|
|
|
|
for (const product of officialMapProducts.officialVector) {
|
|
|
|
|
if (officialMapProducts.officialVector.length > 0) {
|
|
|
|
|
for (const product of officialMapProducts.officialVector.filter((item) =>
|
|
|
|
|
productCoversZones(item.coverage_zones, selectedCoverageZones),
|
|
|
|
|
)) {
|
|
|
|
|
result[product.theme] = null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -865,7 +958,9 @@ export function MapWorkspace({
|
|
|
|
|
selectedDhmvProductKey,
|
|
|
|
|
selectedFloodHazardDatasetId,
|
|
|
|
|
selectedFloodHazardProductKey,
|
|
|
|
|
selectedMapArea?.name,
|
|
|
|
|
selectedMapAreaId,
|
|
|
|
|
selectedCoverageZones,
|
|
|
|
|
])
|
|
|
|
|
const themePartitionMap = useMemo(
|
|
|
|
|
() =>
|
|
|
|
@@ -876,11 +971,12 @@ export function MapWorkspace({
|
|
|
|
|
availableMapDatasets,
|
|
|
|
|
themeDatasetMap[theme.id],
|
|
|
|
|
selectedMapAreaId,
|
|
|
|
|
selectedMapArea?.name,
|
|
|
|
|
regionalScopeSelected,
|
|
|
|
|
),
|
|
|
|
|
]),
|
|
|
|
|
) as Record<DataThemeId, DatasetCreateResponse[]>,
|
|
|
|
|
[availableMapDatasets, regionalScopeSelected, selectedMapAreaId, themeDatasetMap],
|
|
|
|
|
[availableMapDatasets, regionalScopeSelected, selectedMapArea?.name, selectedMapAreaId, themeDatasetMap],
|
|
|
|
|
)
|
|
|
|
|
const activeTheme = DATA_THEMES.find((theme) => theme.id === activeThemeId) ?? DATA_THEMES[0]
|
|
|
|
|
const activeCoverageTheme = COVERAGE_THEME_BY_MAP_THEME[activeTheme.id]
|
|
|
|
@@ -892,14 +988,16 @@ export function MapWorkspace({
|
|
|
|
|
) ?? { operational: 0, partial: 0, not_configured: 0, unsupported: 0 },
|
|
|
|
|
[coverage],
|
|
|
|
|
)
|
|
|
|
|
const onDemandProductMap = useMemo(
|
|
|
|
|
() => {
|
|
|
|
|
const result = new Map<DataThemeId, OnDemandMapProduct>()
|
|
|
|
|
if (!flandersScopeSelected) {
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
const onDemandProductsForZones = useCallback((zones: string[] | null): OnDemandMapProduct[] => {
|
|
|
|
|
const result: OnDemandMapProduct[] = []
|
|
|
|
|
const effectiveZones = zones ?? selectedCoverageZones
|
|
|
|
|
const includesFlanders = Boolean(
|
|
|
|
|
effectiveZones?.includes('flanders')
|
|
|
|
|
|| (!effectiveZones && activeScopeProject?.name === FLANDERS_WORKSPACE_PROJECT_NAME),
|
|
|
|
|
)
|
|
|
|
|
if (includesFlanders) {
|
|
|
|
|
for (const product of officialMapProducts.thematic) {
|
|
|
|
|
result.set(product.theme, {
|
|
|
|
|
result.push({
|
|
|
|
|
kind: 'thematic_raster',
|
|
|
|
|
productKey: product.key,
|
|
|
|
|
displayName: product.display_name,
|
|
|
|
@@ -910,7 +1008,7 @@ export function MapWorkspace({
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
for (const product of officialMapProducts.grb) {
|
|
|
|
|
result.set(product.key, {
|
|
|
|
|
result.push({
|
|
|
|
|
kind: 'grb',
|
|
|
|
|
productKey: product.key,
|
|
|
|
|
displayName: product.display_name,
|
|
|
|
@@ -920,52 +1018,65 @@ export function MapWorkspace({
|
|
|
|
|
limitationMessage: product.limitation_message,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
for (const product of officialMapProducts.officialVector) {
|
|
|
|
|
result.set(product.theme, {
|
|
|
|
|
kind: 'official_vector',
|
|
|
|
|
productKey: product.key,
|
|
|
|
|
displayName: product.display_name,
|
|
|
|
|
theme: product.theme,
|
|
|
|
|
availabilityLabel: `${product.observation_label} · officiële vectorbron · laad bij selectie`,
|
|
|
|
|
attribution: product.attribution,
|
|
|
|
|
limitationMessage: product.limitation_message,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
const dhmvProduct = officialMapProducts.dhmv.find((product) => product.key === selectedDhmvProductKey)
|
|
|
|
|
if (dhmvProduct) {
|
|
|
|
|
result.set('elevation', {
|
|
|
|
|
kind: 'dhmv',
|
|
|
|
|
productKey: dhmvProduct.key,
|
|
|
|
|
displayName: dhmvProduct.display_name,
|
|
|
|
|
theme: 'elevation',
|
|
|
|
|
availabilityLabel: `${dhmvProduct.native_resolution_m} m · ${dhmvProduct.acquisition_period} · laad bij selectie`,
|
|
|
|
|
attribution: dhmvProduct.attribution,
|
|
|
|
|
limitationMessage: dhmvProduct.limitation_message,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
const floodProduct = officialMapProducts.floodHazard.find(
|
|
|
|
|
(product) => product.key === selectedFloodHazardProductKey,
|
|
|
|
|
)
|
|
|
|
|
if (floodProduct) {
|
|
|
|
|
result.set('flood_hazard', {
|
|
|
|
|
kind: 'flood_hazard',
|
|
|
|
|
productKey: floodProduct.key,
|
|
|
|
|
displayName: floodProduct.display_name,
|
|
|
|
|
theme: 'flood_hazard',
|
|
|
|
|
availabilityLabel: `${floodProduct.native_resolution_m} m · ${floodProduct.climate_context} · T${floodProduct.return_period_years} · laad bij selectie`,
|
|
|
|
|
attribution: floodProduct.attribution,
|
|
|
|
|
limitationMessage: floodProduct.limitation_message,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
},
|
|
|
|
|
[
|
|
|
|
|
flandersScopeSelected,
|
|
|
|
|
officialMapProducts,
|
|
|
|
|
selectedDhmvProductKey,
|
|
|
|
|
selectedFloodHazardProductKey,
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
for (const product of officialMapProducts.officialVector.filter((item) =>
|
|
|
|
|
productCoversZones(item.coverage_zones, effectiveZones),
|
|
|
|
|
)) {
|
|
|
|
|
result.push({
|
|
|
|
|
kind: 'official_vector',
|
|
|
|
|
productKey: product.key,
|
|
|
|
|
displayName: product.display_name,
|
|
|
|
|
theme: product.theme,
|
|
|
|
|
availabilityLabel: `${product.observation_label} · officiële vectorbron · laad bij selectie`,
|
|
|
|
|
attribution: product.attribution,
|
|
|
|
|
limitationMessage: product.limitation_message,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
const dhmvProduct = includesFlanders
|
|
|
|
|
? officialMapProducts.dhmv.find((product) => product.key === selectedDhmvProductKey)
|
|
|
|
|
: null
|
|
|
|
|
if (dhmvProduct) {
|
|
|
|
|
result.push({
|
|
|
|
|
kind: 'dhmv',
|
|
|
|
|
productKey: dhmvProduct.key,
|
|
|
|
|
displayName: dhmvProduct.display_name,
|
|
|
|
|
theme: 'elevation',
|
|
|
|
|
availabilityLabel: `${dhmvProduct.native_resolution_m} m · ${dhmvProduct.acquisition_period} · laad bij selectie`,
|
|
|
|
|
attribution: dhmvProduct.attribution,
|
|
|
|
|
limitationMessage: dhmvProduct.limitation_message,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
const floodProduct = includesFlanders
|
|
|
|
|
? officialMapProducts.floodHazard.find(
|
|
|
|
|
(product) => product.key === selectedFloodHazardProductKey,
|
|
|
|
|
)
|
|
|
|
|
: null
|
|
|
|
|
if (floodProduct) {
|
|
|
|
|
result.push({
|
|
|
|
|
kind: 'flood_hazard',
|
|
|
|
|
productKey: floodProduct.key,
|
|
|
|
|
displayName: floodProduct.display_name,
|
|
|
|
|
theme: 'flood_hazard',
|
|
|
|
|
availabilityLabel: `${floodProduct.native_resolution_m} m · ${floodProduct.climate_context} · T${floodProduct.return_period_years} · laad bij selectie`,
|
|
|
|
|
attribution: floodProduct.attribution,
|
|
|
|
|
limitationMessage: floodProduct.limitation_message,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}, [
|
|
|
|
|
activeScopeProject?.name,
|
|
|
|
|
officialMapProducts,
|
|
|
|
|
selectedCoverageZones,
|
|
|
|
|
selectedDhmvProductKey,
|
|
|
|
|
selectedFloodHazardProductKey,
|
|
|
|
|
])
|
|
|
|
|
const onDemandProductMap = useMemo(() => {
|
|
|
|
|
const result = new Map<DataThemeId, OnDemandMapProduct>()
|
|
|
|
|
for (const product of onDemandProductsForZones(selectedCoverageZones)) {
|
|
|
|
|
result.set(product.theme, product)
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}, [onDemandProductsForZones, selectedCoverageZones])
|
|
|
|
|
const activeOnDemandMapProduct = onDemandProductMap.get(activeTheme.id) ?? null
|
|
|
|
|
const activeThemeMapStyle = DATA_THEME_MAP_STYLES[activeTheme.id]
|
|
|
|
|
const analysisOverlayActive = mapContentMode === 'analysis' && analysisLayerAvailable && Boolean(mapFeatureCollection)
|
|
|
|
@@ -993,15 +1104,20 @@ export function MapWorkspace({
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (
|
|
|
|
|
!flandersScopeSelected
|
|
|
|
|
|| analysisMode !== 'current'
|
|
|
|
|
analysisMode !== 'current'
|
|
|
|
|
|| activeThemeAvailable
|
|
|
|
|
|| (onDemandProductMap.size === 0 && !officialMapProductsError)
|
|
|
|
|
|| (
|
|
|
|
|
selectedProjectId
|
|
|
|
|
&& officialMapProductsLoading
|
|
|
|
|
&& onDemandProductMap.size === 0
|
|
|
|
|
&& !officialMapProductsError
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
const fallbackTheme = DATA_THEMES.find((theme) =>
|
|
|
|
|
theme.id === 'space_occupation'
|
|
|
|
|
flandersScopeSelected
|
|
|
|
|
&& theme.id === 'space_occupation'
|
|
|
|
|
&& Boolean(themeDatasetMap[theme.id] || onDemandProductMap.get(theme.id)),
|
|
|
|
|
) ?? DATA_THEMES.find((theme) =>
|
|
|
|
|
Boolean(themeDatasetMap[theme.id] || onDemandProductMap.get(theme.id)),
|
|
|
|
@@ -1019,8 +1135,10 @@ export function MapWorkspace({
|
|
|
|
|
analysisMode,
|
|
|
|
|
flandersScopeSelected,
|
|
|
|
|
officialMapProductsError,
|
|
|
|
|
officialMapProductsLoading,
|
|
|
|
|
onDemandProductMap,
|
|
|
|
|
onOpenDatasetInMap,
|
|
|
|
|
selectedProjectId,
|
|
|
|
|
themeDatasetMap,
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
@@ -1528,20 +1646,37 @@ export function MapWorkspace({
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const loadAllThemeResults = async (bbox: VectorSelectionBBox, areaId?: string) => {
|
|
|
|
|
let resolvedZones = selectedCoverageZones
|
|
|
|
|
if (analysisMode === 'current' && selectedProjectId) {
|
|
|
|
|
const resolvedCoverage = await resolveCoverage({
|
|
|
|
|
minx: bbox.min_x,
|
|
|
|
|
miny: bbox.min_y,
|
|
|
|
|
maxx: bbox.max_x,
|
|
|
|
|
maxy: bbox.max_y,
|
|
|
|
|
})
|
|
|
|
|
if (!resolvedCoverage) {
|
|
|
|
|
clearThemeInsights()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
resolvedZones = resolvedCoverage.intersected_zones
|
|
|
|
|
}
|
|
|
|
|
const resolvedProducts = analysisMode === 'current'
|
|
|
|
|
? onDemandProductsForZones(resolvedZones)
|
|
|
|
|
: []
|
|
|
|
|
const availableThemes: Array<MapThemeQuery<DataThemeId>> = []
|
|
|
|
|
for (const theme of DATA_THEMES) {
|
|
|
|
|
const onDemandProduct = analysisMode === 'current'
|
|
|
|
|
? onDemandProductMap.get(theme.id)
|
|
|
|
|
: null
|
|
|
|
|
if (onDemandProduct) {
|
|
|
|
|
availableThemes.push({
|
|
|
|
|
themeId: theme.id,
|
|
|
|
|
acquisition: {
|
|
|
|
|
kind: onDemandProduct.kind,
|
|
|
|
|
productKey: onDemandProduct.productKey,
|
|
|
|
|
displayName: onDemandProduct.displayName,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
const onDemandProducts = resolvedProducts.filter((product) => product.theme === theme.id)
|
|
|
|
|
if (onDemandProducts.length > 0) {
|
|
|
|
|
for (const onDemandProduct of onDemandProducts) {
|
|
|
|
|
availableThemes.push({
|
|
|
|
|
themeId: theme.id,
|
|
|
|
|
acquisition: {
|
|
|
|
|
kind: onDemandProduct.kind,
|
|
|
|
|
productKey: onDemandProduct.productKey,
|
|
|
|
|
displayName: onDemandProduct.displayName,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
const dataset = themeDatasetMap[theme.id]
|
|
|
|
@@ -1856,10 +1991,10 @@ export function MapWorkspace({
|
|
|
|
|
: activeTheme.description}
|
|
|
|
|
</small>
|
|
|
|
|
</div>
|
|
|
|
|
{officialMapProductsLoading && flandersScopeSelected ? (
|
|
|
|
|
{officialMapProductsLoading && selectedProjectId ? (
|
|
|
|
|
<p className="geo-data-notice">Beschikbare Vlaamse kaartbronnen worden gecontroleerd…</p>
|
|
|
|
|
) : null}
|
|
|
|
|
{officialMapProductsError && flandersScopeSelected ? (
|
|
|
|
|
{officialMapProductsError && selectedProjectId ? (
|
|
|
|
|
<p className="error">{officialMapProductsError}</p>
|
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
|
@@ -1877,7 +2012,12 @@ export function MapWorkspace({
|
|
|
|
|
(item) =>
|
|
|
|
|
item.source_name === 'digitaal_vlaanderen_dhmv'
|
|
|
|
|
&& datasetProductKey(item) === productKey
|
|
|
|
|
&& datasetCoversSelectedArea(item, selectedMapAreaId, regionalScopeSelected),
|
|
|
|
|
&& datasetCoversSelectedArea(
|
|
|
|
|
item,
|
|
|
|
|
selectedMapAreaId,
|
|
|
|
|
selectedMapArea?.name,
|
|
|
|
|
regionalScopeSelected,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
if (dataset) {
|
|
|
|
|
onOpenDatasetInMap(dataset)
|
|
|
|
|