diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index e5d88b15..27bc2235 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -305,6 +305,8 @@ function WorkbenchApp({ username, accessMode, loggingOut, onLogout }: WorkbenchA
selectedProjectId,
availableVectorDatasets,
loadDatasetJobs,
+ // Bind the comparison to whatever the operator has selected on the map.
+ getSelection: () => ({ bbox: mapSelectionBbox ?? null, areaId: selectedMapAreaId ?? null }),
})
const {
qaCandidateDatasetId,
diff --git a/frontend/src/components/analysis/ChangeDetectionPanel.tsx b/frontend/src/components/analysis/ChangeDetectionPanel.tsx
index 276ba9e5..b057c41d 100644
--- a/frontend/src/components/analysis/ChangeDetectionPanel.tsx
+++ b/frontend/src/components/analysis/ChangeDetectionPanel.tsx
@@ -120,6 +120,10 @@ export function ChangeDetectionPanel({
{result.removed_count}
Verdwenen
+
+ {result.modified_count ?? 0}
+ Gewijzigd
+
{result.unchanged_count}
Ongewijzigd
diff --git a/frontend/src/hooks/useChangeDetectionWorkflow.ts b/frontend/src/hooks/useChangeDetectionWorkflow.ts
index 9f41fcf2..7d1749eb 100644
--- a/frontend/src/hooks/useChangeDetectionWorkflow.ts
+++ b/frontend/src/hooks/useChangeDetectionWorkflow.ts
@@ -1,18 +1,29 @@
import { useState } from 'react'
import { analysisApi } from '../services/api'
-import type { ChangeDetectionSummary, DatasetCreateResponse } from '../types'
+import type { ChangeDetectionSummary, DatasetCreateResponse, VectorSelectionBBox } from '../types'
import { formatError } from '../lib/formatError'
interface ChangeDetectionWorkflowOptions {
selectedProjectId: string | null
availableVectorDatasets: DatasetCreateResponse[]
loadDatasetJobs: (projectId: string, datasetId: string) => Promise
+ /**
+ * Reads the active map selection when the comparison is started. Without a
+ * selection the comparison covers both datasets entire, which is rarely the
+ * question being asked and returns a payload no map can draw.
+ *
+ * A getter rather than a value because this hook runs before the selection
+ * state is declared in the component; it is only ever called from a user
+ * event, long after that binding is initialised.
+ */
+ getSelection?: () => { bbox: VectorSelectionBBox | null; areaId: string | null }
}
export function useChangeDetectionWorkflow({
selectedProjectId,
availableVectorDatasets,
loadDatasetJobs,
+ getSelection,
}: ChangeDetectionWorkflowOptions) {
const [changeSourceDatasetId, setChangeSourceDatasetId] = useState('')
const [changeTargetDatasetId, setChangeTargetDatasetId] = useState('')
@@ -41,12 +52,15 @@ export function useChangeDetectionWorkflow({
setChangeDetectionError(null)
setChangeDetectionResult(null)
setRunningChangeDetection(true)
+ const selection = getSelection?.() ?? { bbox: null, areaId: null }
try {
const job = await analysisApi.runChangeDetection({
source_dataset_id: sourceDatasetId,
target_dataset_id: targetDatasetId,
iou_threshold: changeIouThreshold,
include_unchanged: changeIncludeUnchanged,
+ ...(selection.bbox ? { bbox: selection.bbox } : {}),
+ ...(selection.areaId ? { area_id: selection.areaId } : {}),
})
if (job.status !== 'success') {
throw new Error(job.error_message || 'Change detection job failed')
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index 7293d7ca..05385524 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -1055,7 +1055,13 @@ export interface ChangeDetectionRequest {
source_dataset_id: string
target_dataset_id: string
iou_threshold: number
+ /** Below this the footprints are separate objects rather than one redrawn. */
+ modified_threshold?: number
include_unchanged: boolean
+ /** Without a selection the comparison covers both datasets in full. */
+ bbox?: VectorSelectionBBox | null
+ area_id?: string | null
+ preview_limit?: number
}
export interface ChangeDetectionSummary {
@@ -1065,8 +1071,15 @@ export interface ChangeDetectionSummary {
target_feature_count: number
added_count: number
removed_count: number
+ /** A footprint that was redrawn, not demolished and rebuilt. */
+ modified_count?: number
unchanged_count: number
iou_threshold: number
+ modified_iou_threshold?: number | null
+ selection_area_id?: string | null
+ /** Counts cover the whole selection; the GeoJSON is capped. */
+ preview_limit?: number | null
+ preview_truncated?: boolean
warnings: string[]
generated_at: string
geojson: GeoJSON.FeatureCollection