GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
85 lines
3.2 KiB
Python
85 lines
3.2 KiB
Python
"""A selection smaller than a raster cell must not silently read as zero.
|
|
|
|
``geometry_mask`` selects a cell when its *centre* falls inside the geometry.
|
|
A rectangle smaller than one cell, or one that lands between four centres,
|
|
therefore selects nothing at all — and the analysis returned zeros, which on
|
|
screen is indistinguishable from "we looked and there is nothing here". On a
|
|
100 m population raster a 40 m rectangle over a city block reported no
|
|
inhabitants.
|
|
|
|
Selection now falls back to every touched cell and says that it did, so the
|
|
value is readable as "at least one whole cell", not as an empty area.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
np = pytest.importorskip("numpy")
|
|
rasterio = pytest.importorskip("rasterio")
|
|
|
|
from rasterio.transform import from_origin # noqa: E402 - optional rasterio gate precedes imports
|
|
from shapely.geometry import box # noqa: E402 - optional rasterio gate precedes imports
|
|
|
|
from app.services.raster_cell_selection import select_cells # noqa: E402 - optional rasterio gate precedes service import
|
|
|
|
|
|
# 100 m cells, origin at the top-left corner of a 3x3 grid.
|
|
TRANSFORM = from_origin(200_000, 210_000, 100.0, 100.0)
|
|
SHAPE = (3, 3)
|
|
|
|
|
|
def test_a_normal_selection_uses_cell_centres() -> None:
|
|
selection = select_cells(box(200_000, 209_700, 200_300, 210_000), out_shape=SHAPE, transform=TRANSFORM)
|
|
|
|
assert selection.mask.sum() == 9
|
|
assert selection.mode == "cell_centre"
|
|
assert selection.expanded_to_touched_cells is False
|
|
assert selection.warning is None
|
|
|
|
|
|
def test_a_rectangle_smaller_than_one_cell_still_returns_that_cell() -> None:
|
|
selection = select_cells(box(200_010, 209_960, 200_050, 209_990), out_shape=SHAPE, transform=TRANSFORM)
|
|
|
|
assert selection.mask.sum() == 1
|
|
assert selection.mode == "all_touched"
|
|
assert selection.expanded_to_touched_cells is True
|
|
assert "cel" in selection.warning
|
|
|
|
|
|
def test_a_rectangle_between_four_cell_centres_returns_all_four() -> None:
|
|
selection = select_cells(box(200_080, 209_880, 200_120, 209_920), out_shape=SHAPE, transform=TRANSFORM)
|
|
|
|
assert selection.mask.sum() == 4
|
|
assert selection.expanded_to_touched_cells is True
|
|
|
|
|
|
def test_a_selection_entirely_off_the_raster_selects_nothing() -> None:
|
|
"""Falling back must not invent coverage where the geometry does not reach."""
|
|
|
|
selection = select_cells(box(300_000, 300_000, 300_100, 300_100), out_shape=SHAPE, transform=TRANSFORM)
|
|
|
|
assert selection.mask.sum() == 0
|
|
assert selection.expanded_to_touched_cells is False
|
|
assert selection.mode == "cell_centre"
|
|
|
|
|
|
def test_the_warning_states_how_much_larger_the_analysed_area_is() -> None:
|
|
selection = select_cells(
|
|
box(200_010, 209_960, 200_050, 209_990),
|
|
out_shape=SHAPE,
|
|
transform=TRANSFORM,
|
|
cell_area_m2=100.0 * 100.0,
|
|
)
|
|
|
|
# One 100x100 m cell was analysed for a 40x30 m request.
|
|
assert "1 rastercel" in selection.warning
|
|
assert "1.0 ha" in selection.warning
|
|
|
|
|
|
def test_the_mask_shape_always_matches_the_raster_window() -> None:
|
|
selection = select_cells(box(200_010, 209_960, 200_050, 209_990), out_shape=SHAPE, transform=TRANSFORM)
|
|
|
|
assert selection.mask.shape == SHAPE
|
|
assert selection.mask.dtype == np.bool_
|