Change detection was the one analysis that ignored the selection entirely. It compared two datasets in full, loaded every feature of both into Python with no spatial predicate, and — with include_unchanged defaulting to true — returned a FeatureCollection holding both datasets. For a regional building layer that is the wrong answer to "what changed here" and a response no browser should be asked to hold. It now accepts bbox and area_id, resolved the way every other analysis resolves them, and loads through an indexed ST_Intersects predicate. Features are deliberately not clipped to the selection. A change class describes a whole object: comparing a clipped earlier footprint against an unclipped later one would report the selection edge itself as a change. Objects the edge crosses are compared in full and counted in a warning. The returned geometry is capped by preview_limit, spending that budget on modified, added and removed before unchanged, while every count still describes the whole selection. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
47 lines
1.7 KiB
Python
47 lines
1.7 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app.schemas.operations import VectorSelectionBBox
|
|
|
|
|
|
class ChangeDetectionRequest(BaseModel):
|
|
source_dataset_id: UUID
|
|
target_dataset_id: UUID
|
|
iou_threshold: float = Field(default=0.8, ge=0.0, le=1.0)
|
|
# Below this the two footprints are separate objects rather than one that
|
|
# was redrawn; between the two thresholds the change class is "modified".
|
|
modified_threshold: float = Field(default=0.3, ge=0.0, le=1.0)
|
|
include_unchanged: bool = True
|
|
# Without a selection the comparison covers both datasets in full, which is
|
|
# rarely the question and never a response a map can draw.
|
|
bbox: VectorSelectionBBox | None = None
|
|
area_id: UUID | None = None
|
|
preview_limit: int = Field(default=2_000, ge=1, le=20_000)
|
|
|
|
|
|
class ChangeDetectionSummary(BaseModel):
|
|
source_dataset_id: UUID
|
|
target_dataset_id: UUID
|
|
source_feature_count: int
|
|
target_feature_count: int
|
|
added_count: int
|
|
removed_count: int
|
|
# A footprint that was redrawn rather than demolished and rebuilt. Without
|
|
# this class it appeared as one removal plus one addition.
|
|
modified_count: int = 0
|
|
unchanged_count: int
|
|
iou_threshold: float
|
|
modified_iou_threshold: float | None = None
|
|
selection_area_id: UUID | None = None
|
|
# Counts describe the whole selection; the GeoJSON is capped so a regional
|
|
# comparison does not return both datasets in one response.
|
|
preview_limit: int | None = None
|
|
preview_truncated: bool = False
|
|
warnings: list[str] = Field(default_factory=list)
|
|
generated_at: datetime
|
|
geojson: dict
|