1315 lines
52 KiB
TypeScript
1315 lines
52 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react'
|
|
import './styles/app.css'
|
|
import './styles/premium.css'
|
|
import { ChangeDetectionPanel } from './components/analysis/ChangeDetectionPanel'
|
|
import { DatasetPanel } from './components/datasets/DatasetPanel'
|
|
import { DetectionLab } from './components/detection/DetectionLab'
|
|
import { ExportCenter } from './components/exports/ExportCenter'
|
|
import { ExportPreview } from './components/exports/ExportPreview'
|
|
import { WorkbenchInspector } from './components/inspector/WorkbenchInspector'
|
|
import { MapWorkspace } from './components/map/MapWorkspace'
|
|
import { AreaPanel } from './components/project/AreaPanel'
|
|
import { ProjectPanel } from './components/project/ProjectPanel'
|
|
import { QualityResultsPanel } from './components/quality/QualityResultsPanel'
|
|
import { WorkbenchStatusStrip } from './components/WorkbenchStatusStrip'
|
|
import type { DatasetCreateResponse } from './types'
|
|
import { ProviderPanel } from './components/providers/ProviderPanel'
|
|
import { SegmentationLab } from './components/segmentation/SegmentationLab'
|
|
import { useChangeDetectionWorkflow } from './hooks/useChangeDetectionWorkflow'
|
|
import { useDemoWorkflow } from './hooks/useDemoWorkflow'
|
|
import { useDetectionWorkflow } from './hooks/useDetectionWorkflow'
|
|
import { useDatasetWorkflow } from './hooks/useDatasetWorkflow'
|
|
import { useExportWorkflow } from './hooks/useExportWorkflow'
|
|
import { useMapSelectionDataset } from './hooks/useMapSelectionDataset'
|
|
import { useMapSelectionQa } from './hooks/useMapSelectionQa'
|
|
import { useMapWorkspaceState } from './hooks/useMapWorkspaceState'
|
|
import { useMapSelectionExtract } from './hooks/useMapSelectionExtract'
|
|
import { useMapOrthophotoAnalysis } from './hooks/useMapOrthophotoAnalysis'
|
|
import { useProviderCapabilities } from './hooks/useProviderCapabilities'
|
|
import { useProjectWorkspace } from './hooks/useProjectWorkspace'
|
|
import { useQualityWorkflow } from './hooks/useQualityWorkflow'
|
|
import { useSegmentationWorkflow } from './hooks/useSegmentationWorkflow'
|
|
import { useWorkbenchBootstrap } from './hooks/useWorkbenchBootstrap'
|
|
import { useViewportVectorLayer } from './hooks/useViewportVectorLayer'
|
|
import { getDatasetDisplayName } from './lib/datasetDisplay'
|
|
import { REGIONAL_WORKSPACE_LABEL, REGIONAL_WORKSPACE_PROJECT_NAME } from './config/primaryFocus'
|
|
|
|
function isVectorDatasetType(datasetType: string): boolean {
|
|
return datasetType === 'vector' || datasetType === 'geojson'
|
|
}
|
|
|
|
type WorkspaceKey = 'overview' | 'data' | 'map' | 'analysis' | 'ai' | 'exports' | 'system'
|
|
|
|
const workspaceNavItems: Array<{ key: WorkspaceKey; label: string; description: string }> = [
|
|
{ key: 'overview', label: 'Status', description: 'Beschikbaarheid en aandachtspunten' },
|
|
{ key: 'data', label: 'Bronnen', description: 'Gebieden en ingeladen gegevens' },
|
|
{ key: 'map', label: 'Kaart', description: 'Selecteren, uitlezen en vergelijken' },
|
|
{ key: 'analysis', label: 'Kwaliteit', description: 'Resultaten controleren' },
|
|
{ key: 'ai', label: 'Beeldanalyse', description: 'Gebouwen herkennen op luchtbeelden' },
|
|
{ key: 'exports', label: 'Downloads', description: 'Resultaten bewaren en delen' },
|
|
{ key: 'system', label: 'Beheer', description: 'Databronnen en systeemstatus' },
|
|
]
|
|
|
|
const workspaceNavGroups: Array<{ label: string; keys: WorkspaceKey[] }> = [
|
|
{ label: 'Verkennen', keys: ['map', 'data'] },
|
|
{ label: 'Analyseren', keys: ['analysis', 'ai'] },
|
|
{ label: 'Afronden', keys: ['exports'] },
|
|
{ label: 'Beheer', keys: ['overview', 'system'] },
|
|
]
|
|
|
|
function App(): JSX.Element {
|
|
const [activeWorkspace, setActiveWorkspace] = useState<WorkspaceKey>('map')
|
|
const [inspectorOpen, setInspectorOpen] = useState(false)
|
|
const [mapContentMode, setMapContentMode] = useState<'dataset' | 'analysis'>('dataset')
|
|
useEffect(() => {
|
|
window.scrollTo({ top: 0, left: 0 })
|
|
setInspectorOpen(false)
|
|
}, [activeWorkspace])
|
|
const {
|
|
projects,
|
|
selectedProject,
|
|
selectedProjectId,
|
|
areas,
|
|
datasets,
|
|
loadingProjects,
|
|
loadingAreas,
|
|
loadingDatasets,
|
|
errorMessage,
|
|
projectForm,
|
|
areaForm,
|
|
loadProjects,
|
|
loadProjectData,
|
|
createProject,
|
|
createArea,
|
|
resetProjectData,
|
|
setSelectedProjectId,
|
|
setErrorMessage,
|
|
setProjectForm,
|
|
setAreaForm,
|
|
} = useProjectWorkspace()
|
|
const selectProject = (projectId: string) => {
|
|
setMapContentMode('dataset')
|
|
setSelectedProjectId(projectId)
|
|
}
|
|
const {
|
|
selectedDatasetId,
|
|
selectedDataset,
|
|
selectedDatasetSummary,
|
|
selectedRasterMetadata,
|
|
selectedRasterStats,
|
|
datasetContent,
|
|
jobs,
|
|
rasterPreview,
|
|
latestRasterTileManifestPath,
|
|
latestRasterTileManifest,
|
|
selectedIntersectTargetId,
|
|
selectedClipAreaId,
|
|
rasterTileSize,
|
|
rasterTileOverlap,
|
|
rasterTileOutputName,
|
|
rasterReprojectCrs,
|
|
rasterReprojectResampling,
|
|
ndviNirBand,
|
|
ndviRedBand,
|
|
ndwiGreenBand,
|
|
ndwiNirBand,
|
|
ndbiSwirBand,
|
|
ndbiNirBand,
|
|
loadingDatasetDetails,
|
|
datasetDetailError,
|
|
datasetForm,
|
|
isRasterTileInputValid,
|
|
loadDatasetJobs,
|
|
loadDatasetDetails,
|
|
uploadDataset,
|
|
runVectorClip,
|
|
runVectorBuffer,
|
|
runVectorIntersect,
|
|
runRasterInspect,
|
|
runRasterPreview,
|
|
runRasterStats,
|
|
runRasterReproject,
|
|
runRasterClip,
|
|
runRasterTile,
|
|
runRasterNdvi,
|
|
runRasterNdwi,
|
|
runRasterNdbi,
|
|
pickDerivedDataset,
|
|
refreshMetadata,
|
|
resetDatasetForProject,
|
|
setSelectedDatasetId,
|
|
setSelectedIntersectTargetId,
|
|
setSelectedClipAreaId,
|
|
setRasterTileSize,
|
|
setRasterTileOverlap,
|
|
setRasterTileOutputName,
|
|
setRasterReprojectCrs,
|
|
setRasterReprojectResampling,
|
|
setNdviNirBand,
|
|
setNdviRedBand,
|
|
setNdwiGreenBand,
|
|
setNdwiNirBand,
|
|
setNdbiSwirBand,
|
|
setNdbiNirBand,
|
|
setDatasetForm,
|
|
} = useDatasetWorkflow({
|
|
selectedProjectId,
|
|
areas,
|
|
datasets,
|
|
loadProjectData,
|
|
setErrorMessage,
|
|
isVectorDatasetType,
|
|
})
|
|
const rasterUnavailableMessage = useMemo(() => {
|
|
if (!selectedDataset || selectedDataset.dataset_type !== 'raster' || !selectedDataset.metadata_json) {
|
|
return null
|
|
}
|
|
const metadata = selectedDataset.metadata_json as Record<string, unknown>
|
|
const processingCode = metadata['processing_code']
|
|
if (processingCode === 'RASTER_PROCESSING_UNAVAILABLE') {
|
|
return String(metadata['processing_error'] ?? 'Raster processing unavailable.')
|
|
}
|
|
return null
|
|
}, [selectedDataset])
|
|
|
|
const availableVectorTargets = useMemo(
|
|
() => datasets.filter((item) => item.id !== selectedDatasetId && isVectorDatasetType(item.dataset_type)),
|
|
[datasets, selectedDatasetId],
|
|
)
|
|
const availableVectorDatasets = useMemo(() => datasets.filter((item) => isVectorDatasetType(item.dataset_type)), [datasets])
|
|
const availableMapDatasets = useMemo(
|
|
() => datasets.filter((dataset) => isVectorDatasetType(dataset.dataset_type) && dataset.status === 'ready'),
|
|
[datasets],
|
|
)
|
|
const referenceDatasets = useMemo(
|
|
() => availableVectorDatasets.filter((item) => item.dataset_role === 'reference'),
|
|
[availableVectorDatasets],
|
|
)
|
|
const candidateDatasets = useMemo(
|
|
() => availableVectorDatasets.filter((item) => item.dataset_role !== 'reference'),
|
|
[availableVectorDatasets],
|
|
)
|
|
const rasterDatasets = useMemo(() => datasets.filter((item) => item.dataset_type === 'raster'), [datasets])
|
|
const {
|
|
providers,
|
|
loadingCapabilities,
|
|
capabilitiesError,
|
|
loadCapabilities,
|
|
} = useProviderCapabilities()
|
|
const {
|
|
changeSourceDatasetId,
|
|
changeTargetDatasetId,
|
|
changeIouThreshold,
|
|
changeIncludeUnchanged,
|
|
runningChangeDetection,
|
|
changeDetectionResult,
|
|
changeDetectionError,
|
|
runChangeDetection,
|
|
setChangeSourceDatasetId,
|
|
setChangeTargetDatasetId,
|
|
setChangeIouThreshold,
|
|
setChangeIncludeUnchanged,
|
|
} = useChangeDetectionWorkflow({
|
|
selectedProjectId,
|
|
availableVectorDatasets,
|
|
loadDatasetJobs,
|
|
})
|
|
const {
|
|
qaCandidateDatasetId,
|
|
qaReferenceDatasetId,
|
|
qaAreaId,
|
|
qaIouThreshold,
|
|
qaRunning,
|
|
qaResult,
|
|
qaError,
|
|
qualityChecks,
|
|
qualityChecksError,
|
|
qualityEvidenceGeoJson,
|
|
qualityEvidenceLoading,
|
|
qualityEvidenceError,
|
|
loadQualityChecks,
|
|
loadQualityEvidenceGeoJson,
|
|
clearQualityEvidenceGeoJson,
|
|
runQaComparison,
|
|
setQaCandidateDatasetId,
|
|
setQaReferenceDatasetId,
|
|
setQaAreaId,
|
|
setQaIouThreshold,
|
|
} = useQualityWorkflow({
|
|
selectedProjectId,
|
|
loadProjectData,
|
|
})
|
|
const {
|
|
detectionModels,
|
|
modelAssets,
|
|
loadingDetectionModels,
|
|
detectionModelError,
|
|
modelAssetError,
|
|
selectedDetectionDatasetId,
|
|
selectedDetectionModelId,
|
|
selectedModelAssetId,
|
|
detectionTileManifestPath,
|
|
detectionConfidenceThreshold,
|
|
runningDetection,
|
|
detectionRunResult,
|
|
detectionRunError,
|
|
detectionRuns,
|
|
selectedDetectionRunId,
|
|
detectionItems,
|
|
detectionGeoJson,
|
|
detectionClassFilter,
|
|
detectionMinConfidenceFilter,
|
|
loadingDetectionResults,
|
|
detectionReferenceDatasetId,
|
|
detectionQaResult,
|
|
detectionQaError,
|
|
runningDetectionQa,
|
|
yoloPreflight,
|
|
loadingYoloPreflight,
|
|
yoloPreflightError,
|
|
calibrationThresholdText,
|
|
runningDetectionCalibration,
|
|
detectionCalibrationRows,
|
|
detectionCalibrationError,
|
|
detectionWorkflowStage,
|
|
loadDetectionModels,
|
|
loadYoloPreflight,
|
|
loadDetectionRuns,
|
|
loadDetectionResults,
|
|
runDetection,
|
|
uploadDetectionRaster,
|
|
prepareAndRunDetection,
|
|
compareDetectionRunWithReference,
|
|
runDetectionQa,
|
|
runDetectionCalibration,
|
|
applyDetectionOperatorProfile,
|
|
resetDetectionForProject,
|
|
setSelectedDetectionDatasetId,
|
|
setSelectedDetectionModelId,
|
|
setSelectedModelAssetId,
|
|
setDetectionTileManifestPath,
|
|
setDetectionConfidenceThreshold,
|
|
setSelectedDetectionRunId,
|
|
setDetectionClassFilter,
|
|
setDetectionMinConfidenceFilter,
|
|
setDetectionReferenceDatasetId,
|
|
setCalibrationThresholdText,
|
|
} = useDetectionWorkflow({
|
|
selectedProjectId,
|
|
rasterDatasets,
|
|
qaIouThreshold,
|
|
loadProjectData,
|
|
loadQualityChecks,
|
|
})
|
|
const useRasterTileManifestForDetection = () => {
|
|
const manifestPath = latestRasterTileManifestPath.trim()
|
|
if (!manifestPath || !selectedDataset || selectedDataset.dataset_type !== 'raster') {
|
|
return
|
|
}
|
|
setDetectionTileManifestPath(manifestPath)
|
|
setSelectedDetectionDatasetId(selectedDataset.id)
|
|
setSelectedDetectionModelId('yolo-configured')
|
|
setDetectionConfidenceThreshold(0.25)
|
|
loadYoloPreflight(manifestPath).catch(() => null)
|
|
setActiveWorkspace('ai')
|
|
}
|
|
const {
|
|
segmentationModels,
|
|
loadingSegmentationModels,
|
|
segmentationModelError,
|
|
selectedSegmentationDatasetId,
|
|
selectedSegmentationModelId,
|
|
selectedSegmentationModel,
|
|
segmentationTileManifestPath,
|
|
segmentationConfidenceThreshold,
|
|
runningSegmentation,
|
|
segmentationRunResult,
|
|
segmentationRunError,
|
|
segmentationRuns,
|
|
selectedSegmentationRunId,
|
|
segmentationItems,
|
|
segmentationGeoJson,
|
|
segmentationClassFilter,
|
|
segmentationMinConfidenceFilter,
|
|
loadingSegmentationResults,
|
|
segmentationReferenceDatasetId,
|
|
segmentationQaResult,
|
|
segmentationQaError,
|
|
runningSegmentationQa,
|
|
loadSegmentationModels,
|
|
loadSegmentationRuns,
|
|
loadSegmentationResults,
|
|
runSegmentation,
|
|
runSegmentationQa,
|
|
resetSegmentationForProject,
|
|
setSelectedSegmentationDatasetId,
|
|
setSelectedSegmentationModelId,
|
|
setSegmentationTileManifestPath,
|
|
setSegmentationConfidenceThreshold,
|
|
setSelectedSegmentationRunId,
|
|
setSegmentationClassFilter,
|
|
setSegmentationMinConfidenceFilter,
|
|
setSegmentationReferenceDatasetId,
|
|
} = useSegmentationWorkflow({
|
|
selectedProjectId,
|
|
rasterDatasets,
|
|
qaIouThreshold,
|
|
loadProjectData,
|
|
loadQualityChecks,
|
|
})
|
|
const viewportVectorLayer = useViewportVectorLayer({
|
|
selectedProjectId,
|
|
selectedDataset,
|
|
featureCount: selectedDatasetSummary?.feature_count ?? selectedDataset?.feature_count ?? null,
|
|
isVectorDatasetType,
|
|
})
|
|
const analysisMapLayerAvailable = Boolean(changeDetectionResult?.geojson || segmentationGeoJson || detectionGeoJson)
|
|
const analysisMapLayerActive = mapContentMode === 'analysis' && analysisMapLayerAvailable
|
|
const viewportVectorLayerActive = viewportVectorLayer.enabled && !analysisMapLayerActive
|
|
const datasetMapContent = viewportVectorLayer.enabled ? viewportVectorLayer.data : datasetContent
|
|
const useRasterTileManifestForSegmentation = () => {
|
|
const manifestPath = latestRasterTileManifestPath.trim()
|
|
if (!manifestPath || !selectedDataset || selectedDataset.dataset_type !== 'raster') {
|
|
return
|
|
}
|
|
setSegmentationTileManifestPath(manifestPath)
|
|
setSelectedSegmentationDatasetId(selectedDataset.id)
|
|
setActiveWorkspace('ai')
|
|
}
|
|
const {
|
|
exports,
|
|
latestExport,
|
|
exportError,
|
|
loadingExports,
|
|
exporting,
|
|
selectionExporting,
|
|
selectionExportError,
|
|
latestSelectionExport,
|
|
exportPreview,
|
|
loadExports,
|
|
exportSelectedDatasetGeoJson,
|
|
exportSelectedDetectionRunGeoJson,
|
|
exportSelectedSegmentationRunGeoJson,
|
|
exportMapSelectionGeoJson,
|
|
exportProjectMetadata,
|
|
exportProjectReport,
|
|
previewExportContent,
|
|
downloadExportArtifact,
|
|
resetExportsForProject,
|
|
} = useExportWorkflow({
|
|
selectedProjectId,
|
|
selectedDataset,
|
|
selectedDetectionRunId,
|
|
selectedSegmentationRunId,
|
|
isVectorDatasetType,
|
|
})
|
|
const {
|
|
mapLayerVisible,
|
|
mapLayerOpacity,
|
|
selectedMapAreaId,
|
|
areaLayerVisible,
|
|
areaLayerOpacity,
|
|
selectedMapFeature,
|
|
areaFeatureCollection,
|
|
mapFeatureCollection,
|
|
mapLayerLabel,
|
|
mapFeatureCount,
|
|
areaFeatureCount,
|
|
setMapLayerVisible,
|
|
setMapLayerOpacity,
|
|
setSelectedMapAreaId,
|
|
setAreaLayerVisible,
|
|
setAreaLayerOpacity,
|
|
setSelectedMapFeature,
|
|
} = useMapWorkspaceState({
|
|
areas,
|
|
changeDetectionGeoJson: analysisMapLayerActive ? changeDetectionResult?.geojson ?? null : null,
|
|
segmentationGeoJson: analysisMapLayerActive ? segmentationGeoJson : null,
|
|
detectionGeoJson: analysisMapLayerActive ? detectionGeoJson : null,
|
|
datasetContent: datasetMapContent,
|
|
selectedDataset,
|
|
datasetLayerActive: Boolean(selectedDataset && isVectorDatasetType(selectedDataset.dataset_type)),
|
|
})
|
|
const {
|
|
mapSelectionBbox,
|
|
mapSelectionResult,
|
|
mapSelectionLoading,
|
|
mapSelectionError,
|
|
runMapSelectionExtract,
|
|
resetMapSelectionExtract,
|
|
setMapSelectionBbox,
|
|
} = useMapSelectionExtract({
|
|
selectedProjectId,
|
|
selectedDataset,
|
|
isVectorDatasetType,
|
|
})
|
|
const {
|
|
selectionDatasetSaving,
|
|
selectionDatasetError,
|
|
latestSelectionDataset,
|
|
deriveMapSelectionDataset,
|
|
} = useMapSelectionDataset({
|
|
selectedProjectId,
|
|
selectedDataset,
|
|
isVectorDatasetType,
|
|
loadProjectData,
|
|
loadDatasetDetails,
|
|
setMapLayerVisible,
|
|
})
|
|
const {
|
|
selectedMapQaReferenceDatasetId,
|
|
mapSelectionQaRunning,
|
|
mapSelectionQaError,
|
|
mapSelectionQaResult,
|
|
latestMapSelectionQualityCheckId,
|
|
runMapSelectionQa,
|
|
setSelectedMapQaReferenceDatasetId,
|
|
} = useMapSelectionQa({
|
|
selectedProjectId,
|
|
latestSelectionDataset,
|
|
loadQualityChecks,
|
|
loadProjectData,
|
|
})
|
|
const mapOrthophotoAnalysis = useMapOrthophotoAnalysis({
|
|
selectedProjectId,
|
|
selectedAreaId: areas.find((area) => /vervoerregio|operationele grens/i.test(area.name))?.id ?? selectedMapAreaId,
|
|
selectionBbox: mapSelectionBbox,
|
|
datasets,
|
|
loadProjectData,
|
|
prepareAndRunDetection: (datasetId) => prepareAndRunDetection(datasetId, 'yolo-configured'),
|
|
compareDetectionRunWithReference: (analysisRunId, referenceDatasetId, iouThreshold) =>
|
|
compareDetectionRunWithReference(analysisRunId, referenceDatasetId, false, iouThreshold),
|
|
onAnalysisReady: () => {
|
|
setMapContentMode('analysis')
|
|
setMapLayerVisible(true)
|
|
setActiveWorkspace('map')
|
|
},
|
|
})
|
|
const openMapSelectionQualityEvidence = () => {
|
|
setActiveWorkspace('analysis')
|
|
}
|
|
const openQualityEvidenceOnMap = async (qualityCheckId: string) => {
|
|
const result = await loadQualityEvidenceGeoJson(qualityCheckId)
|
|
if (result) {
|
|
setMapLayerVisible(true)
|
|
setActiveWorkspace('map')
|
|
}
|
|
}
|
|
const {
|
|
loadingDemoWorkflow,
|
|
demoWorkflowMessage,
|
|
loadDemoWorkflow,
|
|
} = useDemoWorkflow({
|
|
loadProjects,
|
|
loadProjectData,
|
|
loadDatasetDetails,
|
|
loadDetectionRuns,
|
|
loadSegmentationRuns,
|
|
loadQualityChecks,
|
|
loadExports,
|
|
setSelectedProjectId: selectProject,
|
|
setSelectedDatasetId,
|
|
setSelectedMapAreaId,
|
|
setQaCandidateDatasetId,
|
|
setQaReferenceDatasetId,
|
|
setQaAreaId,
|
|
setSelectedDetectionDatasetId,
|
|
setDetectionReferenceDatasetId,
|
|
setSelectedSegmentationDatasetId,
|
|
setSegmentationReferenceDatasetId,
|
|
setErrorMessage,
|
|
})
|
|
|
|
useWorkbenchBootstrap({
|
|
selectedProjectId,
|
|
selectedDetectionRunId,
|
|
detectionClassFilter,
|
|
detectionMinConfidenceFilter,
|
|
selectedSegmentationRunId,
|
|
segmentationClassFilter,
|
|
segmentationMinConfidenceFilter,
|
|
loadProjects,
|
|
loadCapabilities,
|
|
loadDetectionModels,
|
|
loadSegmentationModels,
|
|
loadProjectData,
|
|
loadDetectionRuns,
|
|
loadSegmentationRuns,
|
|
loadQualityChecks,
|
|
loadExports,
|
|
loadDetectionResults,
|
|
loadSegmentationResults,
|
|
resetProjectData,
|
|
resetDatasetForProject,
|
|
resetDetectionForProject,
|
|
resetSegmentationForProject,
|
|
resetExportsForProject,
|
|
})
|
|
|
|
const selectedArea = areas.find((area) => area.id === selectedMapAreaId) ?? null
|
|
const activeWorkspaceItem = workspaceNavItems.find((item) => item.key === activeWorkspace) ?? workspaceNavItems[0]
|
|
const mapLayerSourceLabel = useMemo(() => {
|
|
if (analysisMapLayerActive && changeDetectionResult?.geojson) {
|
|
return 'Veranderingsanalyse'
|
|
}
|
|
if (analysisMapLayerActive && segmentationGeoJson) {
|
|
return 'Segmentatierun'
|
|
}
|
|
if (analysisMapLayerActive && detectionGeoJson) {
|
|
return 'Detectierun'
|
|
}
|
|
if ((datasetMapContent || viewportVectorLayer.enabled) && selectedDataset) {
|
|
return `${selectedDataset.dataset_type}-dataset`
|
|
}
|
|
return 'Geen actieve gegevens- of analyselaag'
|
|
}, [analysisMapLayerActive, changeDetectionResult?.geojson, datasetMapContent, detectionGeoJson, segmentationGeoJson, selectedDataset, viewportVectorLayer.enabled])
|
|
const mapLayerProvenance = useMemo(() => {
|
|
if (analysisMapLayerActive && changeDetectionResult?.geojson) {
|
|
return `source ${changeSourceDatasetId || 'n/a'} -> target ${changeTargetDatasetId || 'n/a'}`
|
|
}
|
|
if (analysisMapLayerActive && segmentationGeoJson) {
|
|
return selectedSegmentationRunId ? `analysis run ${selectedSegmentationRunId}` : 'segmentation results loaded'
|
|
}
|
|
if (analysisMapLayerActive && detectionGeoJson) {
|
|
return selectedDetectionRunId ? `analysis run ${selectedDetectionRunId}` : 'detection results loaded'
|
|
}
|
|
if ((datasetMapContent || viewportVectorLayer.enabled) && selectedDataset) {
|
|
return `${selectedDataset.dataset_role ?? 'source'} / ${selectedDataset.source_name ?? selectedDataset.source}`
|
|
}
|
|
return 'Open a dataset, detection run, segmentation run or change result to draw it here.'
|
|
}, [
|
|
analysisMapLayerActive,
|
|
changeDetectionResult?.geojson,
|
|
changeSourceDatasetId,
|
|
changeTargetDatasetId,
|
|
datasetMapContent,
|
|
detectionGeoJson,
|
|
segmentationGeoJson,
|
|
selectedDataset,
|
|
selectedDetectionRunId,
|
|
selectedSegmentationRunId,
|
|
viewportVectorLayer.enabled,
|
|
])
|
|
const openDatasetInMap = (dataset: DatasetCreateResponse) => {
|
|
if (selectedProjectId) {
|
|
loadDatasetDetails(selectedProjectId, dataset)
|
|
}
|
|
setMapContentMode('dataset')
|
|
setMapLayerVisible(true)
|
|
setActiveWorkspace('map')
|
|
}
|
|
const runGuidedDetection = async () => {
|
|
const completed = await prepareAndRunDetection()
|
|
if (completed) {
|
|
setMapContentMode('analysis')
|
|
setMapLayerVisible(true)
|
|
setActiveWorkspace('map')
|
|
}
|
|
}
|
|
const openDetectionResultsOnMap = () => {
|
|
if (!detectionGeoJson) {
|
|
return
|
|
}
|
|
setMapContentMode('analysis')
|
|
setMapLayerVisible(true)
|
|
setActiveWorkspace('map')
|
|
}
|
|
const openDatasetExport = (dataset: DatasetCreateResponse) => {
|
|
if (selectedProjectId) {
|
|
loadDatasetDetails(selectedProjectId, dataset)
|
|
}
|
|
setActiveWorkspace('exports')
|
|
}
|
|
const openWorkflowGuidanceStep = (target: WorkspaceKey) => {
|
|
if (target === 'map' && availableMapDatasets.length > 0 && !mapFeatureCollection) {
|
|
openDatasetInMap(availableMapDatasets[0])
|
|
return
|
|
}
|
|
if (target === 'exports' && availableMapDatasets.length > 0 && !selectedDataset) {
|
|
openDatasetExport(availableMapDatasets[0])
|
|
return
|
|
}
|
|
setActiveWorkspace(target)
|
|
}
|
|
const hasAnalysisOutput = qualityChecks.length > 0 || Boolean(changeDetectionResult) || detectionItems.length > 0 || segmentationItems.length > 0
|
|
const hasMapContext = mapFeatureCount > 0 || areaFeatureCount > 0
|
|
const workflowGuidanceComplete = Boolean(selectedProjectId) && datasets.length > 0 && hasMapContext && hasAnalysisOutput && exports.length > 0
|
|
const recommendedWorkflowTarget: WorkspaceKey = !selectedProjectId
|
|
? 'data'
|
|
: datasets.length === 0
|
|
? 'data'
|
|
: !hasMapContext
|
|
? 'map'
|
|
: !hasAnalysisOutput
|
|
? 'analysis'
|
|
: exports.length === 0
|
|
? 'exports'
|
|
: 'exports'
|
|
const workflowGuidanceSteps: Array<{
|
|
step: string
|
|
title: string
|
|
detail: string
|
|
status: string
|
|
ready: boolean
|
|
target: WorkspaceKey
|
|
}> = [
|
|
{
|
|
step: '1',
|
|
title: 'Project & AOI',
|
|
detail: selectedProjectId ? `${areas.length} AOI record${areas.length === 1 ? '' : 's'} available` : 'Create or load a project context',
|
|
status: selectedProjectId ? 'ready' : 'next',
|
|
ready: Boolean(selectedProjectId),
|
|
target: 'data',
|
|
},
|
|
{
|
|
step: '2',
|
|
title: 'Data',
|
|
detail: datasets.length > 0 ? `${datasets.length} dataset${datasets.length === 1 ? '' : 's'} loaded` : 'Upload source and reference datasets',
|
|
status: datasets.length > 0 ? 'ready' : 'waiting',
|
|
ready: datasets.length > 0,
|
|
target: 'data',
|
|
},
|
|
{
|
|
step: '3',
|
|
title: 'Map',
|
|
detail: hasMapContext
|
|
? mapFeatureCount > 0
|
|
? `${mapFeatureCount} layer feature${mapFeatureCount === 1 ? '' : 's'}${areaFeatureCount > 0 ? ' + AOI' : ''}`
|
|
: 'AOI loaded'
|
|
: 'Inspect AOI and selected layer',
|
|
status: hasMapContext ? 'ready' : 'waiting',
|
|
ready: hasMapContext,
|
|
target: 'map',
|
|
},
|
|
{
|
|
step: '4',
|
|
title: 'QA / AI',
|
|
detail: hasAnalysisOutput ? 'QA, change, detection or segmentation output exists' : 'Run checks after data is ready',
|
|
status: hasAnalysisOutput ? 'ready' : 'waiting',
|
|
ready: hasAnalysisOutput,
|
|
target: 'analysis',
|
|
},
|
|
{
|
|
step: '5',
|
|
title: 'Export',
|
|
detail: exports.length > 0 ? `${exports.length} export artifact${exports.length === 1 ? '' : 's'} recorded` : 'Package validated project outputs',
|
|
status: exports.length > 0 ? 'ready' : 'waiting',
|
|
ready: exports.length > 0,
|
|
target: 'exports',
|
|
},
|
|
]
|
|
const projectContextLabel = selectedProject?.name === REGIONAL_WORKSPACE_PROJECT_NAME
|
|
? REGIONAL_WORKSPACE_LABEL
|
|
: selectedProject?.name ?? 'Geen werkruimte'
|
|
const areaContextLabel = selectedArea?.name ?? (areas.length > 0 ? 'Kies een gebied' : 'Geen gebied')
|
|
const datasetContextLabel = analysisMapLayerActive && mapFeatureCollection
|
|
? `${mapLayerLabel} · controle vereist`
|
|
: selectedDataset
|
|
? getDatasetDisplayName(selectedDataset)
|
|
: datasets.length > 0
|
|
? 'Kies een bron'
|
|
: 'Geen bron'
|
|
const layerContextLabel = mapFeatureCollection
|
|
? `${mapFeatureCount.toLocaleString('nl-BE')} objecten`
|
|
: viewportVectorLayerActive
|
|
? 'Kaartlaag gekozen'
|
|
: 'Geen actieve laag'
|
|
|
|
return (
|
|
<div className="app-shell workbench-shell">
|
|
<a className="skip-link" href="#workspace-main">
|
|
Ga naar de werkruimte
|
|
</a>
|
|
<header className="workbench-topbar">
|
|
<div className="brand-block">
|
|
<span className="brand-mark" aria-hidden="true">GI</span>
|
|
<div>
|
|
<h1>GeoIntel</h1>
|
|
<p>GeoAI-werkruimte · Mol en de Kempen</p>
|
|
</div>
|
|
</div>
|
|
<div className="context-bar" aria-label="Active workbench context">
|
|
<div>
|
|
<span>Werkruimte</span>
|
|
<strong title={projectContextLabel}>{projectContextLabel}</strong>
|
|
</div>
|
|
<div>
|
|
<span>Gebied</span>
|
|
<strong title={areaContextLabel}>{areaContextLabel}</strong>
|
|
</div>
|
|
<div>
|
|
<span>Bron</span>
|
|
<strong title={datasetContextLabel}>{datasetContextLabel}</strong>
|
|
</div>
|
|
<div>
|
|
<span>Kaartlaag</span>
|
|
<strong title={layerContextLabel}>{layerContextLabel}</strong>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
{errorMessage ? <p className="error">{errorMessage}</p> : null}
|
|
|
|
<div className="workbench-layout">
|
|
<aside className="workbench-sidebar" aria-label="Workbench navigation">
|
|
<nav aria-label="Primary workspaces">
|
|
{workspaceNavGroups.map((group) => (
|
|
<div className="nav-group" key={group.label}>
|
|
<p className="nav-section-label">{group.label}</p>
|
|
{group.keys.map((key) => {
|
|
const item = workspaceNavItems.find((candidate) => candidate.key === key)
|
|
if (!item) {
|
|
return null
|
|
}
|
|
return (
|
|
<button
|
|
key={item.key}
|
|
type="button"
|
|
className={item.key === activeWorkspace ? 'nav-item nav-item-active' : 'nav-item'}
|
|
onClick={() => setActiveWorkspace(item.key)}
|
|
aria-current={item.key === activeWorkspace ? 'page' : undefined}
|
|
aria-label={`Open ${item.label} workspace: ${item.description}`}
|
|
data-testid={`workspace-nav-${item.key}`}
|
|
>
|
|
<span>{item.label}</span>
|
|
<small>{item.description}</small>
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
))}
|
|
</nav>
|
|
</aside>
|
|
|
|
<main className="workbench-main" id="workspace-main" tabIndex={-1}>
|
|
{activeWorkspace !== 'map' ? <div className="workspace-heading">
|
|
<div className="workspace-heading-copy">
|
|
<p className="eyebrow">{selectedProject?.region ?? 'Mol, Kempen'}</p>
|
|
<h2>{activeWorkspaceItem.label}</h2>
|
|
</div>
|
|
<div className="workspace-heading-actions">
|
|
<p>{activeWorkspaceItem.description}</p>
|
|
{activeWorkspace !== 'overview' ? (
|
|
<button
|
|
type="button"
|
|
className={inspectorOpen ? 'inspector-toggle inspector-toggle-active' : 'inspector-toggle'}
|
|
onClick={() => setInspectorOpen((current) => !current)}
|
|
aria-expanded={inspectorOpen}
|
|
aria-controls="workbench-inspector"
|
|
>
|
|
{inspectorOpen ? 'Details sluiten' : 'Details openen'}
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
</div> : null}
|
|
<div className="workspace-command-bar" aria-label="Workspace shortcuts">
|
|
<div className="workspace-nav-cluster">
|
|
{workspaceNavItems.slice(0, 5).map((item) => (
|
|
<button
|
|
key={item.key}
|
|
type="button"
|
|
className={item.key === activeWorkspace ? 'command-chip command-chip-active' : 'command-chip'}
|
|
onClick={() => setActiveWorkspace(item.key)}
|
|
aria-pressed={item.key === activeWorkspace}
|
|
aria-label={`Switch to ${item.label} workspace`}
|
|
>
|
|
{item.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
<span>{selectedProject ? `Actief bereik: ${projectContextLabel}` : 'De regionale gegevens worden ingeladen.'}</span>
|
|
</div>
|
|
|
|
{activeWorkspace === 'overview' ? (
|
|
<div className="workspace-stack">
|
|
<WorkbenchStatusStrip
|
|
selectedProject={selectedProject}
|
|
areas={areas}
|
|
datasets={datasets}
|
|
qualityChecks={qualityChecks}
|
|
exports={exports}
|
|
activeLayerFeatureCount={mapFeatureCount}
|
|
selectedAreaHasGeometry={Boolean(areaFeatureCollection)}
|
|
/>
|
|
<details className="status-details-disclosure">
|
|
<summary>
|
|
<span>Volledige workflowstatus</span>
|
|
<strong>{workflowGuidanceComplete ? 'voltooid' : 'optionele stappen open'}</strong>
|
|
</summary>
|
|
<section className="workflow-guidance-panel" aria-label="V1 workflow guidance">
|
|
<div className="panel-title-row">
|
|
<div>
|
|
<p className="eyebrow">Workbench flow</p>
|
|
<h2>Project to export path</h2>
|
|
</div>
|
|
<span className="status-badge">
|
|
{workflowGuidanceComplete
|
|
? 'Ready for handoff'
|
|
: `Next: ${workspaceNavItems.find((item) => item.key === recommendedWorkflowTarget)?.label ?? 'Overview'}`}
|
|
</span>
|
|
</div>
|
|
<div className="workflow-guidance-steps">
|
|
{workflowGuidanceSteps.map((step) => (
|
|
<button
|
|
key={`${step.step}-${step.title}`}
|
|
type="button"
|
|
className={
|
|
step.target === recommendedWorkflowTarget && !step.ready
|
|
? 'workflow-guidance-step workflow-guidance-step-active'
|
|
: step.ready
|
|
? 'workflow-guidance-step workflow-guidance-step-ready'
|
|
: 'workflow-guidance-step'
|
|
}
|
|
onClick={() => openWorkflowGuidanceStep(step.target)}
|
|
aria-label={`Open ${step.title} step`}
|
|
>
|
|
<span className="workflow-step-status">{step.status}</span>
|
|
<strong>
|
|
{step.step}. {step.title}
|
|
</strong>
|
|
<small>{step.detail}</small>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</section>
|
|
<section className="overview-actions">
|
|
<div className="overview-action-copy">
|
|
<p className="eyebrow">Quick access</p>
|
|
<h2>Continue working</h2>
|
|
</div>
|
|
<div className="quick-action-grid overview-quick-actions" aria-label="Recommended next workbench actions">
|
|
<button
|
|
type="button"
|
|
className="quick-action-button"
|
|
onClick={() => setActiveWorkspace('data')}
|
|
aria-label="Open data setup workspace"
|
|
>
|
|
Open data setup
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="quick-action-button"
|
|
onClick={() => setActiveWorkspace('map')}
|
|
aria-label="Inspect map workspace"
|
|
>
|
|
Inspect map
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="quick-action-button"
|
|
onClick={() => setActiveWorkspace('analysis')}
|
|
aria-label="Review QA/QC workspace"
|
|
>
|
|
Review QA/QC
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="quick-action-button"
|
|
onClick={() => setActiveWorkspace('exports')}
|
|
aria-label="Manage exports workspace"
|
|
>
|
|
Manage exports
|
|
</button>
|
|
</div>
|
|
</section>
|
|
</details>
|
|
</div>
|
|
) : null}
|
|
|
|
{activeWorkspace === 'data' ? (
|
|
<div className="workspace-grid workspace-grid-data">
|
|
<ProjectPanel
|
|
projects={projects}
|
|
selectedProjectId={selectedProjectId}
|
|
loadingProjects={loadingProjects}
|
|
projectForm={projectForm}
|
|
loadingDemoWorkflow={loadingDemoWorkflow}
|
|
demoWorkflowMessage={demoWorkflowMessage}
|
|
onCreateProject={createProject}
|
|
onUpdateProjectForm={setProjectForm}
|
|
onSelectProject={selectProject}
|
|
onLoadDemoWorkflow={loadDemoWorkflow}
|
|
/>
|
|
|
|
<AreaPanel
|
|
areas={areas}
|
|
selectedProject={selectedProject}
|
|
selectedProjectId={selectedProjectId}
|
|
loadingAreas={loadingAreas}
|
|
areaForm={areaForm}
|
|
selectedMapAreaId={selectedMapAreaId}
|
|
onCreateArea={createArea}
|
|
onUpdateAreaForm={setAreaForm}
|
|
onSelectMapArea={setSelectedMapAreaId}
|
|
/>
|
|
|
|
<DatasetPanel
|
|
selectedProjectId={selectedProjectId}
|
|
selectedDatasetId={selectedDatasetId}
|
|
areas={areas}
|
|
datasets={datasets}
|
|
datasetForm={datasetForm}
|
|
loadingDatasets={loadingDatasets}
|
|
onDatasetFormChange={setDatasetForm}
|
|
onUploadDataset={uploadDataset}
|
|
onLoadDatasetDetails={loadDatasetDetails}
|
|
onOpenInspector={() => setInspectorOpen(true)}
|
|
onRefreshMetadata={refreshMetadata}
|
|
onOpenDatasetInMap={openDatasetInMap}
|
|
onOpenDatasetExport={openDatasetExport}
|
|
/>
|
|
</div>
|
|
) : null}
|
|
|
|
{activeWorkspace === 'map' ? (
|
|
<MapWorkspace
|
|
selectedProjectId={selectedProjectId}
|
|
projects={projects}
|
|
areas={areas}
|
|
selectedMapAreaId={selectedMapAreaId}
|
|
areaFeatureCollection={areaFeatureCollection}
|
|
mapFeatureCollection={mapFeatureCollection}
|
|
qualityEvidenceGeoJson={qualityEvidenceGeoJson?.geojson ?? null}
|
|
qualityEvidenceFeatureCount={qualityEvidenceGeoJson?.feature_count ?? 0}
|
|
qualityEvidenceLoading={qualityEvidenceLoading}
|
|
qualityEvidenceError={qualityEvidenceError}
|
|
qualityEvidenceWarnings={qualityEvidenceGeoJson?.warnings ?? []}
|
|
mapLayerLabel={mapLayerLabel}
|
|
mapLayerSourceLabel={mapLayerSourceLabel}
|
|
mapLayerProvenance={mapLayerProvenance}
|
|
mapLayerVisible={mapLayerVisible}
|
|
mapLayerOpacity={mapLayerOpacity}
|
|
areaLayerVisible={areaLayerVisible}
|
|
areaLayerOpacity={areaLayerOpacity}
|
|
mapFeatureCount={mapFeatureCount}
|
|
areaFeatureCount={areaFeatureCount}
|
|
viewportVectorEnabled={viewportVectorLayerActive}
|
|
viewportVectorStatus={viewportVectorLayerActive ? viewportVectorLayer.statusMessage : null}
|
|
viewportVectorTone={viewportVectorLayer.error ? 'error' : viewportVectorLayer.truncated ? 'warning' : viewportVectorLayer.loading || viewportVectorLayer.zoomRequired ? 'pending' : 'ready'}
|
|
fitMapDataOnChange={!viewportVectorLayerActive}
|
|
mapContentMode={analysisMapLayerActive ? 'analysis' : 'dataset'}
|
|
analysisLayerAvailable={analysisMapLayerAvailable}
|
|
selectedMapFeature={selectedMapFeature}
|
|
mapSelectionBbox={mapSelectionBbox}
|
|
mapSelectionResult={mapSelectionResult}
|
|
mapSelectionLoading={mapSelectionLoading}
|
|
mapSelectionError={mapSelectionError}
|
|
selectionExporting={selectionExporting}
|
|
selectionExportError={selectionExportError}
|
|
latestSelectionExportPath={latestSelectionExport?.path ?? null}
|
|
selectionDatasetSaving={selectionDatasetSaving}
|
|
selectionDatasetError={selectionDatasetError}
|
|
latestSelectionDataset={latestSelectionDataset}
|
|
latestSelectionDatasetName={latestSelectionDataset?.name ?? null}
|
|
mapQaReferenceDatasets={referenceDatasets}
|
|
selectedMapQaReferenceDatasetId={selectedMapQaReferenceDatasetId}
|
|
mapSelectionQaRunning={mapSelectionQaRunning}
|
|
mapSelectionQaError={mapSelectionQaError}
|
|
mapSelectionQaResult={mapSelectionQaResult}
|
|
latestMapSelectionQualityCheckId={latestMapSelectionQualityCheckId}
|
|
orthophotoAnalysisStage={mapOrthophotoAnalysis.stage}
|
|
orthophotoAnalysisStatus={mapOrthophotoAnalysis.status}
|
|
orthophotoAnalysisError={mapOrthophotoAnalysis.error}
|
|
orthophotoAnalysisRunning={mapOrthophotoAnalysis.running}
|
|
orthophotoAnalysisQuality={mapOrthophotoAnalysis.lastQuality}
|
|
orthophotoAnalysisDetectionCount={mapOrthophotoAnalysis.lastDetectionCount}
|
|
availableMapDatasets={availableMapDatasets}
|
|
selectedMapDatasetId={selectedDataset && isVectorDatasetType(selectedDataset.dataset_type) ? selectedDataset.id : ''}
|
|
selectedFeature={selectedMapFeature}
|
|
onSelectMapArea={setSelectedMapAreaId}
|
|
onOpenDatasetInMap={openDatasetInMap}
|
|
onSetAreaLayerVisible={setAreaLayerVisible}
|
|
onSetAreaLayerOpacity={setAreaLayerOpacity}
|
|
onSetMapLayerVisible={setMapLayerVisible}
|
|
onSetMapLayerOpacity={setMapLayerOpacity}
|
|
onSetMapContentMode={setMapContentMode}
|
|
onSelectMapFeature={setSelectedMapFeature}
|
|
onMapViewportChange={viewportVectorLayer.setViewport}
|
|
onSetMapSelectionBbox={setMapSelectionBbox}
|
|
onRunMapSelectionExtract={runMapSelectionExtract}
|
|
onClearMapSelectionExtract={resetMapSelectionExtract}
|
|
onExportMapSelection={exportMapSelectionGeoJson}
|
|
onDeriveMapSelectionDataset={deriveMapSelectionDataset}
|
|
onSelectMapQaReferenceDataset={setSelectedMapQaReferenceDatasetId}
|
|
onRunMapSelectionQa={runMapSelectionQa}
|
|
onOpenMapSelectionQualityEvidence={openMapSelectionQualityEvidence}
|
|
onRunOrthophotoAnalysis={mapOrthophotoAnalysis.run}
|
|
onClearQualityEvidence={clearQualityEvidenceGeoJson}
|
|
/>
|
|
) : null}
|
|
|
|
{activeWorkspace === 'analysis' ? (
|
|
<div className="workspace-grid workspace-grid-analysis">
|
|
<QualityResultsPanel
|
|
selectedProjectId={selectedProjectId}
|
|
qualityChecks={qualityChecks}
|
|
qualityChecksError={qualityChecksError}
|
|
candidateDatasets={candidateDatasets}
|
|
referenceDatasets={referenceDatasets}
|
|
onRefresh={() => loadQualityChecks()}
|
|
onOpenEvidenceMap={openQualityEvidenceOnMap}
|
|
evidenceLoading={qualityEvidenceLoading}
|
|
evidenceError={qualityEvidenceError}
|
|
/>
|
|
<details className="secondary-analysis-disclosure">
|
|
<summary>
|
|
<span>Historische vectorlagen vergelijken</span>
|
|
<strong>Geavanceerd</strong>
|
|
</summary>
|
|
<ChangeDetectionPanel
|
|
vectorDatasets={availableVectorDatasets}
|
|
sourceDatasetId={changeSourceDatasetId}
|
|
targetDatasetId={changeTargetDatasetId}
|
|
iouThreshold={changeIouThreshold}
|
|
includeUnchanged={changeIncludeUnchanged}
|
|
running={runningChangeDetection}
|
|
result={changeDetectionResult}
|
|
error={changeDetectionError}
|
|
onSourceDatasetChange={setChangeSourceDatasetId}
|
|
onTargetDatasetChange={setChangeTargetDatasetId}
|
|
onIouThresholdChange={setChangeIouThreshold}
|
|
onIncludeUnchangedChange={setChangeIncludeUnchanged}
|
|
onRun={runChangeDetection}
|
|
/>
|
|
</details>
|
|
</div>
|
|
) : null}
|
|
|
|
{activeWorkspace === 'ai' ? (
|
|
<div className="workspace-grid workspace-grid-ai">
|
|
<DetectionLab
|
|
detectionModels={detectionModels}
|
|
modelAssets={modelAssets}
|
|
loadingDetectionModels={loadingDetectionModels}
|
|
detectionModelError={detectionModelError}
|
|
modelAssetError={modelAssetError}
|
|
selectedDetectionDatasetId={selectedDetectionDatasetId}
|
|
selectedDetectionModelId={selectedDetectionModelId}
|
|
selectedModelAssetId={selectedModelAssetId}
|
|
detectionTileManifestPath={detectionTileManifestPath}
|
|
detectionConfidenceThreshold={detectionConfidenceThreshold}
|
|
runningDetection={runningDetection}
|
|
detectionRunResult={detectionRunResult}
|
|
detectionRunError={detectionRunError}
|
|
detectionRuns={detectionRuns}
|
|
qualityChecks={qualityChecks}
|
|
calibrationThresholdText={calibrationThresholdText}
|
|
runningDetectionCalibration={runningDetectionCalibration}
|
|
detectionCalibrationRows={detectionCalibrationRows}
|
|
detectionCalibrationError={detectionCalibrationError}
|
|
detectionWorkflowStage={detectionWorkflowStage}
|
|
selectedDetectionRunId={selectedDetectionRunId}
|
|
detectionItems={detectionItems}
|
|
detectionClassFilter={detectionClassFilter}
|
|
detectionMinConfidenceFilter={detectionMinConfidenceFilter}
|
|
loadingDetectionResults={loadingDetectionResults}
|
|
detectionReferenceDatasetId={detectionReferenceDatasetId}
|
|
detectionQaResult={detectionQaResult}
|
|
detectionQaError={detectionQaError}
|
|
runningDetectionQa={runningDetectionQa}
|
|
yoloPreflight={yoloPreflight}
|
|
loadingYoloPreflight={loadingYoloPreflight}
|
|
yoloPreflightError={yoloPreflightError}
|
|
selectedProjectId={selectedProjectId}
|
|
rasterDatasets={rasterDatasets}
|
|
referenceDatasets={referenceDatasets}
|
|
onLoadModels={loadDetectionModels}
|
|
onRefreshYoloPreflight={() => loadYoloPreflight()}
|
|
onSelectDataset={setSelectedDetectionDatasetId}
|
|
onSelectModel={setSelectedDetectionModelId}
|
|
onSelectModelAsset={setSelectedModelAssetId}
|
|
onSetConfidenceThreshold={setDetectionConfidenceThreshold}
|
|
onSetTileManifestPath={setDetectionTileManifestPath}
|
|
onRunDetection={runDetection}
|
|
onUploadRaster={uploadDetectionRaster}
|
|
onPrepareAndRunDetection={runGuidedDetection}
|
|
onOpenResultsOnMap={openDetectionResultsOnMap}
|
|
onLoadRuns={() => loadDetectionRuns()}
|
|
onSelectRun={setSelectedDetectionRunId}
|
|
onSetClassFilter={setDetectionClassFilter}
|
|
onSetMinConfidenceFilter={setDetectionMinConfidenceFilter}
|
|
onLoadResults={() => loadDetectionResults()}
|
|
onSelectReferenceDataset={setDetectionReferenceDatasetId}
|
|
onRunQa={runDetectionQa}
|
|
onSetCalibrationThresholdText={setCalibrationThresholdText}
|
|
onRunCalibration={runDetectionCalibration}
|
|
onOpenCalibrationEvidence={openQualityEvidenceOnMap}
|
|
onApplyOperatorProfile={applyDetectionOperatorProfile}
|
|
/>
|
|
|
|
<details className="secondary-analysis-disclosure segmentation-disclosure">
|
|
<summary>
|
|
<span>Segmentatie van beeldvlakken</span>
|
|
<strong>Nog niet geconfigureerd</strong>
|
|
</summary>
|
|
<SegmentationLab
|
|
segmentationModels={segmentationModels}
|
|
loadingSegmentationModels={loadingSegmentationModels}
|
|
segmentationModelError={segmentationModelError}
|
|
selectedSegmentationDatasetId={selectedSegmentationDatasetId}
|
|
selectedSegmentationModelId={selectedSegmentationModelId}
|
|
segmentationTileManifestPath={segmentationTileManifestPath}
|
|
segmentationConfidenceThreshold={segmentationConfidenceThreshold}
|
|
runningSegmentation={runningSegmentation}
|
|
segmentationRunResult={segmentationRunResult}
|
|
segmentationRunError={segmentationRunError}
|
|
segmentationRuns={segmentationRuns}
|
|
selectedSegmentationRunId={selectedSegmentationRunId}
|
|
segmentationItems={segmentationItems}
|
|
segmentationClassFilter={segmentationClassFilter}
|
|
segmentationMinConfidenceFilter={segmentationMinConfidenceFilter}
|
|
loadingSegmentationResults={loadingSegmentationResults}
|
|
segmentationReferenceDatasetId={segmentationReferenceDatasetId}
|
|
segmentationQaResult={segmentationQaResult}
|
|
segmentationQaError={segmentationQaError}
|
|
runningSegmentationQa={runningSegmentationQa}
|
|
selectedProjectId={selectedProjectId}
|
|
rasterDatasets={rasterDatasets}
|
|
referenceDatasets={referenceDatasets}
|
|
selectedSegmentationModelConfigured={Boolean(selectedSegmentationModel?.configured)}
|
|
selectedSegmentationModelLimitation={selectedSegmentationModel?.limitation_message ?? null}
|
|
onLoadModels={loadSegmentationModels}
|
|
onSelectDataset={setSelectedSegmentationDatasetId}
|
|
onSelectModel={setSelectedSegmentationModelId}
|
|
onSetTileManifestPath={setSegmentationTileManifestPath}
|
|
onSetConfidenceThreshold={setSegmentationConfidenceThreshold}
|
|
onRunSegmentation={runSegmentation}
|
|
onLoadRuns={() => loadSegmentationRuns()}
|
|
onSelectRun={setSelectedSegmentationRunId}
|
|
onSetClassFilter={setSegmentationClassFilter}
|
|
onSetMinConfidenceFilter={setSegmentationMinConfidenceFilter}
|
|
onLoadResults={() => loadSegmentationResults()}
|
|
onSelectReferenceDataset={setSegmentationReferenceDatasetId}
|
|
onRunQa={runSegmentationQa}
|
|
/>
|
|
</details>
|
|
</div>
|
|
) : null}
|
|
|
|
{activeWorkspace === 'exports' ? (
|
|
<div className="workspace-grid workspace-grid-exports">
|
|
<ExportCenter
|
|
selectedProjectId={selectedProjectId}
|
|
selectedDataset={selectedDataset}
|
|
selectedDetectionRunId={selectedDetectionRunId}
|
|
selectedSegmentationRunId={selectedSegmentationRunId}
|
|
exports={exports}
|
|
latestExport={latestExport}
|
|
exportError={exportError}
|
|
loadingExports={loadingExports}
|
|
exporting={exporting}
|
|
onRefresh={() => loadExports()}
|
|
onExportDataset={exportSelectedDatasetGeoJson}
|
|
onExportDetectionRun={exportSelectedDetectionRunGeoJson}
|
|
onExportSegmentationRun={exportSelectedSegmentationRunGeoJson}
|
|
onExportProjectMetadata={exportProjectMetadata}
|
|
onExportProjectReport={exportProjectReport}
|
|
onPreviewContent={previewExportContent}
|
|
onDownload={downloadExportArtifact}
|
|
/>
|
|
<ExportPreview content={exportPreview} />
|
|
</div>
|
|
) : null}
|
|
|
|
{activeWorkspace === 'system' ? (
|
|
<ProviderPanel
|
|
providers={providers}
|
|
loadingCapabilities={loadingCapabilities}
|
|
capabilitiesError={capabilitiesError}
|
|
onRefresh={loadCapabilities}
|
|
/>
|
|
) : null}
|
|
</main>
|
|
|
|
{inspectorOpen ? (
|
|
<aside className="workbench-inspector" id="workbench-inspector" aria-label="Current selection inspector">
|
|
<WorkbenchInspector
|
|
selectedProject={selectedProject}
|
|
selectedArea={selectedArea}
|
|
selectedMapFeature={selectedMapFeature}
|
|
areas={areas}
|
|
datasetsCount={datasets.length}
|
|
qualityChecks={qualityChecks}
|
|
exports={exports}
|
|
latestExport={latestExport}
|
|
detectionRuns={detectionRuns}
|
|
selectedDetectionRunId={selectedDetectionRunId}
|
|
detectionItems={detectionItems}
|
|
segmentationRuns={segmentationRuns}
|
|
selectedSegmentationRunId={selectedSegmentationRunId}
|
|
segmentationItems={segmentationItems}
|
|
onOpenDataWorkspace={() => setActiveWorkspace('data')}
|
|
onOpenMapWorkspace={() => setActiveWorkspace('map')}
|
|
onOpenQualityWorkspace={() => setActiveWorkspace('analysis')}
|
|
onOpenExportsWorkspace={() => setActiveWorkspace('exports')}
|
|
onOpenAiWorkspace={() => setActiveWorkspace('ai')}
|
|
onClose={() => setInspectorOpen(false)}
|
|
datasetDetailProps={{
|
|
areas,
|
|
availableVectorTargets,
|
|
selectedDatasetId,
|
|
selectedDataset,
|
|
selectedDatasetSummary,
|
|
selectedRasterMetadata,
|
|
selectedRasterStats,
|
|
rasterPreview,
|
|
latestRasterTileManifestPath,
|
|
latestRasterTileManifest,
|
|
rasterUnavailableMessage,
|
|
selectedClipAreaId,
|
|
selectedIntersectTargetId,
|
|
rasterTileSize,
|
|
rasterTileOverlap,
|
|
rasterTileOutputName,
|
|
rasterReprojectCrs,
|
|
rasterReprojectResampling,
|
|
ndviNirBand,
|
|
ndviRedBand,
|
|
ndwiGreenBand,
|
|
ndwiNirBand,
|
|
ndbiSwirBand,
|
|
ndbiNirBand,
|
|
isRasterTileInputValid,
|
|
jobs,
|
|
loadingDatasetDetails,
|
|
datasetDetailError,
|
|
isVectorDatasetType,
|
|
onSetSelectedClipAreaId: setSelectedClipAreaId,
|
|
onSetSelectedIntersectTargetId: setSelectedIntersectTargetId,
|
|
onSetRasterTileSize: setRasterTileSize,
|
|
onSetRasterTileOverlap: setRasterTileOverlap,
|
|
onSetRasterTileOutputName: setRasterTileOutputName,
|
|
onSetRasterReprojectCrs: setRasterReprojectCrs,
|
|
onSetRasterReprojectResampling: setRasterReprojectResampling,
|
|
onSetNdviNirBand: setNdviNirBand,
|
|
onSetNdviRedBand: setNdviRedBand,
|
|
onSetNdwiGreenBand: setNdwiGreenBand,
|
|
onSetNdwiNirBand: setNdwiNirBand,
|
|
onSetNdbiSwirBand: setNdbiSwirBand,
|
|
onSetNdbiNirBand: setNdbiNirBand,
|
|
onRunRasterInspect: runRasterInspect,
|
|
onRunRasterPreview: runRasterPreview,
|
|
onRunRasterStats: runRasterStats,
|
|
onRunRasterReproject: runRasterReproject,
|
|
onRunRasterClip: runRasterClip,
|
|
onRunRasterTile: runRasterTile,
|
|
onUseTileManifestForDetection: useRasterTileManifestForDetection,
|
|
onUseTileManifestForSegmentation: useRasterTileManifestForSegmentation,
|
|
onRunRasterNdvi: runRasterNdvi,
|
|
onRunRasterNdwi: runRasterNdwi,
|
|
onRunRasterNdbi: runRasterNdbi,
|
|
onRunVectorClip: runVectorClip,
|
|
onRunVectorBuffer: runVectorBuffer,
|
|
onRunVectorIntersect: () => runVectorIntersect(availableVectorTargets),
|
|
onPickDerivedDataset: pickDerivedDataset,
|
|
}}
|
|
/>
|
|
</aside>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default App
|