feat: add governed hydrology and historical imagery
This commit is contained in:
@@ -404,6 +404,13 @@ their real observation ranges. A collapsed follow-up catalogue distinguishes
|
||||
official sources that exist from datasets that are already persisted in the
|
||||
active project.
|
||||
|
||||
The Map result panel also loads a governed orthophoto product list. The most
|
||||
recent product retains the configured-YOLO plus current-GRB QA action. Official
|
||||
historical years/periods use the same bounded rectangle, persist as raster
|
||||
Datasets and render as MapLibre image overlays, but expose no misleading
|
||||
current-state AI/QA action. The Sources inventory moves Waterinfo and historical
|
||||
orthophotos from follow-up to loaded evidence only after such datasets exist.
|
||||
|
||||
Evolution mode compares the exact selected persisted Area when the full
|
||||
municipality/region action is used. It shows the selected before/after values,
|
||||
all compatible supporting metrics and a chart/table for every observation in
|
||||
|
||||
@@ -1018,6 +1018,10 @@ function App(): JSX.Element {
|
||||
orthophotoAnalysisRunning={mapOrthophotoAnalysis.running}
|
||||
orthophotoAnalysisQuality={mapOrthophotoAnalysis.lastQuality}
|
||||
orthophotoAnalysisDetectionCount={mapOrthophotoAnalysis.lastDetectionCount}
|
||||
orthophotoProducts={mapOrthophotoAnalysis.products}
|
||||
selectedOrthophotoProductKey={mapOrthophotoAnalysis.selectedProductKey}
|
||||
orthophotoResult={mapOrthophotoAnalysis.lastResult}
|
||||
orthophotoImageUrl={mapOrthophotoAnalysis.imageUrl}
|
||||
availableMapDatasets={availableMapDatasets}
|
||||
selectedMapDatasetId={selectedDataset && isVectorDatasetType(selectedDataset.dataset_type) ? selectedDataset.id : ''}
|
||||
selectedFeature={selectedMapFeature}
|
||||
@@ -1039,6 +1043,7 @@ function App(): JSX.Element {
|
||||
onRunMapSelectionQa={runMapSelectionQa}
|
||||
onOpenMapSelectionQualityEvidence={openMapSelectionQualityEvidence}
|
||||
onRunOrthophotoAnalysis={mapOrthophotoAnalysis.run}
|
||||
onSelectOrthophotoProduct={mapOrthophotoAnalysis.setSelectedProductKey}
|
||||
onClearQualityEvidence={clearQualityEvidenceGeoJson}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -3,7 +3,7 @@ import maplibregl from 'maplibre-gl'
|
||||
import 'maplibre-gl/dist/maplibre-gl.css'
|
||||
import { PRIMARY_FOCUS_CENTER } from '../config/primaryFocus'
|
||||
import { featureCollectionBounds } from '../lib/geojsonBounds'
|
||||
import type { MapViewportState, VectorSelectionBBox } from '../types'
|
||||
import type { MapImageOverlay, MapViewportState, VectorSelectionBBox } from '../types'
|
||||
|
||||
interface GeoMapProps {
|
||||
data: GeoJSON.FeatureCollection | null
|
||||
@@ -13,6 +13,7 @@ interface GeoMapProps {
|
||||
selectedFeature?: GeoJSON.Feature | null
|
||||
selectionData?: GeoJSON.FeatureCollection | null
|
||||
qaEvidenceData?: GeoJSON.FeatureCollection | null
|
||||
imageOverlay?: MapImageOverlay | null
|
||||
selectionBbox?: { min_x: number; min_y: number; max_x: number; max_y: number } | null
|
||||
bboxSelectionMode?: boolean
|
||||
visible?: boolean
|
||||
@@ -153,6 +154,7 @@ function GeoMap({
|
||||
selectedFeature = null,
|
||||
selectionData = null,
|
||||
qaEvidenceData = null,
|
||||
imageOverlay = null,
|
||||
selectionBbox = null,
|
||||
bboxSelectionMode = false,
|
||||
visible = true,
|
||||
@@ -346,6 +348,43 @@ function GeoMap({
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const map = mapRef.current
|
||||
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
|
||||
return
|
||||
}
|
||||
if (map.getLayer('bounded-orthophoto')) {
|
||||
map.removeLayer('bounded-orthophoto')
|
||||
}
|
||||
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],
|
||||
],
|
||||
})
|
||||
const beforeLayer = ['area-fill', 'dataset-fill', 'selection-bbox-fill'].find((layerId) => map.getLayer(layerId))
|
||||
map.addLayer(
|
||||
{
|
||||
id: 'bounded-orthophoto',
|
||||
type: 'raster',
|
||||
source: 'bounded-orthophoto',
|
||||
paint: { 'raster-opacity': imageOverlay.opacity ?? 0.88 },
|
||||
},
|
||||
beforeLayer,
|
||||
)
|
||||
}, [imageOverlay, mapStyleReady])
|
||||
|
||||
useEffect(() => {
|
||||
const map = mapRef.current
|
||||
if (!map || !mapStyleReady || !map.isStyleLoaded()) {
|
||||
|
||||
@@ -15,13 +15,15 @@ const THEME_LABELS: Record<string, string> = {
|
||||
|
||||
const AVAILABLE_SOURCES = [
|
||||
{
|
||||
key: 'historical_orthophoto',
|
||||
name: 'Historische orthofoto’s',
|
||||
owner: 'Digitaal Vlaanderen',
|
||||
coverage: '1971 en 1979-1990; aanvullende jaargangen bestaan afzonderlijk',
|
||||
value: 'Visuele evolutie en toekomstige beeldvergelijking',
|
||||
coverage: '1971, 1979-1990, 2000-2025',
|
||||
value: 'Visuele evolutie via begrensde officiële luchtbeelden',
|
||||
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/orthofotomozaiek-kleinschalig-zomeropnamen',
|
||||
},
|
||||
{
|
||||
key: 'bwk',
|
||||
name: 'Biologische Waarderingskaart / Natura 2000',
|
||||
owner: 'INBO',
|
||||
coverage: 'Toestand 2025',
|
||||
@@ -29,6 +31,7 @@ const AVAILABLE_SOURCES = [
|
||||
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/biologische-waarderingskaart-en-natura-2000-habitatkaart-toestand-2025',
|
||||
},
|
||||
{
|
||||
key: 'agriculture',
|
||||
name: 'Landbouwgebruikspercelen',
|
||||
owner: 'Agentschap Landbouw en Zeevisserij',
|
||||
coverage: 'Jaarlijkse bestanden',
|
||||
@@ -36,6 +39,7 @@ const AVAILABLE_SOURCES = [
|
||||
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/open-geodata-landbouwgebruikspercelen',
|
||||
},
|
||||
{
|
||||
key: 'buildings_register',
|
||||
name: 'Gebouwen- en adressenregister',
|
||||
owner: 'Digitaal Vlaanderen',
|
||||
coverage: 'Continu geactualiseerd',
|
||||
@@ -43,6 +47,7 @@ const AVAILABLE_SOURCES = [
|
||||
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/gebouwen-en-adressenregister',
|
||||
},
|
||||
{
|
||||
key: 'elevation',
|
||||
name: 'Digitaal Hoogtemodel Vlaanderen II',
|
||||
owner: 'Digitaal Vlaanderen',
|
||||
coverage: 'LiDAR-opname 2013-2015, DTM/DSM 1 m en 5 m',
|
||||
@@ -50,6 +55,7 @@ const AVAILABLE_SOURCES = [
|
||||
url: 'https://www.vlaanderen.be/digitaal-vlaanderen/onze-diensten-en-platformen/earth-observation-data-science-eodas/het-digitaal-hoogtemodel/digitaal-hoogtemodel-vlaanderen-ii',
|
||||
},
|
||||
{
|
||||
key: 'waterinfo',
|
||||
name: 'Waterinfo en VMM-metingen',
|
||||
owner: 'Vlaamse Milieumaatschappij',
|
||||
coverage: 'Meetpunten en tijdreeksen voor waterstand, debiet en neerslag',
|
||||
@@ -91,6 +97,17 @@ function timelineSummary(
|
||||
|
||||
export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.Element {
|
||||
const ready = datasets.filter((dataset) => dataset.status === 'ready')
|
||||
const waterinfoDatasets = ready.filter((dataset) => dataset.source_name === 'waterinfo')
|
||||
const historicalOrthophotos = ready.filter(
|
||||
(dataset) =>
|
||||
dataset.source_name === 'digitaal_vlaanderen_orthophoto' &&
|
||||
String(dataset.source_metadata?.['product_key'] ?? 'most_recent') !== 'most_recent',
|
||||
)
|
||||
const pendingSources = AVAILABLE_SOURCES.filter((source) => {
|
||||
if (source.key === 'waterinfo') return waterinfoDatasets.length === 0
|
||||
if (source.key === 'historical_orthophoto') return historicalOrthophotos.length === 0
|
||||
return true
|
||||
})
|
||||
const themes = Object.keys(THEME_LABELS).map((theme) => {
|
||||
const matches = ready.filter((dataset) => datasetTheme(dataset) === theme)
|
||||
const temporal = matches.filter((dataset) => dataset.temporal_series_key && dataset.observed_at)
|
||||
@@ -148,10 +165,29 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
|
||||
))}
|
||||
</div>
|
||||
|
||||
{waterinfoDatasets.length > 0 || historicalOrthophotos.length > 0 ? (
|
||||
<div className="source-catalog-loaded" aria-label="Aanvullende ingeladen bronnen">
|
||||
{waterinfoDatasets.length > 0 ? (
|
||||
<article>
|
||||
<strong>Waterinfo meetreeksen</strong>
|
||||
<span>{new Set(waterinfoDatasets.map((dataset) => dataset.temporal_series_key).filter(Boolean)).size} stationsreeksen · {waterinfoDatasets.length} jaarmetingen</span>
|
||||
<p>Puntmetingen blijven afzonderlijk per station en worden niet als gebiedsgemiddelde of watervolume voorgesteld.</p>
|
||||
</article>
|
||||
) : null}
|
||||
{historicalOrthophotos.length > 0 ? (
|
||||
<article>
|
||||
<strong>Historische luchtbeelden</strong>
|
||||
<span>{historicalOrthophotos.length} begrensde kaartselecties bewaard</span>
|
||||
<p>Officiële jaargangen en periodes zijn via de kaart beschikbaar zonder actuele GRB-validatie.</p>
|
||||
</article>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<details className="source-opportunity-list">
|
||||
<summary>Officiële bronnen die hierna kunnen worden ingeladen</summary>
|
||||
<div>
|
||||
{AVAILABLE_SOURCES.map((source) => (
|
||||
{pendingSources.map((source) => (
|
||||
<article key={source.name}>
|
||||
<div>
|
||||
<strong>{source.name}</strong>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
DetectionQaResult,
|
||||
DetectionRunResponse,
|
||||
OrthophotoAcquisitionResult,
|
||||
OrthophotoProductRead,
|
||||
VectorSelectionBBox,
|
||||
} from '../types'
|
||||
import { formatError } from '../lib/formatError'
|
||||
@@ -87,6 +88,33 @@ export function useMapOrthophotoAnalysis({
|
||||
const [lastQuality, setLastQuality] = useState<DetectionQaResult | null>(null)
|
||||
const [lastDetectionCount, setLastDetectionCount] = useState<number | null>(null)
|
||||
const [lastAnalysisRunId, setLastAnalysisRunId] = useState<string | null>(null)
|
||||
const [products, setProducts] = useState<OrthophotoProductRead[]>([])
|
||||
const [selectedProductKey, setSelectedProductKey] = useState('most_recent')
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedProjectId) {
|
||||
setProducts([])
|
||||
return
|
||||
}
|
||||
let cancelled = false
|
||||
void datasetsApi.listOrthophotoProducts(selectedProjectId)
|
||||
.then((response) => {
|
||||
if (!cancelled) {
|
||||
setProducts(response.items)
|
||||
setSelectedProductKey((current) =>
|
||||
response.items.some((item) => item.key === current) ? current : response.items[0]?.key ?? 'most_recent',
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch((caught) => {
|
||||
if (!cancelled) {
|
||||
setError(formatError(caught, 'De luchtbeeldcatalogus kon niet worden geladen'))
|
||||
}
|
||||
})
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [selectedProjectId])
|
||||
|
||||
useEffect(() => {
|
||||
setStage('idle')
|
||||
@@ -96,7 +124,7 @@ export function useMapOrthophotoAnalysis({
|
||||
setLastQuality(null)
|
||||
setLastDetectionCount(null)
|
||||
setLastAnalysisRunId(null)
|
||||
}, [selectionBbox?.min_x, selectionBbox?.min_y, selectionBbox?.max_x, selectionBbox?.max_y])
|
||||
}, [selectionBbox?.min_x, selectionBbox?.min_y, selectionBbox?.max_x, selectionBbox?.max_y, selectedProductKey])
|
||||
|
||||
const run = async (bbox: VectorSelectionBBox): Promise<boolean> => {
|
||||
if (!selectedProjectId) {
|
||||
@@ -115,6 +143,7 @@ export function useMapOrthophotoAnalysis({
|
||||
const job = await datasetsApi.acquireOrthophoto(selectedProjectId, {
|
||||
bbox,
|
||||
area_id: selectedAreaId || undefined,
|
||||
product_key: selectedProductKey,
|
||||
})
|
||||
const acquisition = job.result_json as unknown as OrthophotoAcquisitionResult | null
|
||||
const datasetId = job.output_dataset_id || acquisition?.output_dataset_id
|
||||
@@ -124,6 +153,14 @@ export function useMapOrthophotoAnalysis({
|
||||
setLastResult(acquisition)
|
||||
await loadProjectData(selectedProjectId)
|
||||
|
||||
if (!acquisition.supports_detection) {
|
||||
setStatus(
|
||||
`${acquisition.display_name} is ingeladen en op de kaart geplaatst. ${acquisition.limitation_message}`,
|
||||
)
|
||||
setStage('complete')
|
||||
return true
|
||||
}
|
||||
|
||||
setStage('detecting')
|
||||
setStatus('2/3 Lokaal AI-model herkent gebouwen...')
|
||||
const detection = await prepareAndRunDetection(datasetId)
|
||||
@@ -169,9 +206,15 @@ export function useMapOrthophotoAnalysis({
|
||||
status,
|
||||
error,
|
||||
lastResult,
|
||||
imageUrl: lastResult && selectedProjectId
|
||||
? datasetsApi.orthophotoImageUrl(selectedProjectId, lastResult.output_dataset_id)
|
||||
: null,
|
||||
lastQuality,
|
||||
lastDetectionCount,
|
||||
lastAnalysisRunId,
|
||||
products,
|
||||
selectedProductKey,
|
||||
setSelectedProductKey,
|
||||
running: stage === 'acquiring' || stage === 'detecting' || stage === 'validating',
|
||||
run,
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import type {
|
||||
RasterNdwiRequest,
|
||||
RasterNdbiRequest,
|
||||
OrthophotoAcquireRequest,
|
||||
OrthophotoProductRead,
|
||||
} from '../../types'
|
||||
|
||||
export const datasetsApi = {
|
||||
@@ -84,6 +85,10 @@ export const datasetsApi = {
|
||||
},
|
||||
acquireOrthophoto: (projectId: string, payload: OrthophotoAcquireRequest): Promise<JobRead> =>
|
||||
apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/orthophoto/acquire`, payload),
|
||||
listOrthophotoProducts: (projectId: string): Promise<{ items: OrthophotoProductRead[]; total: number }> =>
|
||||
apiGet<{ items: OrthophotoProductRead[]; total: number }>(`/api/v1/projects/${projectId}/datasets/orthophoto/products`),
|
||||
orthophotoImageUrl: (projectId: string, datasetId: string): string =>
|
||||
`/api/v1/projects/${projectId}/datasets/${datasetId}/raster/image`,
|
||||
refreshMetadata: (projectId: string, datasetId: string): Promise<DatasetCreateResponse> =>
|
||||
apiPost<DatasetCreateResponse>(`/api/v1/projects/${projectId}/datasets/${datasetId}/metadata/refresh`, {}),
|
||||
inspectRaster: (projectId: string, datasetId: string): Promise<RasterInspectResponse> =>
|
||||
|
||||
@@ -5953,6 +5953,11 @@ section {
|
||||
background: rgba(107, 74, 170, 0.18);
|
||||
}
|
||||
|
||||
.geo-map-legend .geo-legend-imagery {
|
||||
border-color: #334155;
|
||||
background: linear-gradient(135deg, #7a9b68 0 33%, #d1b37a 33% 66%, #8eb5cb 66%);
|
||||
}
|
||||
|
||||
.geo-map-legend .geo-legend-added {
|
||||
border-color: #15803d;
|
||||
background: rgba(22, 163, 74, 0.2);
|
||||
@@ -6085,6 +6090,23 @@ section {
|
||||
min-height: 2.35rem;
|
||||
}
|
||||
|
||||
.geo-orthophoto-product {
|
||||
display: grid;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.geo-orthophoto-product select {
|
||||
width: 100%;
|
||||
min-height: 2.25rem;
|
||||
border: 1px solid #b9cec7;
|
||||
border-radius: 4px;
|
||||
padding: 0.35rem 0.5rem;
|
||||
background: #fff;
|
||||
color: #263a34;
|
||||
font: inherit;
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
.geo-image-analysis-acquiring,
|
||||
.geo-image-analysis-detecting,
|
||||
.geo-image-analysis-validating {
|
||||
@@ -6675,6 +6697,28 @@ section {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.source-catalog-loaded {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0.6rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.source-catalog-loaded article {
|
||||
display: grid;
|
||||
gap: 0.2rem;
|
||||
border-left: 3px solid #287051;
|
||||
padding: 0.6rem 0.75rem;
|
||||
background: #f2f8f5;
|
||||
}
|
||||
|
||||
.source-catalog-loaded span,
|
||||
.source-catalog-loaded p {
|
||||
margin: 0;
|
||||
color: #5d6d67;
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
.source-opportunity-list {
|
||||
margin-top: 0.8rem;
|
||||
border-top: 1px solid #dfe7e4;
|
||||
@@ -6716,6 +6760,7 @@ section {
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.source-catalog-grid,
|
||||
.source-catalog-loaded,
|
||||
.source-opportunity-list > div {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
@@ -6723,6 +6768,7 @@ section {
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.source-catalog-grid,
|
||||
.source-catalog-loaded,
|
||||
.source-opportunity-list > div {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -301,13 +301,31 @@ export interface VectorSelectionBBox {
|
||||
export interface OrthophotoAcquireRequest {
|
||||
bbox: VectorSelectionBBox
|
||||
area_id?: string
|
||||
product_key?: string
|
||||
force_refresh?: boolean
|
||||
}
|
||||
|
||||
export interface OrthophotoProductRead {
|
||||
key: string
|
||||
display_name: string
|
||||
observation_label: string
|
||||
temporal_granularity: string
|
||||
native_resolution_m: number
|
||||
supports_detection: boolean
|
||||
color_mode: string
|
||||
catalog_url: string
|
||||
limitation_message: string
|
||||
}
|
||||
|
||||
export interface OrthophotoAcquisitionResult {
|
||||
output_dataset_id: string
|
||||
reused: boolean
|
||||
provider: string
|
||||
product_key: string
|
||||
display_name: string
|
||||
observation_label: string
|
||||
temporal_granularity: string
|
||||
supports_detection: boolean
|
||||
layer: string
|
||||
width: number
|
||||
height: number
|
||||
@@ -318,6 +336,13 @@ export interface OrthophotoAcquisitionResult {
|
||||
limitation_message: string
|
||||
}
|
||||
|
||||
export interface MapImageOverlay {
|
||||
url: string
|
||||
bbox: [number, number, number, number]
|
||||
label: string
|
||||
opacity?: number
|
||||
}
|
||||
|
||||
export interface MapViewportState {
|
||||
bbox: VectorSelectionBBox
|
||||
zoom: number
|
||||
|
||||
Reference in New Issue
Block a user