feat: add governed hydrology and historical imagery
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-15 12:17:45 +02:00
parent 5b1156e989
commit fb38eb3e91
32 changed files with 1646 additions and 55 deletions
+42 -5
View File
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from 'react'
import GeoMap from '../GeoMap'
import type { AreaRead, DatasetCreateResponse, DetectionQaResult, MapViewportState, ProjectRead, QaComparisonResult, VectorSelectionBBox, VectorSelectionMetric, VectorSelectionResponse } from '../../types'
import type { AreaRead, DatasetCreateResponse, DetectionQaResult, MapViewportState, OrthophotoAcquisitionResult, OrthophotoProductRead, ProjectRead, QaComparisonResult, VectorSelectionBBox, VectorSelectionMetric, VectorSelectionResponse } from '../../types'
import { featureCollectionBounds } from '../../lib/geojsonBounds'
import { useMapThemeSelectionInsights } from '../../hooks/useMapThemeSelectionInsights'
import { useTemporalComparison } from '../../hooks/useTemporalComparison'
@@ -459,6 +459,10 @@ interface MapWorkspaceProps {
orthophotoAnalysisRunning: boolean
orthophotoAnalysisQuality: DetectionQaResult | null
orthophotoAnalysisDetectionCount: number | null
orthophotoProducts: OrthophotoProductRead[]
selectedOrthophotoProductKey: string
orthophotoResult: OrthophotoAcquisitionResult | null
orthophotoImageUrl: string | null
availableMapDatasets: DatasetCreateResponse[]
selectedMapDatasetId: string
onSelectMapArea: (areaId: string) => void
@@ -479,6 +483,7 @@ interface MapWorkspaceProps {
onRunMapSelectionQa: (candidateDataset?: DatasetCreateResponse | null) => Promise<QaComparisonResult | null>
onOpenMapSelectionQualityEvidence: () => void
onRunOrthophotoAnalysis: (bbox: VectorSelectionBBox) => Promise<boolean>
onSelectOrthophotoProduct: (productKey: string) => void
onClearQualityEvidence?: () => void
}
@@ -534,6 +539,10 @@ export function MapWorkspace({
orthophotoAnalysisRunning,
orthophotoAnalysisQuality,
orthophotoAnalysisDetectionCount,
orthophotoProducts,
selectedOrthophotoProductKey,
orthophotoResult,
orthophotoImageUrl,
availableMapDatasets,
selectedMapDatasetId,
onSelectMapArea,
@@ -554,6 +563,7 @@ export function MapWorkspace({
onRunMapSelectionQa,
onOpenMapSelectionQualityEvidence,
onRunOrthophotoAnalysis,
onSelectOrthophotoProduct,
onClearQualityEvidence,
}: MapWorkspaceProps): JSX.Element {
const [advancedMode, setAdvancedMode] = useState(false)
@@ -615,6 +625,15 @@ export function MapWorkspace({
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 activeThemeDataset = themeDatasetMap[activeTheme.id]
const activeScopeProject = projects.find((project) => project.id === selectedProjectId) ?? null
const activeScopeLabel = activeScopeProject ? operationalScopeProjectLabel(activeScopeProject) : 'Werkgebied'
@@ -1173,6 +1192,7 @@ export function MapWorkspace({
areaData={areaFeatureCollection}
selectedFeature={selectedFeature}
selectionData={analysisMode === 'current' ? mapSelectionResult?.geojson ?? null : null}
imageOverlay={orthophotoImageOverlay}
selectionBbox={mapSelectionBbox}
bboxSelectionMode={bboxSelectionMode}
visible={mapLayerVisible}
@@ -1188,6 +1208,7 @@ export function MapWorkspace({
/>
<div className="geo-map-legend" aria-label="Kaartlegende">
<span><i className="geo-legend-area" /> Werkgebied</span>
{orthophotoImageOverlay ? <span><i className="geo-legend-imagery" /> {orthophotoImageOverlay.label}</span> : null}
{analysisOverlayActive ? (
<>
<span><i className="geo-legend-layer geo-legend-layer-buildings" /> AI-kandidaten</span>
@@ -1233,9 +1254,25 @@ export function MapWorkspace({
<div className={`geo-image-analysis geo-image-analysis-${orthophotoAnalysisStage}`}>
<div>
<span>Beeldanalyse</span>
<strong>Gebouwen herkennen op luchtbeeld</strong>
<small>Officieel luchtbeeld, lokaal AI-model en automatische controle met GRB.</small>
<strong>{selectedOrthophotoProduct?.supports_detection ? 'Gebouwen herkennen op luchtbeeld' : 'Historisch luchtbeeld bekijken'}</strong>
<small>
{selectedOrthophotoProduct?.supports_detection
? 'Officieel luchtbeeld, lokaal AI-model en automatische controle met GRB.'
: 'Officieel historisch mozaïek. Geen vergelijking met de actuele GRB-toestand.'}
</small>
</div>
<label className="geo-orthophoto-product">
<span>Luchtbeeld</span>
<select
value={selectedOrthophotoProductKey}
onChange={(event) => onSelectOrthophotoProduct(event.target.value)}
disabled={orthophotoAnalysisRunning}
>
{orthophotoProducts.map((product) => (
<option key={product.key} value={product.key}>{product.display_name}</option>
))}
</select>
</label>
<button
className="primary-action"
disabled={orthophotoAnalysisRunning}
@@ -1249,8 +1286,8 @@ export function MapWorkspace({
: orthophotoAnalysisStage === 'validating'
? 'Controleren...'
: orthophotoAnalysisStage === 'complete'
? 'Opnieuw analyseren'
: 'Herken gebouwen'}
? selectedOrthophotoProduct?.supports_detection ? 'Opnieuw analyseren' : 'Opnieuw tonen'
: selectedOrthophotoProduct?.supports_detection ? 'Herken gebouwen' : 'Toon luchtbeeld'}
</button>
{orthophotoAnalysisStatus ? <p role="status">{orthophotoAnalysisStatus}</p> : null}
{orthophotoAnalysisError ? <p className="error" role="alert">{orthophotoAnalysisError}</p> : null}