don't crash

This commit is contained in:
Andras Schmelczer 2026-06-04 20:40:42 +01:00
parent aab85fe32e
commit d6d20ccd37
13 changed files with 2630 additions and 3924 deletions

View file

@ -263,6 +263,43 @@ def _postcode_buffers(
return circles, shapely.STRtree(circles)
# 0.1 mm in the BNG working CRS (EPSG:27700) — far below survey resolution; the
# same grid the postcode_boundaries overlay uses.
_OVERLAY_GRID_M = 1e-4
def _robust_intersection_area(a: np.ndarray, b: np.ndarray) -> np.ndarray:
"""Vectorized ``area(a[i] ∩ b[i])`` that survives GEOS robustness failures.
External Forest Research TOW/NFI polygons are occasionally invalid
(self-intersections), and a single bad polygon makes the batched
``shapely.intersection`` raise ``TopologyException: side location conflict``,
aborting the whole run. The fast path is the raw batched overlay unchanged,
full-speed, when the data is clean and only a failure triggers repair.
The repair deliberately uses a *plain* overlay rather than the fixed-precision
(``grid_size``) one: ``make_valid`` can emit a mixed-dimension
``GeometryCollection`` (a polygon plus a dangling line), which OverlayNG
rejects with ``Overlay input is mixed-dimension`` whereas a plain overlay
accepts it, and its non-polygonal debris has zero area and is dropped by the
``clipped_area > 0`` filter downstream anyway. A final pointwise coordinate
snap (which never raises) collapses the near-coincident edges behind any
residual full-precision robustness failure.
"""
try:
return shapely.area(shapely.intersection(a, b))
except shapely.errors.GEOSException:
pass
a = shapely.make_valid(a)
b = shapely.make_valid(b)
try:
return shapely.area(shapely.intersection(a, b))
except shapely.errors.GEOSException:
a = shapely.make_valid(shapely.set_precision(a, _OVERLAY_GRID_M, mode="pointwise"))
b = shapely.make_valid(shapely.set_precision(b, _OVERLAY_GRID_M, mode="pointwise"))
return shapely.area(shapely.intersection(a, b))
def _accumulate_clipped_area(
geoms: np.ndarray,
circles: np.ndarray,
@ -294,8 +331,8 @@ def _accumulate_clipped_area(
if geom_index.size == 0:
return
clipped_area = shapely.area(
shapely.intersection(geoms[geom_index], circles[postcode_index])
clipped_area = _robust_intersection_area(
geoms[geom_index], circles[postcode_index]
)
positive = clipped_area > 0
geom_index = geom_index[positive]