Add vector change detection foundation
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-16 23:55:52 +02:00
parent 97017c6512
commit 01f063b921
18 changed files with 871 additions and 10 deletions
@@ -0,0 +1,128 @@
import type { ChangeDetectionSummary, DatasetCreateResponse } from '../../types'
interface ChangeDetectionPanelProps {
vectorDatasets: DatasetCreateResponse[]
sourceDatasetId: string
targetDatasetId: string
iouThreshold: number
includeUnchanged: boolean
running: boolean
result: ChangeDetectionSummary | null
error: string | null
onSourceDatasetChange: (value: string) => void
onTargetDatasetChange: (value: string) => void
onIouThresholdChange: (value: number) => void
onIncludeUnchangedChange: (value: boolean) => void
onRun: () => void
}
function datasetLabel(dataset: DatasetCreateResponse): string {
const role = dataset.dataset_role ? ` (${dataset.dataset_role})` : ''
return `${dataset.name}${role}`
}
export function ChangeDetectionPanel({
vectorDatasets,
sourceDatasetId,
targetDatasetId,
iouThreshold,
includeUnchanged,
running,
result,
error,
onSourceDatasetChange,
onTargetDatasetChange,
onIouThresholdChange,
onIncludeUnchangedChange,
onRun,
}: ChangeDetectionPanelProps): JSX.Element {
return (
<section className="panel">
<div className="panel-header">
<div>
<p className="eyebrow">Analysis</p>
<h2>Change Detection</h2>
</div>
<button disabled={running || vectorDatasets.length < 2} onClick={onRun} type="button">
{running ? 'Comparing...' : 'Compare vectors'}
</button>
</div>
<div className="form-grid">
<label>
Source vector
<select value={sourceDatasetId} onChange={(event) => onSourceDatasetChange(event.target.value)}>
<option value="">Select source</option>
{vectorDatasets.map((dataset) => (
<option key={dataset.id} value={dataset.id}>
{datasetLabel(dataset)}
</option>
))}
</select>
</label>
<label>
Target vector
<select value={targetDatasetId} onChange={(event) => onTargetDatasetChange(event.target.value)}>
<option value="">Select target</option>
{vectorDatasets.map((dataset) => (
<option key={dataset.id} value={dataset.id}>
{datasetLabel(dataset)}
</option>
))}
</select>
</label>
<label>
IoU threshold
<input
max="1"
min="0"
step="0.05"
type="number"
value={iouThreshold}
onChange={(event) => onIouThresholdChange(Number(event.target.value))}
/>
</label>
<label className="checkbox-row">
<input
checked={includeUnchanged}
type="checkbox"
onChange={(event) => onIncludeUnchangedChange(event.target.checked)}
/>
Include unchanged geometry
</label>
</div>
{error ? <p className="error">{error}</p> : null}
{!error && vectorDatasets.length < 2 ? <p className="muted">Upload at least two vector datasets to compare.</p> : null}
{result ? (
<div className="summary-grid">
<div>
<span className="metric">{result.added_count}</span>
<span>Added</span>
</div>
<div>
<span className="metric">{result.removed_count}</span>
<span>Removed</span>
</div>
<div>
<span className="metric">{result.unchanged_count}</span>
<span>Unchanged</span>
</div>
<div>
<span className="metric">{result.geojson.features.length}</span>
<span>Map features</span>
</div>
</div>
) : null}
{result?.warnings.length ? (
<ul className="compact-list">
{result.warnings.map((warning) => (
<li key={warning}>{warning}</li>
))}
</ul>
) : null}
</section>
)
}