Add governed bathymetry profile workflow
This commit is contained in:
@@ -635,3 +635,16 @@ validation remain inside the collapsed follow-up list with an explicit
|
||||
priority and examples of the measurements they should eventually support.
|
||||
Theme/time-series detail and active-source limitations are also collapsed by
|
||||
default so the Data workspace stays readable.
|
||||
|
||||
## Bathymetry profile map theme
|
||||
|
||||
Ready `vmm_vha_bathymetry_profiles` Datasets activate the `Waterbodem` map
|
||||
theme. The layer renders persisted profile points through the existing
|
||||
MapLibre/GeoJSON path. Clicking a point shows the watercourse, profile number,
|
||||
measurement date, nullable registered depth and an allowlisted official VHA
|
||||
profile-document link.
|
||||
|
||||
The UI always labels the points as historical cross-sections. It does not
|
||||
present them as a continuous bed raster and does not calculate water volume.
|
||||
The Sources workspace lists the future MDK North Sea and SPW Walloon
|
||||
bathymetry integrations as planned until backend acquisition is operational.
|
||||
|
||||
@@ -19,6 +19,7 @@ const THEME_LABELS: Record<string, string> = {
|
||||
nature_value: 'Natuurwaarde',
|
||||
agriculture: 'Landbouw',
|
||||
water: 'Water',
|
||||
bathymetry: 'Waterbodem en dwarsprofielen',
|
||||
flood_hazard: 'Overstromingsgevaar',
|
||||
elevation: 'Hoogte en reliëf',
|
||||
roads: 'Wegen en transport',
|
||||
@@ -81,6 +82,9 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
|
||||
)
|
||||
const dhmvDatasets = ready.filter((dataset) => dataset.source_name === 'digitaal_vlaanderen_dhmv')
|
||||
const floodHazardDatasets = ready.filter((dataset) => dataset.source_name === 'vmm_flood_hazard')
|
||||
const bathymetryProfileDatasets = ready.filter(
|
||||
(dataset) => dataset.source_name === 'vmm_vha_bathymetry_profiles',
|
||||
)
|
||||
const latestBuildingsRegister = [...buildingsRegisterDatasets].sort(
|
||||
(left, right) => new Date(right.observed_at ?? 0).getTime() - new Date(left.observed_at ?? 0).getTime(),
|
||||
)[0]
|
||||
@@ -184,7 +188,7 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
|
||||
</div>
|
||||
</details>
|
||||
|
||||
{waterinfoDatasets.length > 0 || historicalOrthophotos.length > 0 || bwkDatasets.length > 0 || agricultureDatasets.length > 0 || buildingsRegisterDatasets.length > 0 || dhmvDatasets.length > 0 || floodHazardDatasets.length > 0 ? (
|
||||
{waterinfoDatasets.length > 0 || historicalOrthophotos.length > 0 || bwkDatasets.length > 0 || agricultureDatasets.length > 0 || buildingsRegisterDatasets.length > 0 || dhmvDatasets.length > 0 || floodHazardDatasets.length > 0 || bathymetryProfileDatasets.length > 0 ? (
|
||||
<details className="source-loaded-disclosure">
|
||||
<summary>Actieve broncollecties en hun beperkingen</summary>
|
||||
<div className="source-catalog-loaded" aria-label="Aanvullende ingeladen bronnen">
|
||||
@@ -244,6 +248,18 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
|
||||
<p>Gemodelleerde maximumdiepte per kansscenario. Geen actuele waterstand, bathymetrie of permanent watervolume.</p>
|
||||
</article>
|
||||
) : null}
|
||||
{bathymetryProfileDatasets.length > 0 ? (
|
||||
<article>
|
||||
<strong>VHA-dwarsprofielen waterbodem</strong>
|
||||
<span>
|
||||
{bathymetryProfileDatasets.reduce(
|
||||
(total, dataset) => total + Number(dataset.feature_count ?? 0),
|
||||
0,
|
||||
).toLocaleString('nl-BE')} profielpunten · historische meetcampagnes
|
||||
</span>
|
||||
<p>Meetvelden en officiële profielbladen per punt. Geen continue actuele bathymetrie of volume zonder een gelijktijdig waterpeil.</p>
|
||||
</article>
|
||||
) : null}
|
||||
</div>
|
||||
</details>
|
||||
) : null}
|
||||
|
||||
@@ -36,7 +36,7 @@ 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' | 'flood_hazard' | 'elevation' | 'accessibility' | 'services' | 'roads' | 'parcels'
|
||||
type DataThemeId = 'buildings' | 'space_occupation' | 'open_space' | 'population' | 'forest' | 'nature_value' | 'agriculture' | 'soil' | 'water' | 'bathymetry' | 'flood_hazard' | 'elevation' | 'accessibility' | 'services' | 'roads' | 'parcels'
|
||||
|
||||
interface DataTheme {
|
||||
id: DataThemeId
|
||||
@@ -116,6 +116,13 @@ const DATA_THEMES: DataTheme[] = [
|
||||
description: 'Waterlopen, grachten, kanalen en wateroppervlakken.',
|
||||
tokens: ['waterways', 'waterway', 'water', 'hydro', 'river', 'stream', 'canal', 'waterloop'],
|
||||
},
|
||||
{
|
||||
id: 'bathymetry',
|
||||
label: 'Waterbodem',
|
||||
shortLabel: 'Dwarsprofielen',
|
||||
description: 'Officiële historische VHA-dwarsprofielen met meetvelden en brondocumenten.',
|
||||
tokens: ['bathymetry', 'bathymetry_profiles', 'dwarsprofielen', 'waterbodem'],
|
||||
},
|
||||
{
|
||||
id: 'flood_hazard',
|
||||
label: 'Overstroming',
|
||||
@@ -170,6 +177,7 @@ const DATA_THEME_MAP_STYLES: Record<DataThemeId, { fill: string; line: string }>
|
||||
agriculture: { fill: '#7b8f32', line: '#53671d' },
|
||||
soil: { fill: '#9a7040', line: '#6f4c27' },
|
||||
water: { fill: '#2676a8', line: '#155b85' },
|
||||
bathymetry: { fill: '#0e7490', line: '#164e63' },
|
||||
flood_hazard: { fill: '#1597c2', line: '#075985' },
|
||||
elevation: { fill: '#a57a4b', line: '#315f59' },
|
||||
accessibility: { fill: '#0f766e', line: '#115e59' },
|
||||
@@ -188,6 +196,11 @@ function datasetAvailabilityLabel(dataset: DatasetCreateResponse, partitionCount
|
||||
const resolution = Number(dataset.source_metadata?.['analysis_resolution_m'])
|
||||
return `${Number.isFinite(resolution) ? `${resolution.toLocaleString('nl-BE')} m` : 'Raster'} overstromingsscenario${regionalSuffix}`
|
||||
}
|
||||
if (dataset.source_name === 'vmm_vha_bathymetry_profiles') {
|
||||
const profiles = dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 0
|
||||
const documents = Number(dataset.source_metadata?.['document_count'] ?? 0)
|
||||
return `${profiles.toLocaleString('nl-BE')} profielen · ${documents.toLocaleString('nl-BE')} bronbladen`
|
||||
}
|
||||
if (dataset.dataset_type === 'raster' && dataset.source_name === 'department_omgeving_thematic_raster') {
|
||||
const resolution = Number(dataset.source_metadata?.['analysis_resolution_m'])
|
||||
const year = Number(dataset.source_metadata?.['observation_year'])
|
||||
@@ -221,6 +234,9 @@ function datasetMatchesTheme(dataset: DatasetCreateResponse, theme: DataTheme):
|
||||
if (dataset.source_name === 'vmm_flood_hazard') {
|
||||
return theme.id === 'flood_hazard'
|
||||
}
|
||||
if (dataset.source_name === 'vmm_vha_bathymetry_profiles') {
|
||||
return theme.id === 'bathymetry'
|
||||
}
|
||||
if (dataset.source_name === 'digitaal_vlaanderen_dhmv') {
|
||||
return theme.id === 'elevation'
|
||||
}
|
||||
@@ -310,6 +326,7 @@ function pickThemeDataset(
|
||||
(dataset.source_name === 'digitaal_vlaanderen_buildings_addresses_register' ? 5_000_000 : 0) +
|
||||
(dataset.source_name === 'digitaal_vlaanderen_dhmv' ? 5_000_000 : 0) +
|
||||
(dataset.source_name === 'vmm_flood_hazard' ? 5_000_000 : 0) +
|
||||
(dataset.source_name === 'vmm_vha_bathymetry_profiles' ? 5_000_000 : 0) +
|
||||
(dataset.source_metadata?.['product_key'] === 'dtm_1m' ? 1_000_000 : 0) +
|
||||
(dataset.source_metadata?.['product_key'] === 'pluviaal_current_t100' ? 1_000_000 : 0) +
|
||||
(dataset.dataset_role === 'reference' ? 10_000 : 0)
|
||||
@@ -618,6 +635,12 @@ export function MapWorkspace({
|
||||
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'
|
||||
|| featureProperties?.['measurement_semantics'] === 'historical_cross_section_profile_point'
|
||||
const bathymetryDocumentUrl = typeof featureProperties?.['source_document_url'] === 'string'
|
||||
&& featureProperties['source_document_url'].startsWith('https://vha.waterinfo.be/')
|
||||
? featureProperties['source_document_url']
|
||||
: null
|
||||
const featureSummaryEntries = featureProperties
|
||||
? Object.entries(featureProperties)
|
||||
.filter(([, value]) => value !== null && value !== undefined && typeof value !== 'object')
|
||||
@@ -2555,6 +2578,41 @@ export function MapWorkspace({
|
||||
</div>
|
||||
{selectedMapFeature ? (
|
||||
<>
|
||||
{isBathymetryProfile ? (
|
||||
<div className="bathymetry-profile-summary" aria-label="Samenvatting VHA-dwarsprofiel">
|
||||
<div>
|
||||
<span>Waterloop</span>
|
||||
<strong>{String(featureProperties?.['watercourse_name'] ?? 'Onbekende waterloop')}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Profiel</span>
|
||||
<strong>{String(featureProperties?.['profile_number'] ?? 'n.v.t.')}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Meetdatum</span>
|
||||
<strong>{String(featureProperties?.['measurement_date'] ?? 'Niet geregistreerd')}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Geregistreerde diepte</span>
|
||||
<strong>
|
||||
{typeof featureProperties?.['recorded_depth_m'] === 'number'
|
||||
? `${featureProperties['recorded_depth_m'].toLocaleString('nl-BE')} m`
|
||||
: 'Niet als veld beschikbaar'}
|
||||
</strong>
|
||||
</div>
|
||||
{bathymetryDocumentUrl ? (
|
||||
<a href={bathymetryDocumentUrl} target="_blank" rel="noreferrer">
|
||||
Officieel profielblad openen
|
||||
</a>
|
||||
) : (
|
||||
<small>Voor dit meetpunt is geen digitaal profielblad gekoppeld.</small>
|
||||
)}
|
||||
<p>
|
||||
Historisch dwarsprofiel. Dit punt is geen continue actuele bodemkaart en levert zonder
|
||||
gelijktijdig waterpeil geen actueel watervolume.
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="feature-extract-grid" aria-label="Geometrie van het geselecteerde object">
|
||||
<div>
|
||||
<span>Geometrie</span>
|
||||
|
||||
@@ -4,6 +4,7 @@ const DATASET_LABEL_BY_LAYER: Record<string, string> = {
|
||||
buildings: 'Bebouwing',
|
||||
roads: 'Wegen',
|
||||
water: 'Water',
|
||||
bathymetry_profiles: 'Dwarsprofielen waterbodem',
|
||||
parcels: 'Percelen',
|
||||
population: 'Bevolking',
|
||||
forest: 'Bos en groen',
|
||||
@@ -34,6 +35,7 @@ const DATASET_SOURCE_LABELS: Record<string, string> = {
|
||||
vrbg: 'Digitaal Vlaanderen',
|
||||
waterinfo: 'Waterinfo Vlaanderen',
|
||||
vmm_flood_hazard: 'Vlaamse Milieumaatschappij',
|
||||
vmm_vha_bathymetry_profiles: 'VMM / Vlaamse Hydrografische Atlas',
|
||||
department_omgeving_thematic_raster: 'Departement Omgeving',
|
||||
dov_soil_map: 'Databank Ondergrond Vlaanderen',
|
||||
}
|
||||
@@ -52,6 +54,9 @@ export function getDatasetDisplayName(dataset: DatasetCreateResponse): string {
|
||||
const productName = dataset.source_metadata?.['product_display_name']
|
||||
return typeof productName === 'string' && productName.trim() ? productName : 'VMM-overstromingsscenario'
|
||||
}
|
||||
if (dataset.source_name === 'vmm_vha_bathymetry_profiles') {
|
||||
return 'VHA-dwarsprofielen waterbodem'
|
||||
}
|
||||
if (dataset.source_name === 'department_omgeving_thematic_raster') {
|
||||
const productName = dataset.source_metadata?.['product_display_name']
|
||||
return typeof productName === 'string' && productName.trim() ? productName : 'Officieel Vlaams themaraster'
|
||||
|
||||
@@ -384,6 +384,42 @@ export const OFFICIAL_SOURCE_PORTFOLIO: OfficialSourceDefinition[] = [
|
||||
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/ogc-api-features-vlaamse-hydrografische-atlas-waterlopen',
|
||||
matches: (dataset) => sourceNameIs(dataset, 'vmm_vha'),
|
||||
},
|
||||
{
|
||||
key: 'vha_bathymetry_profiles',
|
||||
domain: 'climate',
|
||||
name: 'VHA-dwarsprofielen waterbodem',
|
||||
owner: 'Vlaamse Milieumaatschappij',
|
||||
coverage: 'Historische profielpunten op Vlaamse waterlopen',
|
||||
value: 'Gemeten profielinformatie en officiële bronbladen op exacte kaartlocaties.',
|
||||
metricExamples: 'aantal profielen, geregistreerde diepte en breedte per meetpunt',
|
||||
priority: 'next',
|
||||
url: 'https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer',
|
||||
matches: (dataset) => sourceNameIs(dataset, 'vmm_vha_bathymetry_profiles'),
|
||||
},
|
||||
{
|
||||
key: 'mdk_bcp_bathymetry',
|
||||
domain: 'climate',
|
||||
name: 'Dieptemodel Belgisch Continentaal Plat',
|
||||
owner: 'Agentschap Maritieme Dienstverlening en Kust',
|
||||
coverage: 'Belgische Noordzee, 20 x 20 m raster in LAT',
|
||||
value: 'Continue zeebodemhoogte voor mariene selectie en evolutie.',
|
||||
metricExamples: 'minimum/maximum diepte, diepteklassen en bodemvolume per peilcampagne',
|
||||
priority: 'planned',
|
||||
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/dieptemodel-van-de-zeebodem-belgisch-continentaal-plat-noordzee',
|
||||
matches: (dataset) => sourceNameIs(dataset, 'mdk_bcp_bathymetry'),
|
||||
},
|
||||
{
|
||||
key: 'spw_waterway_bathymetry',
|
||||
domain: 'climate',
|
||||
name: 'Waalse vaarweg- en stuwmeerbathymetrie',
|
||||
owner: 'Service public de Wallonie',
|
||||
coverage: 'Gemeten Waalse vaarwegen en stuwmeren, 0,5 m in mDNG',
|
||||
value: 'Hoogwaardige bodemrasters voor federale uitbreiding buiten Vlaanderen.',
|
||||
metricExamples: 'bodemhoogte, diepteprofiel en vergelijkbare meetcampagnes',
|
||||
priority: 'planned',
|
||||
url: 'https://geoportail.wallonie.be/catalogue/c450c28f-d357-48af-8423-62d524632cf9.html',
|
||||
matches: (dataset) => sourceNameIs(dataset, 'spw_walloon_waterway_bathymetry'),
|
||||
},
|
||||
]
|
||||
|
||||
export function operationalSources(
|
||||
|
||||
@@ -22,6 +22,8 @@ import type {
|
||||
FloodHazardAcquireRequest,
|
||||
FloodHazardProductRead,
|
||||
FloodHazardSelectionResponse,
|
||||
BathymetryProfileAcquireRequest,
|
||||
BathymetrySourceRead,
|
||||
DhmvProductRead,
|
||||
TerrainSelectionResponse,
|
||||
ThematicRasterAcquireRequest,
|
||||
@@ -167,6 +169,12 @@ export const datasetsApi = {
|
||||
payload: { bbox: VectorSelectionRequest['bbox']; area_id?: string; product_key: string },
|
||||
): Promise<FloodHazardSelectionResponse> =>
|
||||
apiPost<FloodHazardSelectionResponse>(`/api/v1/projects/${projectId}/datasets/raster/flood-hazard/select`, payload),
|
||||
listBathymetrySources: (projectId: string): Promise<{ items: BathymetrySourceRead[]; total: number }> =>
|
||||
apiGet<{ items: BathymetrySourceRead[]; total: number }>(
|
||||
`/api/v1/projects/${projectId}/datasets/bathymetry/sources`,
|
||||
),
|
||||
acquireBathymetryProfiles: (projectId: string, payload: BathymetryProfileAcquireRequest): Promise<JobRead> =>
|
||||
apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/bathymetry/profiles/acquire`, payload),
|
||||
acquireThematicRaster: (projectId: string, payload: ThematicRasterAcquireRequest): Promise<JobRead> =>
|
||||
apiPost<JobRead>(`/api/v1/projects/${projectId}/datasets/thematic-raster/acquire`, payload),
|
||||
listThematicRasterProducts: (projectId: string): Promise<{ items: ThematicRasterProductRead[]; total: number }> =>
|
||||
|
||||
@@ -5966,6 +5966,11 @@ section {
|
||||
background: rgba(38, 118, 168, 0.24);
|
||||
}
|
||||
|
||||
.geo-map-legend .geo-legend-layer-bathymetry {
|
||||
background: #0e7490;
|
||||
border-color: #164e63;
|
||||
}
|
||||
|
||||
.geo-map-legend .geo-legend-layer-soil {
|
||||
border-color: #6f4c27;
|
||||
background: rgba(154, 112, 64, 0.24);
|
||||
@@ -6036,6 +6041,60 @@ section {
|
||||
.geo-legend-ramp-population { background: linear-gradient(90deg, #eee7f6, #673a97); }
|
||||
.geo-legend-ramp-accessibility { background: linear-gradient(90deg, #e9f1f4, #0f766e); }
|
||||
.geo-legend-ramp-services { background: linear-gradient(90deg, #fff4bf, #b66d16); }
|
||||
.geo-legend-ramp-bathymetry { background: linear-gradient(90deg, #cffafe, #0e7490); }
|
||||
|
||||
.bathymetry-profile-summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 0.65rem;
|
||||
margin-bottom: 0.9rem;
|
||||
padding: 0.8rem;
|
||||
border: 1px solid #a5d8e6;
|
||||
border-radius: 6px;
|
||||
background: #effbff;
|
||||
}
|
||||
|
||||
.bathymetry-profile-summary > div {
|
||||
display: grid;
|
||||
gap: 0.2rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bathymetry-profile-summary span,
|
||||
.bathymetry-profile-summary small {
|
||||
color: #46646f;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.bathymetry-profile-summary strong {
|
||||
overflow-wrap: anywhere;
|
||||
color: #113d4b;
|
||||
}
|
||||
|
||||
.bathymetry-profile-summary a,
|
||||
.bathymetry-profile-summary p,
|
||||
.bathymetry-profile-summary small {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.bathymetry-profile-summary a {
|
||||
width: fit-content;
|
||||
color: #075985;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.bathymetry-profile-summary p {
|
||||
margin: 0;
|
||||
color: #315867;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.bathymetry-profile-summary {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.geo-map-legend .geo-legend-added {
|
||||
border-color: #15803d;
|
||||
|
||||
@@ -442,6 +442,33 @@ export interface FloodHazardSelectionResponse {
|
||||
generated_at: string
|
||||
}
|
||||
|
||||
export interface BathymetryProfileAcquireRequest {
|
||||
bbox: VectorSelectionBBox
|
||||
area_id?: string | null
|
||||
force_refresh?: boolean
|
||||
}
|
||||
|
||||
export interface BathymetrySourceRead {
|
||||
key: string
|
||||
display_name: string
|
||||
owner: string
|
||||
authority_level: 'authoritative' | 'contextual'
|
||||
geographic_coverage: string
|
||||
data_kind: string
|
||||
query_modes: string[]
|
||||
vertical_reference: string
|
||||
horizontal_crs: string
|
||||
native_resolution?: string | null
|
||||
integration_status: 'operational' | 'available_not_integrated' | 'catalog_only'
|
||||
acquisition_supported: boolean
|
||||
configured: boolean
|
||||
service_url?: string | null
|
||||
catalog_url: string
|
||||
attribution: string
|
||||
license_note: string
|
||||
limitation_message: string
|
||||
}
|
||||
|
||||
export interface ThematicRasterAcquireRequest {
|
||||
bbox: VectorSelectionBBox
|
||||
area_id?: string | null
|
||||
|
||||
Reference in New Issue
Block a user