Initial public release
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
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
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
"""Choosing which raster cells a drawn selection covers.
|
||||
|
||||
``rasterio.features.geometry_mask`` selects a cell when the cell *centre* falls
|
||||
inside the geometry. That is the right rule for a selection spanning many
|
||||
cells, and the wrong one for a small selection: a rectangle smaller than a cell,
|
||||
or one landing between four centres, selects nothing. The analysis then reports
|
||||
zeros, which on screen reads as "we measured this area and found nothing"
|
||||
rather than "this selection is finer than the source raster".
|
||||
|
||||
Falling back to every touched cell keeps a small selection answerable, at the
|
||||
cost of analysing more ground than was drawn. That trade is only honest if it
|
||||
is stated, so the fallback is reported alongside the result.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RasterCellSelection:
|
||||
mask: Any
|
||||
mode: str
|
||||
expanded_to_touched_cells: bool
|
||||
warning: str | None
|
||||
|
||||
|
||||
def select_cells(
|
||||
geometry,
|
||||
*,
|
||||
out_shape: tuple[int, int],
|
||||
transform,
|
||||
cell_area_m2: float | None = None,
|
||||
) -> RasterCellSelection:
|
||||
"""Mask the cells a selection covers, widening only when it covers none.
|
||||
|
||||
``geometry`` must already be in the raster's own CRS.
|
||||
"""
|
||||
|
||||
from rasterio.features import geometry_mask
|
||||
from shapely.geometry import mapping
|
||||
|
||||
shapes = [mapping(geometry)]
|
||||
mask = geometry_mask(shapes, out_shape=out_shape, transform=transform, invert=True)
|
||||
if mask.any():
|
||||
return RasterCellSelection(
|
||||
mask=mask,
|
||||
mode="cell_centre",
|
||||
expanded_to_touched_cells=False,
|
||||
warning=None,
|
||||
)
|
||||
|
||||
touched = geometry_mask(
|
||||
shapes,
|
||||
out_shape=out_shape,
|
||||
transform=transform,
|
||||
invert=True,
|
||||
all_touched=True,
|
||||
)
|
||||
if not touched.any():
|
||||
# The selection does not reach the raster at all. Widening the rule
|
||||
# must never manufacture coverage that is genuinely absent.
|
||||
return RasterCellSelection(
|
||||
mask=mask,
|
||||
mode="cell_centre",
|
||||
expanded_to_touched_cells=False,
|
||||
warning=None,
|
||||
)
|
||||
|
||||
cell_count = int(touched.sum())
|
||||
if cell_area_m2:
|
||||
analysed_ha = cell_count * float(cell_area_m2) / 10_000.0
|
||||
detail = f"{cell_count} rastercel{'' if cell_count == 1 else 'len'} ({analysed_ha:.1f} ha)"
|
||||
else:
|
||||
detail = f"{cell_count} rastercel{'' if cell_count == 1 else 'len'}"
|
||||
return RasterCellSelection(
|
||||
mask=touched,
|
||||
mode="all_touched",
|
||||
expanded_to_touched_cells=True,
|
||||
warning=(
|
||||
f"De selectie is kleiner dan één rastercel van deze bron. Het resultaat geldt voor {detail} "
|
||||
"die de selectie raken, dus voor een groter gebied dan getekend."
|
||||
),
|
||||
)
|
||||
Reference in New Issue
Block a user