Close full operational audit findings
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-17 14:19:02 +02:00
parent cc36dabcbe
commit 78a5f0b0b2
81 changed files with 2248 additions and 1467 deletions
@@ -73,7 +73,7 @@ export interface DatasetDetailPanelProps {
function formatBytes(value: number | null | undefined): string {
if (!value && value !== 0) {
return 'n/a'
return 'n.v.t.'
}
const units = ['B', 'KB', 'MB', 'GB']
let size = value
@@ -87,11 +87,11 @@ function formatBytes(value: number | null | undefined): string {
function formatBounds(bounds: Record<string, number> | null | undefined): string {
if (!bounds) {
return 'n/a'
return 'n.v.t.'
}
const keys = ['min_x', 'min_y', 'max_x', 'max_y']
if (!keys.every((key) => key in bounds)) {
return 'n/a'
return 'n.v.t.'
}
return `${bounds.min_x?.toFixed(4)}, ${bounds.min_y?.toFixed(4)} -> ${bounds.max_x?.toFixed(4)}, ${bounds.max_y?.toFixed(4)}`
}
@@ -157,8 +157,8 @@ export function DatasetDetailPanel({
}: DatasetDetailPanelProps) {
return (
<section className="dataset-detail-panel">
<h2>Dataset details</h2>
{selectedDatasetId ? <p>Selected dataset: {selectedDatasetId}</p> : <p>No dataset selected</p>}
<h2>Details van de databron</h2>
{!selectedDatasetId ? <p>Geen databron geselecteerd.</p> : null}
{selectedDataset ? (
<div>
<p>
@@ -166,13 +166,19 @@ export function DatasetDetailPanel({
</p>
<p>Type: {selectedDataset.dataset_type}</p>
<p>Status: {selectedDataset.status}</p>
<p>Original file: {selectedDataset.original_filename ?? 'n/a'}</p>
<p>Stored file: {selectedDataset.stored_filename ?? 'n/a'}</p>
<p>Content type: {selectedDataset.content_type ?? 'n/a'}</p>
<p>File size: {formatBytes(selectedDataset.size_bytes)}</p>
<p>SHA256: {selectedDataset.checksum_sha256 ?? 'n/a'}</p>
<p>Feature count: {selectedDatasetSummary?.feature_count ?? selectedDataset.feature_count ?? 'n/a'}</p>
<p>BBox: {formatBounds(selectedDatasetSummary?.bounds_json ?? selectedDataset.bounds_json)}</p>
<p>Aantal objecten: {selectedDatasetSummary?.feature_count ?? selectedDataset.feature_count ?? 'n.v.t.'}</p>
<p>Ruimtelijke begrenzing: {formatBounds(selectedDatasetSummary?.bounds_json ?? selectedDataset.bounds_json)}</p>
<details className="technical-inline-details">
<summary>Technische bestandsgegevens</summary>
<div className="entity-meta">
<span>Dataset-ID: {selectedDataset.id}</span>
<span>Oorspronkelijk bestand: {selectedDataset.original_filename ?? 'n.v.t.'}</span>
<span>Bewaard bestand: {selectedDataset.stored_filename ?? 'n.v.t.'}</span>
<span>Inhoudstype: {selectedDataset.content_type ?? 'n.v.t.'}</span>
<span>Bestandsgrootte: {formatBytes(selectedDataset.size_bytes)}</span>
<span>SHA-256: {selectedDataset.checksum_sha256 ?? 'n.v.t.'}</span>
</div>
</details>
{selectedDataset.dataset_type === 'raster' ? (
<RasterControls
areas={areas}
@@ -236,8 +242,9 @@ export function DatasetDetailPanel({
/>
) : null}
<h3>Jobs</h3>
{jobs.length === 0 ? <p>No jobs yet.</p> : null}
<details className="technical-inline-details">
<summary>Technische verwerking ({jobs.length})</summary>
{jobs.length === 0 ? <p>Nog geen verwerkingen.</p> : null}
<ul>
{jobs.map((job) => (
<li key={job.id}>
@@ -247,19 +254,20 @@ export function DatasetDetailPanel({
{job.result_json ? (
<pre className="job-result">{JSON.stringify(job.result_json, null, 2)}</pre>
) : null}
{job.error_message ? <div className="error">error: {job.error_message}</div> : null}
{job.error_message ? <div className="error">Fout: {job.error_message}</div> : null}
{job.result_json?.output_dataset_id ? (
<button type="button" onClick={() => onPickDerivedDataset(String(job.result_json?.output_dataset_id))}>
open derived dataset
Open afgeleide databron
</button>
) : null}
</li>
))}
</ul>
</details>
</div>
) : null}
{loadingDatasetDetails ? <p>Loading dataset details...</p> : null}
{datasetDetailError ? <p className="error">Dataset detail error: {datasetDetailError}</p> : null}
{loadingDatasetDetails ? <p>Details van de databron laden...</p> : null}
{datasetDetailError ? <p className="error">De details konden niet worden geladen: {datasetDetailError}</p> : null}
</section>
)
}
@@ -32,7 +32,7 @@ interface DatasetPanelProps {
function formatBytes(value: number | null | undefined): string {
if (!value && value !== 0) {
return 'n/a'
return 'n.v.t.'
}
const units = ['B', 'KB', 'MB', 'GB']
let size = value
@@ -46,11 +46,11 @@ function formatBytes(value: number | null | undefined): string {
function formatBounds(bounds: Record<string, number> | null | undefined): string {
if (!bounds) {
return 'n/a'
return 'n.v.t.'
}
const keys = ['min_x', 'min_y', 'max_x', 'max_y']
if (!keys.every((key) => key in bounds)) {
return 'n/a'
return 'n.v.t.'
}
return `${bounds.min_x?.toFixed(4)}, ${bounds.min_y?.toFixed(4)} -> ${bounds.max_x?.toFixed(4)}, ${bounds.max_y?.toFixed(4)}`
}
@@ -49,7 +49,7 @@ interface RasterControlsProps {
function formatRasterBounds(bounds: number[] | undefined | null): string {
if (!bounds || bounds.length < 4) {
return 'n/a'
return 'n.v.t.'
}
const [minX, minY, maxX, maxY] = bounds
return `${minX.toFixed(4)}, ${minY.toFixed(4)} -> ${maxX.toFixed(4)}, ${maxY.toFixed(4)}`
@@ -101,57 +101,57 @@ export function RasterControls({
onRunRasterNdwi,
onRunRasterNdbi,
}: RasterControlsProps) {
const previewState = rasterPreview ? 'ready' : 'not generated'
const previewState = rasterPreview ? 'gereed' : 'nog niet aangemaakt'
const rasterReadinessItems = [
{
label: 'Metadata profile',
state: selectedRasterMetadata ? 'ready' : 'inspect needed',
label: 'Bestandsprofiel',
state: selectedRasterMetadata ? 'gereed' : 'controle nodig',
detail: selectedRasterMetadata
? `${selectedRasterMetadata.driver} | ${selectedRasterMetadata.width} x ${selectedRasterMetadata.height} | ${selectedRasterMetadata.band_count} bands`
: 'Run metadata inspection before derived raster operations.',
? `${selectedRasterMetadata.driver} | ${selectedRasterMetadata.width} x ${selectedRasterMetadata.height} | ${selectedRasterMetadata.band_count} banden`
: 'Controleer eerst de metadata voordat je afgeleide rasters maakt.',
},
{
label: 'CRS readiness',
state: selectedRasterMetadata?.crs ? 'ready' : 'missing',
detail: selectedRasterMetadata?.crs ?? 'Raster CRS is required for safe map handoff and clipping.',
label: 'Coördinatenstelsel',
state: selectedRasterMetadata?.crs ? 'gereed' : 'ontbreekt',
detail: selectedRasterMetadata?.crs ?? 'Een coördinatenstelsel is vereist om veilig te begrenzen en op de kaart te tonen.',
},
{
label: 'Preview artifact',
label: 'Voorbeeldweergave',
state: previewState,
detail: rasterPreview
? `${rasterPreview.preview.path} (${rasterPreview.preview.width ?? 'n/a'} x ${rasterPreview.preview.height ?? 'n/a'})`
: 'Generate a preview to confirm visual orientation before AI runs.',
? `${rasterPreview.preview.width ?? 'n.v.t.'} x ${rasterPreview.preview.height ?? 'n.v.t.'} pixels`
: 'Maak een voorbeeld om de oriëntatie te controleren vóór beeldanalyse.',
},
{
label: 'Tile manifest handoff',
state: isRasterTileInputValid ? 'input valid' : 'input blocked',
label: 'Beeldtegels',
state: isRasterTileInputValid ? 'instellingen geldig' : 'instellingen geblokkeerd',
detail: isRasterTileInputValid
? 'Tile generation will create the manifest path used by detection and segmentation requests.'
: 'Tile size must be positive and overlap must stay below tile size.',
? 'GeoIntel kan een manifest maken voor detectie en segmentatie.'
: 'De tegelgrootte moet positief zijn en de overlap moet kleiner blijven.',
},
{
label: 'Clip AOI',
state: areas.length > 0 ? 'available' : 'missing',
detail: areas.length > 0 ? `${areas.length} project area${areas.length === 1 ? '' : 's'} available.` : 'Create an area before clipping.',
label: 'Werkgebied voor begrenzing',
state: areas.length > 0 ? 'beschikbaar' : 'ontbreekt',
detail: areas.length > 0 ? `${areas.length} ${areas.length === 1 ? 'gebied' : 'gebieden'} beschikbaar.` : 'Maak eerst een gebied aan.',
},
]
const rasterGuardrailItems = [
!selectedDatasetId ? 'Select a raster dataset before running raster operations.' : null,
rasterUnavailableMessage ? `Raster unavailable: ${rasterUnavailableMessage}` : null,
selectedDatasetId && !selectedRasterMetadata ? 'Inspect metadata before reprojecting, clipping, tiling or computing indices.' : null,
selectedRasterMetadata && !selectedRasterMetadata?.crs ? 'CRS is missing; geospatial handoff should be fixed before downstream QA.' : null,
!rasterPreview ? 'Preview is not generated yet; create it before visual review.' : null,
!isRasterTileInputValid ? 'Tile settings are invalid; update tile size and overlap before generating a manifest.' : null,
areas.length === 0 ? 'No project area exists yet, so raster clipping is disabled.' : null,
!selectedDatasetId ? 'Kies eerst een rasterbestand.' : null,
rasterUnavailableMessage ? `Raster niet beschikbaar: ${rasterUnavailableMessage}` : null,
selectedDatasetId && !selectedRasterMetadata ? 'Controleer de metadata vóór herprojectie, begrenzing, tegels of indexberekening.' : null,
selectedRasterMetadata && !selectedRasterMetadata?.crs ? 'Het coördinatenstelsel ontbreekt; herstel dit vóór verdere ruimtelijke analyse.' : null,
!rasterPreview ? 'Er is nog geen voorbeeldweergave gemaakt.' : null,
!isRasterTileInputValid ? 'De tegelinstellingen zijn ongeldig.' : null,
areas.length === 0 ? 'Er bestaat nog geen werkgebied; begrenzen is daarom uitgeschakeld.' : null,
].filter((item): item is string => Boolean(item))
return (
<div className="dataset-tool-panel raster-tool-panel">
<section className="raster-readiness-surface" aria-label="Raster pipeline readiness">
<section className="raster-readiness-surface" aria-label="Gereedheid van rasterverwerking">
<div className="panel-title-row">
<div>
<h3>Raster pipeline readiness</h3>
<p>Operational state for metadata, preview, clipping and tile-manifest handoff.</p>
<h3>Gereedheid van het raster</h3>
<p>Status van metadata, voorbeeldweergave, begrenzing en beeldtegels.</p>
</div>
</div>
<div className="raster-readiness-grid">
@@ -164,54 +164,54 @@ export function RasterControls({
))}
</div>
<div className="raster-manifest-handoff">
<span>Latest tile manifest</span>
<span>Laatste beeldtegelmanifest</span>
<p>
{latestRasterTileManifestPath ||
'No tile manifest generated yet. Generate tiles before handing this raster to Detection Lab.'}
'Nog geen beeldtegels gemaakt. Maak beeldtegels voordat je dit raster gebruikt voor beeldanalyse.'}
</p>
{latestRasterTileManifest ? (
<dl className="raster-manifest-details">
<div>
<dt>Tile count</dt>
<dd>{latestRasterTileManifest.count ?? 'n/a'}</dd>
<dt>Aantal tegels</dt>
<dd>{latestRasterTileManifest.count ?? 'n.v.t.'}</dd>
</div>
<div>
<dt>Tile size</dt>
<dd>{latestRasterTileManifest.tile_size ?? 'n/a'}</dd>
<dt>Tegelgrootte</dt>
<dd>{latestRasterTileManifest.tile_size ?? 'n.v.t.'}</dd>
</div>
<div>
<dt>Overlap</dt>
<dd>{latestRasterTileManifest.overlap ?? 'n/a'}</dd>
<dd>{latestRasterTileManifest.overlap ?? 'n.v.t.'}</dd>
</div>
<div>
<dt>Tile set</dt>
<dd>{latestRasterTileManifest.tile_set_id ?? 'n/a'}</dd>
<dt>Tegelset</dt>
<dd>{latestRasterTileManifest.tile_set_id ?? 'n.v.t.'}</dd>
</div>
</dl>
) : null}
<button
type="button"
aria-label="Use in Detection Lab with current manifest"
aria-label="Gebruik huidig manifest voor gebouwdetectie"
onClick={onUseTileManifestForDetection}
disabled={!latestRasterTileManifestPath}
>
Use manifest in Detection Lab
Gebruik voor gebouwdetectie
</button>
<button
type="button"
aria-label="Use in Segmentation Lab with current manifest"
aria-label="Gebruik huidig manifest voor segmentatie"
onClick={onUseTileManifestForSegmentation}
disabled={!latestRasterTileManifestPath}
>
Use manifest in Segmentation Lab
Gebruik voor segmentatie
</button>
</div>
</section>
<section className="raster-guardrail-surface" aria-label="Processing guardrails">
<section className="raster-guardrail-surface" aria-label="Veiligheidscontroles">
<div className="panel-title-row">
<div>
<h3>Processing guardrails</h3>
<p>Checks that protect downstream GIS and AI operations from ambiguous raster state.</p>
<h3>Veiligheidscontroles</h3>
<p>Controles die onduidelijke of ruimtelijk onveilige verwerking voorkomen.</p>
</div>
</div>
{rasterGuardrailItems.length > 0 ? (
@@ -221,83 +221,83 @@ export function RasterControls({
))}
</ul>
) : (
<p className="raster-readiness-state">Raster controls are ready for safe operation.</p>
<p className="raster-readiness-state">Het raster is gereed voor veilige verwerking.</p>
)}
</section>
<div className="dataset-tool-group">
<h4 className="dataset-tool-heading">Raster metadata</h4>
<p>Raster driver: {selectedRasterMetadata?.driver ?? 'n/a'}</p>
<p>Raster size: {selectedRasterMetadata ? `${selectedRasterMetadata.width} x ${selectedRasterMetadata.height}` : 'n/a'}</p>
<p>Raster checksum: {selectedRasterMetadata?.checksum_sha256 ?? 'n/a'}</p>
<h4 className="dataset-tool-heading">Rastermetadata</h4>
<p>Bestandsdriver: {selectedRasterMetadata?.driver ?? 'n.v.t.'}</p>
<p>Afmetingen: {selectedRasterMetadata ? `${selectedRasterMetadata.width} x ${selectedRasterMetadata.height}` : 'n.v.t.'}</p>
<p>Controlesom: {selectedRasterMetadata?.checksum_sha256 ?? 'n.v.t.'}</p>
<p>
Profile: CRS {selectedRasterMetadata?.crs ?? 'n/a'} | bands {selectedRasterMetadata?.band_count ?? 'n/a'} | dtype {
(selectedRasterMetadata?.dtype as string[] | undefined)?.join(', ') ?? 'n/a'}
Profiel: CRS {selectedRasterMetadata?.crs ?? 'n.v.t.'} | banden {selectedRasterMetadata?.band_count ?? 'n.v.t.'} | datatype {
(selectedRasterMetadata?.dtype as string[] | undefined)?.join(', ') ?? 'n.v.t.'}
</p>
<p>Bounds: {formatRasterBounds(selectedRasterMetadata?.bounds)}</p>
<p>Resolution: {selectedRasterMetadata?.resolution ? selectedRasterMetadata.resolution.join(', ') : 'n/a'}</p>
{rasterUnavailableMessage ? <p className="error">Raster unavailable: {rasterUnavailableMessage}</p> : null}
<p>Begrenzing: {formatRasterBounds(selectedRasterMetadata?.bounds)}</p>
<p>Resolutie: {selectedRasterMetadata?.resolution ? selectedRasterMetadata.resolution.join(', ') : 'n.v.t.'}</p>
{rasterUnavailableMessage ? <p className="error">Raster niet beschikbaar: {rasterUnavailableMessage}</p> : null}
</div>
<h3>Raster operations</h3>
<p>Available operations: inspect, stats, reproject, preview, clip by selected area, tile generation.</p>
<p>Preview: {rasterPreview?.preview.path ?? 'not generated'}</p>
<p>Preview size: {rasterPreview?.preview.width ?? 'n/a'} x {rasterPreview?.preview.height ?? 'n/a'}</p>
<h3>Rasterbewerkingen</h3>
<p>Controleer metadata en statistieken, herprojecteer, begrens of maak beeldtegels.</p>
<p>Voorbeeld: {rasterPreview ? 'aangemaakt' : 'nog niet aangemaakt'}</p>
<p>Afmetingen voorbeeld: {rasterPreview?.preview.width ?? 'n.v.t.'} x {rasterPreview?.preview.height ?? 'n.v.t.'}</p>
<button type="button" onClick={onRunRasterInspect}>
Inspect raster metadata
Metadata controleren
</button>
<button type="button" onClick={onRunRasterPreview} disabled={!selectedDatasetId}>
Generate preview
Voorbeeld maken
</button>
<button type="button" onClick={onRunRasterStats}>
Compute band statistics
Bandstatistieken berekenen
</button>
{selectedRasterStats ? (
<div>
<h4>Band statistics</h4>
<p>Generated: {selectedRasterStats.generated_at ?? 'n/a'}</p>
<h4>Bandstatistieken</h4>
<p>Aangemaakt: {selectedRasterStats.generated_at ?? 'n.v.t.'}</p>
<ul>
{selectedRasterStats.bands.map((band) => (
<li key={band.band_index}>
Band {band.band_index}: min {band.min ?? 'n/a'}, max {band.max ?? 'n/a'}, mean {band.mean ?? 'n/a'}, std {band.std ?? 'n/a'},
valid {band.valid_pixel_count}, nodata ratio {(band.nodata_ratio * 100).toFixed(2)}%, dtype {band.dtype ?? 'n/a'}
Band {band.band_index}: minimum {band.min ?? 'n.v.t.'}, maximum {band.max ?? 'n.v.t.'}, gemiddelde {band.mean ?? 'n.v.t.'}, standaardafwijking {band.std ?? 'n.v.t.'},
geldig {band.valid_pixel_count}, aandeel zonder data {(band.nodata_ratio * 100).toFixed(2)}%, datatype {band.dtype ?? 'n.v.t.'}
</li>
))}
</ul>
</div>
) : null}
<div className="dataset-tool-group">
<h4 className="dataset-tool-heading">Reproject raster</h4>
<p className="dataset-tool-helper">Create a derived raster in a target CRS with the selected resampling method.</p>
<h4 className="dataset-tool-heading">Raster herprojecteren</h4>
<p className="dataset-tool-helper">Maak een afgeleid raster in een ander coördinatenstelsel.</p>
<div className="dataset-tool-grid">
<label className="dataset-tool-field">
<span className="dataset-tool-label">Reproject CRS</span>
<span className="dataset-tool-label">Doel-CRS</span>
<input
value={rasterReprojectCrs}
onChange={(event) => onSetRasterReprojectCrs(event.target.value)}
placeholder="EPSG:31370"
/>
<span className="dataset-tool-helper">Target CRS for the derived raster artifact.</span>
<span className="dataset-tool-helper">Coördinatenstelsel van het afgeleide raster.</span>
</label>
<label className="dataset-tool-field">
<span className="dataset-tool-label">Resampling</span>
<span className="dataset-tool-label">Herbemonstering</span>
<select value={rasterReprojectResampling} onChange={(event) => onSetRasterReprojectResampling(event.target.value)}>
<option value="nearest">nearest</option>
<option value="bilinear">bilinear</option>
<option value="cubic">cubic</option>
</select>
<span className="dataset-tool-helper">Nearest preserves classes; bilinear/cubic smooth continuous rasters.</span>
<span className="dataset-tool-helper">Dichtstbijzijnd behoudt klassen; bilineair en kubisch verzachten continue rasters.</span>
</label>
</div>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunRasterReproject}>
Reproject raster
Raster herprojecteren
</button>
</div>
</div>
<div className="dataset-tool-group">
<h4 className="dataset-tool-heading">Clip raster</h4>
<p className="dataset-tool-helper">Clip the selected raster to an existing project area.</p>
<h4 className="dataset-tool-heading">Raster begrenzen</h4>
<p className="dataset-tool-helper">Beperk het raster tot een bestaand werkgebied.</p>
<label className="dataset-tool-field">
<span className="dataset-tool-label">Clip area</span>
<span className="dataset-tool-label">Werkgebied</span>
<select value={selectedClipAreaId} onChange={(event) => onSetSelectedClipAreaId(event.target.value)}>
{areas.map((area) => (
<option key={area.id} value={area.id}>
@@ -308,24 +308,24 @@ export function RasterControls({
</label>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunRasterClip} disabled={areas.length === 0}>
Clip raster by area
Raster begrenzen
</button>
</div>
{areas.length === 0 ? <p className="dataset-tool-error">Create an area before raster clipping.</p> : null}
{areas.length === 0 ? <p className="dataset-tool-error">Maak eerst een werkgebied aan.</p> : null}
</div>
<div className="dataset-tool-group">
<h4 className="dataset-tool-heading">Generate tiles</h4>
<p className="dataset-tool-helper">Create a tile manifest for downstream detection or segmentation runs.</p>
<h4 className="dataset-tool-heading">Beeldtegels maken</h4>
<p className="dataset-tool-helper">Maak beeldtegels voor detectie of segmentatie.</p>
<div className="dataset-tool-grid">
<label className="dataset-tool-field">
<span className="dataset-tool-label">Tile size</span>
<span className="dataset-tool-label">Tegelgrootte</span>
<input
type="number"
min={1}
value={rasterTileSize}
onChange={(event) => onSetRasterTileSize(Number(event.target.value))}
/>
<span className="dataset-tool-helper">{'Tile size must be > 0.'}</span>
<span className="dataset-tool-helper">De tegelgrootte moet groter zijn dan nul.</span>
</label>
<label className="dataset-tool-field">
<span className="dataset-tool-label">Overlap</span>
@@ -335,86 +335,86 @@ export function RasterControls({
value={rasterTileOverlap}
onChange={(event) => onSetRasterTileOverlap(Number(event.target.value))}
/>
<span className="dataset-tool-helper">Overlap must be smaller than tile size.</span>
<span className="dataset-tool-helper">De overlap moet kleiner zijn dan de tegelgrootte.</span>
</label>
<label className="dataset-tool-field">
<span className="dataset-tool-label">Tile basename</span>
<span className="dataset-tool-label">Bestandsnaamvoorvoegsel</span>
<input
value={rasterTileOutputName}
onChange={(event) => onSetRasterTileOutputName(event.target.value)}
placeholder="optional"
placeholder="optioneel"
/>
<span className="dataset-tool-helper">Optional artifact name prefix for generated tiles.</span>
<span className="dataset-tool-helper">Optioneel voorvoegsel voor de aangemaakte tegels.</span>
</label>
</div>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunRasterTile} disabled={!isRasterTileInputValid}>
Generate tiles
Beeldtegels maken
</button>
</div>
{!isRasterTileInputValid ? (
<p className="dataset-tool-error">
Tile size must be {'>'} 0 and overlap must be {'>='} 0 and smaller than tile size.
De tegelgrootte moet groter zijn dan nul en de overlap moet positief en kleiner zijn dan de tegelgrootte.
</p>
) : null}
</div>
<h4>Spectral indices</h4>
<h4>Spectrale indexen</h4>
<div>
<p>Use available band indexes from the raster file (1-based).</p>
<p>Gebruik de beschikbare bandnummers uit het rasterbestand, beginnend bij 1.</p>
<div className="dataset-tool-group">
<h4 className="dataset-tool-heading">NDVI</h4>
<p className="dataset-tool-helper">Vegetation index from NIR and red bands.</p>
<p className="dataset-tool-helper">Vegetatie-index op basis van nabij-infrarood en rood.</p>
<div className="dataset-tool-grid">
<label className="dataset-tool-field">
<span className="dataset-tool-label">NIR band</span>
<span className="dataset-tool-label">NIR-band</span>
<input type="number" min={1} value={ndviNirBand} onChange={(event) => onSetNdviNirBand(Number(event.target.value))} />
</label>
<label className="dataset-tool-field">
<span className="dataset-tool-label">Red band</span>
<span className="dataset-tool-label">Rode band</span>
<input type="number" min={1} value={ndviRedBand} onChange={(event) => onSetNdviRedBand(Number(event.target.value))} />
</label>
</div>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunRasterNdvi}>
Compute NDVI
NDVI berekenen
</button>
</div>
</div>
<div className="dataset-tool-group">
<h4 className="dataset-tool-heading">NDWI</h4>
<p className="dataset-tool-helper">Water index from NIR and green bands.</p>
<p className="dataset-tool-helper">Waterindex op basis van nabij-infrarood en groen.</p>
<div className="dataset-tool-grid">
<label className="dataset-tool-field">
<span className="dataset-tool-label">NIR band</span>
<span className="dataset-tool-label">NIR-band</span>
<input type="number" min={1} value={ndwiNirBand} onChange={(event) => onSetNdwiNirBand(Number(event.target.value))} />
</label>
<label className="dataset-tool-field">
<span className="dataset-tool-label">Green band</span>
<span className="dataset-tool-label">Groene band</span>
<input type="number" min={1} value={ndwiGreenBand} onChange={(event) => onSetNdwiGreenBand(Number(event.target.value))} />
</label>
</div>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunRasterNdwi}>
Compute NDWI
NDWI berekenen
</button>
</div>
</div>
<div className="dataset-tool-group">
<h4 className="dataset-tool-heading">NDBI</h4>
<p className="dataset-tool-helper">Built-up index from SWIR and NIR bands.</p>
<p className="dataset-tool-helper">Bebouwingsindex op basis van kortgolvig en nabij-infrarood.</p>
<div className="dataset-tool-grid">
<label className="dataset-tool-field">
<span className="dataset-tool-label">SWIR band</span>
<span className="dataset-tool-label">SWIR-band</span>
<input type="number" min={1} value={ndbiSwirBand} onChange={(event) => onSetNdbiSwirBand(Number(event.target.value))} />
</label>
<label className="dataset-tool-field">
<span className="dataset-tool-label">NIR band</span>
<span className="dataset-tool-label">NIR-band</span>
<input type="number" min={1} value={ndbiNirBand} onChange={(event) => onSetNdbiNirBand(Number(event.target.value))} />
</label>
</div>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunRasterNdbi}>
Compute NDBI
NDBI berekenen
</button>
</div>
</div>
@@ -25,13 +25,13 @@ export function VectorControls({
}: VectorControlsProps) {
return (
<div className="dataset-tool-panel vector-tool-panel">
<h3>Vector operations</h3>
<h3>Vectorbewerkingen</h3>
<div className="dataset-tool-group">
<h4 className="dataset-tool-heading">Clip vector</h4>
<p className="dataset-tool-helper">Clip features to the selected project area.</p>
<h4 className="dataset-tool-heading">Begrenzen tot gebied</h4>
<p className="dataset-tool-helper">Beperk objecten tot het gekozen werkgebied.</p>
<div className="dataset-tool-grid">
<label className="dataset-tool-field">
<span className="dataset-tool-label">Clip area</span>
<span className="dataset-tool-label">Werkgebied</span>
<select value={selectedClipAreaId} onChange={(event) => onSetSelectedClipAreaId(event.target.value)}>
{areas.map((area) => (
<option key={area.id} value={area.id}>
@@ -43,39 +43,39 @@ export function VectorControls({
</div>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunVectorClip} disabled={areas.length === 0}>
Run clip
Begrenzen
</button>
</div>
</div>
<div className="dataset-tool-group">
<h4 className="dataset-tool-heading">Buffer vector</h4>
<p className="dataset-tool-helper">Create a 25m buffer using the existing vector operation defaults.</p>
<h4 className="dataset-tool-heading">Invloedszone</h4>
<p className="dataset-tool-helper">Maak een zone van 25 meter rond ieder object.</p>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunVectorBuffer}>
Run buffer (25m)
Zone van 25 m maken
</button>
</div>
</div>
<div className="dataset-tool-group">
<h4 className="dataset-tool-heading">Intersect vector</h4>
<p className="dataset-tool-helper">Intersect with another persisted vector dataset.</p>
<h4 className="dataset-tool-heading">Lagen doorsnijden</h4>
<p className="dataset-tool-helper">Bereken de overlap met een andere bewaarde vectorlaag.</p>
<div className="dataset-tool-grid">
<label className="dataset-tool-field">
<span className="dataset-tool-label">Intersect target</span>
<span className="dataset-tool-label">Tweede kaartlaag</span>
<select value={selectedIntersectTargetId} onChange={(event) => onSetSelectedIntersectTargetId(event.target.value)}>
<option value="">auto first vector</option>
<option value="">Automatisch de eerste geschikte laag</option>
{availableVectorTargets.map((target) => (
<option key={target.id} value={target.id}>
{target.name}
</option>
))}
</select>
<span className="dataset-tool-helper">Leave automatic to use the first available vector target.</span>
<span className="dataset-tool-helper">Laat automatisch staan om de eerste beschikbare vectorlaag te gebruiken.</span>
</label>
</div>
<div className="dataset-tool-action-row">
<button type="button" onClick={onRunVectorIntersect}>
Run intersect
Overlap berekenen
</button>
</div>
</div>