162 lines
5.1 KiB
Python
162 lines
5.1 KiB
Python
"""The object count and the area metric must describe the same selection.
|
|
|
|
``intersection_area`` clips a feature to the drawn rectangle, but the object
|
|
count treated any feature that merely touches the rectangle as wholly inside.
|
|
For a rectangle across a built-up area that overstates the count at every
|
|
edge, and the two headline numbers on the same panel then describe different
|
|
populations: "1.000 gebouwen" next to the clipped area of rather fewer.
|
|
|
|
The count now reports how many features lie entirely inside and how many are
|
|
cut by the selection edge, and is marked as an estimate when any are.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from app.services.vector_feature_service import VectorFeatureService
|
|
|
|
|
|
def test_a_count_without_partial_features_is_exact() -> None:
|
|
disclosure = VectorFeatureService.count_disclosure(
|
|
total_feature_count=120,
|
|
fully_covered_feature_count=120,
|
|
)
|
|
|
|
assert disclosure["partially_covered_feature_count"] == 0
|
|
assert disclosure["is_estimate"] is False
|
|
assert disclosure["warning"] is None
|
|
|
|
|
|
def test_features_cut_by_the_selection_edge_are_reported() -> None:
|
|
disclosure = VectorFeatureService.count_disclosure(
|
|
total_feature_count=120,
|
|
fully_covered_feature_count=98,
|
|
)
|
|
|
|
assert disclosure["partially_covered_feature_count"] == 22
|
|
assert disclosure["is_estimate"] is True
|
|
assert "22" in disclosure["warning"]
|
|
assert "rand" in disclosure["warning"]
|
|
|
|
|
|
def test_a_selection_of_only_partial_features_is_still_coherent() -> None:
|
|
disclosure = VectorFeatureService.count_disclosure(
|
|
total_feature_count=3,
|
|
fully_covered_feature_count=0,
|
|
)
|
|
|
|
assert disclosure["partially_covered_feature_count"] == 3
|
|
assert disclosure["is_estimate"] is True
|
|
|
|
|
|
def test_an_empty_selection_makes_no_claim() -> None:
|
|
disclosure = VectorFeatureService.count_disclosure(
|
|
total_feature_count=0,
|
|
fully_covered_feature_count=0,
|
|
)
|
|
|
|
assert disclosure["partially_covered_feature_count"] == 0
|
|
assert disclosure["is_estimate"] is False
|
|
assert disclosure["warning"] is None
|
|
|
|
|
|
def test_a_preclipped_full_area_selection_has_no_edge_effect() -> None:
|
|
"""Selecting the whole work area cuts nothing; the count is exact."""
|
|
|
|
disclosure = VectorFeatureService.count_disclosure(
|
|
total_feature_count=500,
|
|
fully_covered_feature_count=None,
|
|
)
|
|
|
|
assert disclosure["partially_covered_feature_count"] is None
|
|
assert disclosure["is_estimate"] is False
|
|
assert disclosure["warning"] is None
|
|
|
|
|
|
def test_an_inconsistent_covered_count_never_produces_a_negative() -> None:
|
|
disclosure = VectorFeatureService.count_disclosure(
|
|
total_feature_count=10,
|
|
fully_covered_feature_count=14,
|
|
)
|
|
|
|
assert disclosure["partially_covered_feature_count"] == 0
|
|
assert disclosure["is_estimate"] is False
|
|
|
|
|
|
class _ScalarQuery:
|
|
def __init__(self, value):
|
|
self.value = value
|
|
|
|
def filter(self, *args): # noqa: ANN002, ARG002
|
|
return self
|
|
|
|
def scalar(self):
|
|
return self.value
|
|
|
|
|
|
class _SequenceSession:
|
|
"""Answers the summary's scalar queries in order: covered count, then metrics."""
|
|
|
|
def __init__(self, values):
|
|
self.values = iter(values)
|
|
|
|
def query(self, *args): # noqa: ANN002, ARG002
|
|
return _ScalarQuery(next(self.values))
|
|
|
|
|
|
def _buildings_dataset():
|
|
from uuid import uuid4
|
|
|
|
from app.models import Dataset
|
|
|
|
return Dataset(
|
|
id=uuid4(),
|
|
project_id=uuid4(),
|
|
name="grb-buildings.geojson",
|
|
dataset_type="vector",
|
|
dataset_role="reference",
|
|
source_name="grb",
|
|
reference_layer_name="buildings",
|
|
source_metadata={"theme": "buildings"},
|
|
)
|
|
|
|
|
|
BBOX = {"min_x": 5.0, "min_y": 51.1, "max_x": 5.2, "max_y": 51.3, "crs": "EPSG:4326"}
|
|
|
|
|
|
def test_summary_reports_the_edge_cut_next_to_the_object_count() -> None:
|
|
summary = VectorFeatureService.summarize_features_by_bbox(
|
|
_SequenceSession([88, 125_000.0]),
|
|
dataset=_buildings_dataset(),
|
|
bbox=BBOX,
|
|
total_feature_count=100,
|
|
)
|
|
|
|
assert summary["feature_count"] == 100
|
|
assert summary["fully_covered_feature_count"] == 88
|
|
assert summary["partially_covered_feature_count"] == 12
|
|
assert "12 van de 100" in summary["selection_edge_warning"]
|
|
|
|
count_metric = next(
|
|
item for item in summary["metrics"] if item["aggregation_method"] == "feature_count"
|
|
)
|
|
assert count_metric["is_estimate"] is True
|
|
assert "doorgesneden" in count_metric["warning"]
|
|
|
|
# The clipped area metric is exact and must not inherit the count's caveat.
|
|
area_metric = next(
|
|
item for item in summary["metrics"] if item["aggregation_method"] == "intersection_area"
|
|
)
|
|
assert area_metric["is_estimate"] is False
|
|
|
|
|
|
def test_summary_stays_exact_when_the_selection_cuts_nothing() -> None:
|
|
summary = VectorFeatureService.summarize_features_by_bbox(
|
|
_SequenceSession([100, 125_000.0]),
|
|
dataset=_buildings_dataset(),
|
|
bbox=BBOX,
|
|
total_feature_count=100,
|
|
)
|
|
|
|
assert summary["partially_covered_feature_count"] == 0
|
|
assert summary["selection_edge_warning"] is None
|