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
+28 -2
View File
@@ -89,13 +89,39 @@ function GeoMap({ data }: GeoMapProps): JSX.Element {
id: 'dataset-fill',
type: 'fill',
source: 'dataset',
paint: { 'fill-color': '#f97316', 'fill-opacity': 0.4 },
paint: {
'fill-color': [
'match',
['get', 'change_type'],
'added',
'#16a34a',
'removed',
'#dc2626',
'unchanged',
'#2563eb',
'#f97316',
],
'fill-opacity': 0.4,
},
})
map.addLayer({
id: 'dataset-line',
type: 'line',
source: 'dataset',
paint: { 'line-color': '#ea580c', 'line-width': 2 },
paint: {
'line-color': [
'match',
['get', 'change_type'],
'added',
'#15803d',
'removed',
'#b91c1c',
'unchanged',
'#1d4ed8',
'#ea580c',
],
'line-width': 2,
},
})
}
@@ -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>
)
}