Add Belgium and North Sea coverage foundation
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-18 01:45:00 +02:00
parent a8fd6e5e9c
commit 1e527bf810
29 changed files with 2524 additions and 10 deletions
+103 -1
View File
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from 'react'
import GeoMap from '../GeoMap'
import type { AreaRead, DatasetCreateResponse, DetectionQaResult, MapResultExportRequest, MapViewportState, OrthophotoAcquisitionResult, OrthophotoProductRead, ProjectRead, QaComparisonResult, VectorSelectionBBox, VectorSelectionMetric, VectorSelectionResponse } from '../../types'
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'
import { useOfficialMapProducts } from '../../hooks/useOfficialMapProducts'
import { useTemporalComparison } from '../../hooks/useTemporalComparison'
@@ -176,6 +176,49 @@ const DATA_THEMES: DataTheme[] = [
},
]
const COVERAGE_THEME_BY_MAP_THEME: Record<DataThemeId, string> = {
buildings: 'buildings',
space_occupation: 'land_cover_use',
open_space: 'land_cover_use',
population: 'population',
forest: 'land_cover_use',
nature_value: 'nature',
agriculture: 'land_cover_use',
soil: 'soil',
water: 'surface_water',
bathymetry: 'bathymetry',
flood_hazard: 'flood_climate',
elevation: 'elevation',
accessibility: 'roads',
services: 'population',
roads: 'roads',
parcels: 'parcels',
}
function coverageStatusLabel(status: CoverageStatus): string {
const labels: Record<CoverageStatus, string> = {
operational: 'Beschikbaar',
partial: 'Gedeeltelijk',
not_configured: 'Niet gekoppeld',
unsupported: 'Niet ondersteund',
}
return labels[status]
}
function coverageZoneLabel(zone: string): string {
const labels: Record<string, string> = {
belgium: 'Belgie',
flanders: 'Vlaanderen',
wallonia: 'Wallonie',
brussels: 'Brussel',
belgian_north_sea: 'Belgische Noordzee',
territorial_sea: 'Territoriale zee',
exclusive_economic_zone: 'EEZ',
continental_shelf: 'Continentaal plat',
}
return labels[zone] ?? zone
}
const DATA_THEME_MAP_STYLES: Record<DataThemeId, { fill: string; line: string }> = {
buildings: { fill: '#d45f3d', line: '#9f3e24' },
space_occupation: { fill: '#be3e33', line: '#8f2c24' },
@@ -516,6 +559,9 @@ interface MapWorkspaceProps {
mapSelectionResult: VectorSelectionResponse | null
mapSelectionLoading: boolean
mapSelectionError: string | null
coverage: CoverageResolveResponse | null
coverageLoading: boolean
coverageError: string | null
selectionExporting: boolean
selectionExportError: string | null
latestSelectionExportPath: string | null
@@ -602,6 +648,9 @@ export function MapWorkspace({
mapSelectionResult,
mapSelectionLoading,
mapSelectionError,
coverage,
coverageLoading,
coverageError,
selectionExporting,
selectionExportError,
latestSelectionExportPath,
@@ -819,6 +868,15 @@ export function MapWorkspace({
[availableMapDatasets, regionalScopeSelected, selectedMapAreaId, themeDatasetMap],
)
const activeTheme = DATA_THEMES.find((theme) => theme.id === activeThemeId) ?? DATA_THEMES[0]
const activeCoverageTheme = COVERAGE_THEME_BY_MAP_THEME[activeTheme.id]
const activeCoverageItems = coverage?.items.filter((item) => item.theme === activeCoverageTheme) ?? []
const coverageCounts = useMemo(
() => coverage?.items.reduce<Record<CoverageStatus, number>>(
(counts, item) => ({ ...counts, [item.status]: counts[item.status] + 1 }),
{ operational: 0, partial: 0, not_configured: 0, unsupported: 0 },
) ?? { operational: 0, partial: 0, not_configured: 0, unsupported: 0 },
[coverage],
)
const onDemandProductMap = useMemo(
() => {
const result = new Map<DataThemeId, OnDemandMapProduct>()
@@ -2527,6 +2585,50 @@ export function MapWorkspace({
</div>
) : null}
{mapSelectionBbox ? (
<section className="coverage-resolution-surface" aria-label="Datadekking van de kaartselectie">
<div className="panel-title-row">
<div>
<p className="eyebrow">Dekking van deze selectie</p>
<h3>{activeTheme.label}</h3>
</div>
{coverageLoading ? <span className="count-pill">controleren</span> : null}
</div>
{coverageError ? <p className="error">{coverageError}</p> : null}
{coverage ? (
<>
<div className="coverage-zone-row">
{coverage.intersected_zones.map((zone) => (
<span key={zone}>{coverageZoneLabel(zone)}</span>
))}
{coverage.outside_supported_scope ? <span className="coverage-zone-warning">deels buiten scope</span> : null}
</div>
<div className="coverage-active-theme-grid">
{activeCoverageItems.map((item) => (
<div className={`coverage-status-item coverage-status-${item.status}`} key={`${item.zone}:${item.theme}`}>
<span>{coverageZoneLabel(item.zone)}</span>
<strong>{coverageStatusLabel(item.status)}</strong>
<small>{item.source_names.join(', ') || 'Geen broncontract'}</small>
</div>
))}
{activeCoverageItems.length === 0 ? (
<p className="muted">Deze selectie raakt geen bewaarde Belgische land- of zeezone.</p>
) : null}
</div>
<div className="coverage-summary-row" aria-label="Samenvatting van alle themas">
<span>{coverageCounts.operational} beschikbaar</span>
<span>{coverageCounts.partial} gedeeltelijk</span>
<span>{coverageCounts.not_configured} niet gekoppeld</span>
<span>{coverageCounts.unsupported} niet ondersteund</span>
</div>
{coverage.warnings.map((warning) => <p className="muted" key={warning}>{warning}</p>)}
</>
) : !coverageLoading && !coverageError ? (
<p className="muted">De dekkingsmatrix wordt bepaald zodra de selectie volledig is.</p>
) : null}
</section>
) : null}
<div className="map-inspection-surface">
<div className="gis-test-run-surface" aria-label="Operationele GIS-controle">
<div className="panel-title-row">