Broaden official source portfolio
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-16 02:04:31 +02:00
parent 8628582caa
commit fdf3881592
10 changed files with 970 additions and 197 deletions
+14
View File
@@ -547,3 +547,17 @@ regional Dataset remains available and the backend clips metrics and map output
to the selected municipality or drawn rectangle. The Sources workspace reports
overlapping BWK area snapshots as separate coverages and does not sum their
feature counts as if they were disjoint observations.
## Platform-wide source portfolio
The Sources inventory is organized around six end-user questions rather than
provider technology: space/buildings, nature/agriculture, soil/relief,
mobility/accessibility, population/services and climate/living environment.
The central definitions live in `src/lib/sourcePortfolio.ts`.
The domain cards count only matching `ready` Datasets as operational. Audited
official sources that have not passed acquisition, persistence and metric
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.
@@ -1,5 +1,12 @@
import type { DatasetCreateResponse } from '../../types'
import { getDatasetSourceDisplayName } from '../../lib/datasetDisplay'
import {
OFFICIAL_SOURCE_PORTFOLIO,
SOURCE_DOMAINS,
operationalSources,
sourcesForDomain,
type SourcePriority,
} from '../../lib/sourcePortfolio'
interface SourceCatalogPanelProps {
datasets: DatasetCreateResponse[]
@@ -18,65 +25,6 @@ const THEME_LABELS: Record<string, string> = {
parcels: 'Percelen',
}
const AVAILABLE_SOURCES = [
{
key: 'historical_orthophoto',
name: 'Historische orthofoto’s',
owner: 'Digitaal Vlaanderen',
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',
value: 'Natuurwaarde, biotopen en habitattypen',
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',
value: 'Landbouwoppervlakte, teelten en perceelevolutie',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/open-geodata-landbouwgebruikspercelen',
},
{
key: 'buildings_register',
name: 'Gebouwen- en adressenregister',
owner: 'Digitaal Vlaanderen',
coverage: 'Continu geactualiseerd',
value: 'Gebouwstatus, levensloop en adressen als aanvulling op GRB',
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',
value: 'Hoogte, reliëf en helling; afstroming is afgeleid, geen waterdiepte',
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',
value: 'Hydrologische toestand; geen gebiedsdekkend watervolume zonder bodemprofiel',
url: 'https://waterinfo.vlaanderen.be/',
},
{
key: 'flood_hazard',
name: 'Overstromingsgevaarkaarten waterdiepte',
owner: 'Vlaamse Milieumaatschappij',
coverage: 'Fluviaal/pluviaal, T10/T100/T1000, huidig klimaat en projectie 2050',
value: 'Gemodelleerd overstroomd oppervlak en maximale waterdiepte per vast scenario; geen bathymetrie',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/publieke-inspire-coverage-service-van-ogrk',
},
]
function datasetTheme(dataset: DatasetCreateResponse): string | null {
const configured = String(dataset.source_metadata?.['theme'] ?? dataset.reference_layer_name ?? '').toLowerCase()
if (configured === 'built' || configured === 'building') return 'buildings'
@@ -85,6 +33,12 @@ function datasetTheme(dataset: DatasetCreateResponse): string | null {
return null
}
function priorityLabel(priority: SourcePriority): string {
if (priority === 'next') return 'Eerstvolgend'
if (priority === 'planned') return 'Daarna'
return 'Later'
}
function timelineSummary(
temporalCount: number,
temporalSeriesCount: number,
@@ -132,15 +86,23 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
)[0]
const agricultureYears = agricultureDatasets
.flatMap((dataset) => dataset.observed_at ? [new Date(dataset.observed_at).getUTCFullYear()] : [])
const pendingSources = AVAILABLE_SOURCES.filter((source) => {
if (source.key === 'waterinfo') return waterinfoDatasets.length === 0
if (source.key === 'historical_orthophoto') return historicalOrthophotos.length === 0
const availableSources = operationalSources(ready)
const availableSourceKeys = new Set(availableSources.map((source) => source.key))
const pendingSources = OFFICIAL_SOURCE_PORTFOLIO.filter((source) => {
if (source.key === 'bwk') return bwkDatasets.length === 0
if (source.key === 'agriculture') return agricultureDatasets.length === 0
if (source.key === 'buildings_register') return buildingsRegisterDatasets.length === 0
if (source.key === 'elevation') return dhmvDatasets.length === 0
if (source.key === 'flood_hazard') return floodHazardDatasets.length === 0
return true
return !availableSourceKeys.has(source.key)
})
const domains = SOURCE_DOMAINS.map((domain) => {
const sources = sourcesForDomain(domain.key)
const loaded = sources.filter((source) => availableSourceKeys.has(source.key))
const next = sources.filter((source) => source.priority === 'next' && !availableSourceKeys.has(source.key))
return {
...domain,
sourceCount: sources.length,
loadedCount: loaded.length,
nextCount: next.length,
loadedNames: loaded.map((source) => source.name),
}
})
const themes = Object.keys(THEME_LABELS).map((theme) => {
const matches = ready.filter((dataset) => datasetTheme(dataset) === theme)
@@ -174,33 +136,58 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
<div className="panel-title-row">
<div>
<span className="section-kicker">Broninventaris</span>
<h2>Wat is werkelijk beschikbaar?</h2>
<p className="muted">Ingeladen bronnen staan direct klaar voor de kaart. Andere officiële bronnen worden pas gebruikt na een gecontroleerde import.</p>
<h2>Welke vragen kan GeoIntel beantwoorden?</h2>
<p className="muted">Zes domeinen vormen samen het platform. Alleen werkelijk ingeladen bronnen krijgen de status operationeel; de rest blijft een transparante aansluitplanning.</p>
</div>
<span className="status-badge status-badge-ready">{ready.length} datasets klaar</span>
<span className="status-badge status-badge-ready">{availableSources.length} officiële bronnen operationeel</span>
</div>
<div className="source-catalog-grid">
{themes.map((theme) => (
<article className="source-catalog-card" key={theme.theme}>
<div className="source-domain-grid" aria-label="Platformbrede brondomeinen">
{domains.map((domain) => (
<article className="source-domain-card" key={domain.key}>
<div>
<strong>{theme.label}</strong>
<span>{theme.source ?? 'Nog niet ingeladen'}</span>
<strong>{domain.label}</strong>
<span>{domain.loadedCount} van {domain.sourceCount} bronnen operationeel</span>
</div>
<b>{theme.datasetCount > 0 ? 'Beschikbaar' : 'Ontbreekt'}</b>
<p>{timelineSummary(
theme.temporalCount,
theme.temporalSeriesCount,
theme.otherMethodCount,
theme.firstYear,
theme.lastYear,
)}</p>
<b className={domain.loadedCount > 0 ? 'source-status-operational' : 'source-status-planned'}>
{domain.loadedCount > 0 ? 'Operationeel' : 'Aansluiting nodig'}
</b>
<p>{domain.question}</p>
<small>
{domain.loadedNames.length > 0
? domain.loadedNames.slice(0, 2).join(' · ')
: `${domain.nextCount} geverifieerde bron${domain.nextCount === 1 ? '' : 'nen'} eerstvolgend`}
</small>
</article>
))}
</div>
<details className="source-theme-coverage">
<summary>Dekking per kaartthema en tijdreeks</summary>
<div className="source-catalog-grid">
{themes.map((theme) => (
<article className="source-catalog-card" key={theme.theme}>
<div>
<strong>{theme.label}</strong>
<span>{theme.source ?? 'Nog niet ingeladen'}</span>
</div>
<b>{theme.datasetCount > 0 ? 'Beschikbaar' : 'Ontbreekt'}</b>
<p>{timelineSummary(
theme.temporalCount,
theme.temporalSeriesCount,
theme.otherMethodCount,
theme.firstYear,
theme.lastYear,
)}</p>
</article>
))}
</div>
</details>
{waterinfoDatasets.length > 0 || historicalOrthophotos.length > 0 || bwkDatasets.length > 0 || agricultureDatasets.length > 0 || buildingsRegisterDatasets.length > 0 || dhmvDatasets.length > 0 || floodHazardDatasets.length > 0 ? (
<div className="source-catalog-loaded" aria-label="Aanvullende ingeladen bronnen">
<details className="source-loaded-disclosure">
<summary>Actieve broncollecties en hun beperkingen</summary>
<div className="source-catalog-loaded" aria-label="Aanvullende ingeladen bronnen">
{waterinfoDatasets.length > 0 ? (
<article>
<strong>Waterinfo meetreeksen</strong>
@@ -257,22 +244,39 @@ export function SourceCatalogPanel({ datasets }: SourceCatalogPanelProps): JSX.E
<p>Gemodelleerde maximumdiepte per kansscenario. Geen actuele waterstand, bathymetrie of permanent watervolume.</p>
</article>
) : null}
</div>
</div>
</details>
) : null}
<details className="source-opportunity-list">
<summary>Officiële bronnen die hierna kunnen worden ingeladen</summary>
<div>
{pendingSources.map((source) => (
<article key={source.name}>
<div>
<strong>{source.name}</strong>
<span>{source.owner} · {source.coverage}</span>
</div>
<p>{source.value}</p>
<a href={source.url} target="_blank" rel="noreferrer">Bekijk officiële bron</a>
</article>
))}
<div className="source-opportunity-domains">
{SOURCE_DOMAINS.map((domain) => {
const sources = pendingSources.filter((source) => source.domain === domain.key)
if (sources.length === 0) return null
return (
<section className="source-opportunity-domain" key={domain.key}>
<header>
<strong>{domain.label}</strong>
<span>{sources.length} gecontroleerde kandidaatbron{sources.length === 1 ? '' : 'nen'}</span>
</header>
<div>
{sources.map((source) => (
<article key={source.name}>
<div>
<strong>{source.name}</strong>
<span>{source.owner} · {source.coverage}</span>
</div>
<span className={`source-priority source-priority-${source.priority}`}>{priorityLabel(source.priority)}</span>
<p>{source.value}</p>
<small>Meetbaar als: {source.metricExamples}</small>
<a href={source.url} target="_blank" rel="noreferrer">Bekijk officiële bron</a>
</article>
))}
</div>
</section>
)
})}
</div>
</details>
</section>
+398
View File
@@ -0,0 +1,398 @@
import type { DatasetCreateResponse } from '../types'
export type SourceDomainKey =
| 'space'
| 'nature'
| 'soil'
| 'mobility'
| 'people'
| 'climate'
export type SourcePriority = 'next' | 'planned' | 'later'
export interface SourceDomainDefinition {
key: SourceDomainKey
label: string
question: string
}
export interface OfficialSourceDefinition {
key: string
domain: SourceDomainKey
name: string
owner: string
coverage: string
value: string
metricExamples: string
priority: SourcePriority
url: string
matches: (dataset: DatasetCreateResponse) => boolean
}
const sourceNameIs = (dataset: DatasetCreateResponse, sourceName: string): boolean =>
dataset.source_name === sourceName
const referenceLayerIs = (dataset: DatasetCreateResponse, layerName: string): boolean =>
String(dataset.reference_layer_name ?? dataset.source_metadata?.['theme'] ?? '').toLowerCase() === layerName
export const SOURCE_DOMAINS: SourceDomainDefinition[] = [
{
key: 'space',
label: 'Ruimte en bebouwing',
question: 'Wat staat en gebeurt hier, en hoe verandert het ruimtegebruik?',
},
{
key: 'nature',
label: 'Natuur en landbouw',
question: 'Hoeveel bos, natuurwaarde en landbouwgebruik ligt in de selectie?',
},
{
key: 'soil',
label: 'Bodem en reliëf',
question: 'Welke bodem, hoogte, helling en erosiegevoeligheid komen voor?',
},
{
key: 'mobility',
label: 'Mobiliteit en bereikbaarheid',
question: 'Welke netwerken en knooppunten maken het gebied bereikbaar?',
},
{
key: 'people',
label: 'Bevolking en voorzieningen',
question: 'Hoeveel mensen wonen er en hoe bereikbaar zijn dagelijkse diensten?',
},
{
key: 'climate',
label: 'Klimaat en leefomgeving',
question: 'Welke risico’s en omgevingsdruk spelen rond hitte, lucht en water?',
},
]
export const OFFICIAL_SOURCE_PORTFOLIO: OfficialSourceDefinition[] = [
{
key: 'grb_buildings',
domain: 'space',
name: 'GRB-gebouwen',
owner: 'Digitaal Vlaanderen',
coverage: 'Actuele gebouwcontouren',
value: 'Objectniveau voor bebouwing, kaartselectie en AI-validatie.',
metricExamples: 'aantal gebouwen, bebouwde oppervlakte, dichtheid',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/grbgis',
matches: (dataset) => sourceNameIs(dataset, 'grb') && referenceLayerIs(dataset, 'buildings'),
},
{
key: 'building_register',
domain: 'space',
name: 'Gebouwen- en Adressenregister',
owner: 'Digitaal Vlaanderen',
coverage: 'Continu geactualiseerd',
value: 'Gebouwstatus en geaggregeerde eenheden als aanvulling op GRB.',
metricExamples: 'gerealiseerde gebouwen, gebouweenheden, statusverdeling',
priority: 'planned',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/gebouwen-en-adressenregister',
matches: (dataset) => sourceNameIs(dataset, 'digitaal_vlaanderen_buildings_addresses_register'),
},
{
key: 'land_use',
domain: 'space',
name: 'Landgebruik Vlaanderen',
owner: 'Departement Omgeving',
coverage: '10 m raster, meerdere officiële toestanden',
value: 'Werkelijk gebruik voor wonen, industrie, landbouw en natuur.',
metricExamples: 'hectare per landgebruiksklasse, aandeel van selectie, evolutie',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/landgebruik-vlaanderen-toestand-2022',
matches: (dataset) => sourceNameIs(dataset, 'department_omgeving_land_use') || sourceNameIs(dataset, 'historical_landuse'),
},
{
key: 'orthophoto',
domain: 'space',
name: 'Actuele en historische orthofoto’s',
owner: 'Digitaal Vlaanderen',
coverage: '1971, 1979-1990 en jaarlijkse reeksen 2000-2025',
value: 'Beeldbewijs voor actuele toestand en visuele evolutie.',
metricExamples: 'beeldjaargang en begrensde AI- of visuele analyse',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/orthofotomozaiek-kleinschalig-zomeropnamen',
matches: (dataset) => sourceNameIs(dataset, 'digitaal_vlaanderen_orthophoto'),
},
{
key: 'space_occupation',
domain: 'space',
name: 'Ruimtebeslag Vlaanderen 2025',
owner: 'Departement Omgeving',
coverage: '10 m raster, toestand 2025',
value: 'Gebiedsdruk en ingenomen ruimte naast het fijnere GRB-objectbeeld.',
metricExamples: 'hectare ruimtebeslag, aandeel, vergelijking met open ruimte',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/ruimtebeslag-vlaanderen-toestand-2025',
matches: (dataset) => sourceNameIs(dataset, 'department_omgeving_space_occupation'),
},
{
key: 'settlement_typology',
domain: 'space',
name: 'Kernen, linten en verspreide bebouwing',
owner: 'Departement Omgeving',
coverage: 'Toestand 2022',
value: 'Begrijpelijke morfologie voor kern, lint en verspreide bebouwing.',
metricExamples: 'oppervlakte en aandeel per morfologisch type',
priority: 'planned',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/kernen-linten-verspreide-bebouwing-in-vlaanderen-kernen-toestand-2022',
matches: (dataset) => sourceNameIs(dataset, 'department_omgeving_settlement_typology'),
},
{
key: 'business_parks',
domain: 'space',
name: 'Bedrijventerreinen OSLO',
owner: 'VLAIO / Digitaal Vlaanderen',
coverage: 'Productieservice, toestand 2026',
value: 'Economische ruimte, bezetting en bedrijventerreincontext.',
metricExamples: 'hectare bedrijventerrein, bezette/onbezette oppervlakte',
priority: 'planned',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/wfs-bedrijventerreinen',
matches: (dataset) => sourceNameIs(dataset, 'vlaio_business_parks'),
},
{
key: 'bwk',
domain: 'nature',
name: 'BWK / Natura 2000',
owner: 'INBO',
coverage: 'Toestand 2025',
value: 'Biologische waardering, biotopen en habitattypen.',
metricExamples: 'hectare natuurwaarde, habitat en regionaal belangrijk biotoop',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/biologische-waarderingskaart-en-natura-2000-habitatkaart-toestand-2025',
matches: (dataset) => sourceNameIs(dataset, 'inbo_bwk_natura2000'),
},
{
key: 'agriculture',
domain: 'nature',
name: 'Landbouwgebruikspercelen',
owner: 'Agentschap Landbouw en Zeevisserij',
coverage: 'Jaarlijkse definitieve bestanden',
value: 'Landbouwoppervlakte en teeltevolutie zonder perceellijnage te verzinnen.',
metricExamples: 'hectare landbouw en hoofdteeltgroep per jaar',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/open-geodata-landbouwgebruikspercelen',
matches: (dataset) => sourceNameIs(dataset, 'agentschap_landbouw_zeevisserij_agricultural_parcels'),
},
{
key: 'open_space',
domain: 'nature',
name: 'Open ruimte Vlaanderen',
owner: 'Departement Omgeving',
coverage: '10 m raster, toestand 2022',
value: 'Openruimte-aandeel en versnippering naast ruimtebeslag en landgebruik.',
metricExamples: 'hectare open ruimte, aandeel en fragmentatie',
priority: 'planned',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/open-ruimte-vlaanderen-toestand-2022',
matches: (dataset) => sourceNameIs(dataset, 'department_omgeving_open_space'),
},
{
key: 'dhmv',
domain: 'soil',
name: 'Digitaal Hoogtemodel Vlaanderen II',
owner: 'Digitaal Vlaanderen',
coverage: 'DTM/DSM, opname 2013-2015',
value: 'Hoogte, reliëf en helling als gemeten terreinbasis.',
metricExamples: 'hoogte m TAW, reliëf en helling in graden',
priority: 'next',
url: 'https://www.vlaanderen.be/digitaal-vlaanderen/onze-diensten-en-platformen/earth-observation-data-science-eodas/het-digitaal-hoogtemodel/digitaal-hoogtemodel-vlaanderen-ii',
matches: (dataset) => sourceNameIs(dataset, 'digitaal_vlaanderen_dhmv'),
},
{
key: 'soil_map',
domain: 'soil',
name: 'Digitale bodemkaart',
owner: 'Databank Ondergrond Vlaanderen',
coverage: 'Vlaams Gewest, historische kartering',
value: 'Bodemtype, textuur en drainage voor landbouw en geschiktheid.',
metricExamples: 'hectare per bodemtype, drainage- en textuurverdeling',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/digitale-bodemkaart-van-het-vlaams-gewest-bodemtypes',
matches: (dataset) => sourceNameIs(dataset, 'dov_soil_map'),
},
{
key: 'erosion',
domain: 'soil',
name: 'Potentiële bodemerosie per perceel',
owner: 'Departement Omgeving',
coverage: 'Perceelmodel, editie 2023',
value: 'Potentiële erosiedruk gecombineerd met landbouw en reliëf.',
metricExamples: 'hectare per erosieklasse en aandeel risicogebied',
priority: 'planned',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/potentiele-bodemerosiekaart-per-perceel-2023',
matches: (dataset) => sourceNameIs(dataset, 'department_omgeving_erosion'),
},
{
key: 'grb_roads',
domain: 'mobility',
name: 'GRB-wegen en transport',
owner: 'Digitaal Vlaanderen',
coverage: 'Actuele wegcontext',
value: 'Wegennet en transportcontext in de kaartselectie.',
metricExamples: 'kilometer weg, dichtheid en afstand tot netwerk',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/grbgis',
matches: (dataset) => sourceNameIs(dataset, 'grb') && referenceLayerIs(dataset, 'roads'),
},
{
key: 'node_value',
domain: 'mobility',
name: 'Knooppuntwaarde per hectare',
owner: 'Departement Omgeving',
coverage: '1 ha raster, toestand 2022',
value: 'Modelmatige bereikbaarheid via collectief vervoer.',
metricExamples: 'gemiddelde score en aandeel per bereikbaarheidsklasse',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/knooppuntwaarde-per-ha-toestand-2022',
matches: (dataset) => sourceNameIs(dataset, 'department_omgeving_node_value'),
},
{
key: 'hoppin',
domain: 'mobility',
name: 'Hoppinpunten en mobiliteitsknooppunten',
owner: 'Departement Mobiliteit en Openbare Werken',
coverage: 'Geplande en gerealiseerde punten',
value: 'Multimodale overstappunten en voorzieningen in de buurt.',
metricExamples: 'aantal punten, status en afstand tot selectie',
priority: 'planned',
url: 'https://www.vlaanderen.be/DataCatalogRecord/c262d7d0-d90d-4695-a8c6-6427ef4abb38',
matches: (dataset) => sourceNameIs(dataset, 'mow_hoppin'),
},
{
key: 'cycle_highways',
domain: 'mobility',
name: 'Fietssnelwegen',
owner: 'Vereniging van de Vlaamse Provincies',
coverage: 'Vlaams netwerk, productieservice',
value: 'Gerealiseerde en geplande functionele fietsverbindingen.',
metricExamples: 'kilometer route, realisatiestatus en kwaliteit',
priority: 'planned',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/fietssnelwegen-wfs',
matches: (dataset) => sourceNameIs(dataset, 'vvp_cycle_highways'),
},
{
key: 'statistical_sectors',
domain: 'people',
name: 'Statistische sectoren',
owner: 'Statbel / Digitaal Vlaanderen',
coverage: 'Gebiedsindeling, toestand 2024/2025',
value: 'Betrouwbare ruimtelijke sleutel voor socio-economische indicatoren.',
metricExamples: 'sectorprofielen en gewogen indicatoren per selectie',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/statistische-sectoren-van-belgie-toestand-01-01-2024',
matches: (dataset) => sourceNameIs(dataset, 'statbel') && referenceLayerIs(dataset, 'statistical_sectors'),
},
{
key: 'population_density',
domain: 'people',
name: 'Inwonersdichtheid per hectare',
owner: 'Departement Omgeving / Statbel',
coverage: '1 ha raster, toestand 2019',
value: 'Gebiedsdekkende bevolkingsdichtheid zonder adressen te tonen.',
metricExamples: 'geschat aantal inwoners en inwoners per hectare',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/inwonersdichtheid-per-ha-vlaanderen-toestand-2019',
matches: (dataset) => sourceNameIs(dataset, 'department_omgeving_population_density'),
},
{
key: 'service_level',
domain: 'people',
name: 'Totaal voorzieningenniveau',
owner: 'Departement Omgeving',
coverage: '1 ha raster, toestand 2022',
value: 'Nabijheid van dagelijkse voorzieningen in één brongetrouwe score.',
metricExamples: 'gemiddelde score en aandeel per voorzieningsklasse',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/totaal-voorzieningenniveau-toestand-2022',
matches: (dataset) => sourceNameIs(dataset, 'department_omgeving_service_level'),
},
{
key: 'municipality_indicators',
domain: 'people',
name: 'Gemeente in cijfers',
owner: 'Vlaamse Milieumaatschappij',
coverage: 'Gemeentelijke klimaat- en leefomgevingsindicatoren',
value: 'Contextcijfers voor Mol en vergelijking met andere gemeenten.',
metricExamples: 'hittegolfdagen, temperatuur en luchtkwaliteitsindex',
priority: 'later',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/gemeente-in-cijfers',
matches: (dataset) => sourceNameIs(dataset, 'vmm_municipality_indicators'),
},
{
key: 'flood_hazard',
domain: 'climate',
name: 'VMM-overstromingsgevaar',
owner: 'Vlaamse Milieumaatschappij',
coverage: 'Fluviaal/pluviaal, T10/T100/T1000, huidig en 2050',
value: 'Gemodelleerde lokale maximumdiepte per kansscenario.',
metricExamples: 'hectare positieve diepte, gemiddelde/P90/maximale diepte',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/publieke-inspire-coverage-service-van-ogrk',
matches: (dataset) => sourceNameIs(dataset, 'vmm_flood_hazard'),
},
{
key: 'waterinfo',
domain: 'climate',
name: 'Waterinfo meetreeksen',
owner: 'Waterinfo Vlaanderen',
coverage: 'Stationgebonden jaarmetingen',
value: 'Hydrologische waarnemingen als puntreeksen, niet als gebiedsvolume.',
metricExamples: 'waterstand, debiet en neerslag per station en periode',
priority: 'planned',
url: 'https://waterinfo.vlaanderen.be/',
matches: (dataset) => sourceNameIs(dataset, 'waterinfo'),
},
{
key: 'heat_stress',
domain: 'climate',
name: 'Hitte-eilanden in steden',
owner: 'Vlaamse Milieumaatschappij',
coverage: 'Jaarbeelden 2019-2022',
value: 'Ruimtelijke blootstelling aan hittestress voor leefomgevingsanalyse.',
metricExamples: 'hittegolfgraaddagen en aandeel blootgesteld gebied',
priority: 'next',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/hitte-eilanden-in-steden',
matches: (dataset) => sourceNameIs(dataset, 'vmm_heat_stress'),
},
{
key: 'air_quality',
domain: 'climate',
name: 'Luchtkwaliteit',
owner: 'Vlaamse Milieumaatschappij / Departement Omgeving',
coverage: 'Meetreeksen en geselecteerde gebiedskaarten',
value: 'PM2,5 en andere luchtindicatoren met expliciete schaal en jaargang.',
metricExamples: 'concentratie, normoverschrijding en trend per brongebied',
priority: 'planned',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/concentratie-fijn-stof-pm25-in-de-omgevingslucht',
matches: (dataset) => sourceNameIs(dataset, 'vmm_air_quality'),
},
{
key: 'vha',
domain: 'climate',
name: 'Vlaamse Hydrografische Atlas',
owner: 'Vlaamse Milieumaatschappij',
coverage: 'Actuele gecategoriseerde waterlopen',
value: 'Autoritatief waterlopennetwerk voor lengte, categorie en beheerder.',
metricExamples: 'kilometer waterloop per categorie en afstand tot netwerk',
priority: 'later',
url: 'https://www.vlaanderen.be/datavindplaats/catalogus/ogc-api-features-vlaamse-hydrografische-atlas-waterlopen',
matches: (dataset) => sourceNameIs(dataset, 'vmm_vha'),
},
]
export function operationalSources(
datasets: DatasetCreateResponse[],
domain?: SourceDomainKey,
): OfficialSourceDefinition[] {
const ready = datasets.filter((dataset) => dataset.status === 'ready')
return OFFICIAL_SOURCE_PORTFOLIO.filter(
(source) => (!domain || source.domain === domain) && ready.some(source.matches),
)
}
export function sourcesForDomain(domain: SourceDomainKey): OfficialSourceDefinition[] {
return OFFICIAL_SOURCE_PORTFOLIO.filter((source) => source.domain === domain)
}
+156 -5
View File
@@ -6669,13 +6669,91 @@ section {
grid-column: 1 / -1;
}
.source-catalog-grid {
.source-domain-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.65rem;
margin-top: 1rem;
}
.source-domain-card {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.42rem 0.7rem;
min-width: 0;
border: 1px solid #d9e4df;
border-left: 3px solid #75968a;
border-radius: 6px;
padding: 0.82rem;
background: #fbfcfc;
}
.source-domain-card > div {
display: grid;
min-width: 0;
gap: 0.14rem;
}
.source-domain-card span,
.source-domain-card p,
.source-domain-card small {
margin: 0;
color: #62716c;
font-size: 0.73rem;
line-height: 1.42;
}
.source-domain-card b {
align-self: start;
font-size: 0.68rem;
}
.source-domain-card p,
.source-domain-card small {
grid-column: 1 / -1;
}
.source-domain-card small {
border-top: 1px solid #e6ece9;
padding-top: 0.4rem;
}
.source-status-operational {
color: #286646;
}
.source-status-planned {
color: #8a5a16;
}
.source-theme-coverage,
.source-loaded-disclosure,
.source-catalog-grid {
min-width: 0;
}
.source-theme-coverage,
.source-loaded-disclosure {
margin-top: 0.8rem;
border-top: 1px solid #dfe7e4;
padding-top: 0.7rem;
}
.source-theme-coverage > summary,
.source-loaded-disclosure > summary {
cursor: pointer;
color: #34433e;
font-size: 0.82rem;
font-weight: 750;
}
.source-catalog-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.65rem;
margin-top: 0.7rem;
}
.source-catalog-card {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
@@ -6715,7 +6793,7 @@ section {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.6rem;
margin-top: 0.75rem;
margin-top: 0.7rem;
}
.source-catalog-loaded article {
@@ -6747,12 +6825,47 @@ section {
.source-opportunity-list > div {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-columns: minmax(0, 1fr);
gap: 0.6rem;
margin-top: 0.7rem;
}
.source-opportunity-domain {
display: grid;
gap: 0.55rem;
border: 0;
border-top: 1px solid #e2e8e6;
border-radius: 0;
padding: 0.7rem 0 0;
background: transparent;
box-shadow: none;
}
.source-opportunity-domain:first-child {
border-top: 0;
padding-top: 0;
}
.source-opportunity-domain > header {
display: flex;
gap: 0.7rem;
align-items: baseline;
justify-content: space-between;
}
.source-opportunity-domain > header span {
color: #66746f;
font-size: 0.7rem;
}
.source-opportunity-domain > div {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.55rem;
}
.source-opportunity-list article {
position: relative;
display: grid;
gap: 0.35rem;
padding: 0.75rem;
@@ -6772,20 +6885,58 @@ section {
font-weight: 700;
}
.source-opportunity-list article > small {
margin: 0;
color: #52635d;
font-size: 0.7rem;
line-height: 1.4;
}
.source-priority {
position: absolute;
top: 0.7rem;
right: 0.7rem;
border-left: 2px solid #718096;
padding-left: 0.38rem;
font-size: 0.65rem !important;
font-weight: 800;
}
.source-opportunity-list article > div {
padding-right: 5.5rem;
}
.source-priority-next {
border-left-color: #287051;
color: #286646 !important;
}
.source-priority-planned {
border-left-color: #a66a18;
color: #8a5a16 !important;
}
@media (max-width: 980px) {
.source-domain-grid,
.source-catalog-grid,
.source-catalog-loaded,
.source-opportunity-list > div {
.source-opportunity-domain > div {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 640px) {
.source-domain-grid,
.source-catalog-grid,
.source-catalog-loaded,
.source-opportunity-list > div {
.source-opportunity-domain > div {
grid-template-columns: 1fr;
}
.source-opportunity-domain > header {
display: grid;
gap: 0.15rem;
}
}
.workspace-grid-assistant {