More
This commit is contained in:
parent
1f68ca0512
commit
3599803589
43 changed files with 3578 additions and 262 deletions
|
|
@ -63,10 +63,34 @@ def to_wgs84_geojson(
|
|||
}
|
||||
|
||||
|
||||
def _fill_holes(geom):
|
||||
"""Remove all interior rings (holes) from a polygon or multipolygon."""
|
||||
if geom.geom_type == "Polygon":
|
||||
return Polygon(geom.exterior)
|
||||
elif geom.geom_type == "MultiPolygon":
|
||||
return MultiPolygon([Polygon(p.exterior) for p in geom.geoms])
|
||||
return geom
|
||||
|
||||
|
||||
def _largest_polygon(geom):
|
||||
"""Extract the largest polygon from a MultiPolygon."""
|
||||
if geom.geom_type == "MultiPolygon":
|
||||
return max(geom.geoms, key=lambda g: g.area)
|
||||
return geom
|
||||
|
||||
|
||||
def merge_fragments(
|
||||
all_fragments: list[tuple[str, Polygon | MultiPolygon]],
|
||||
greenspace_tree=None,
|
||||
greenspace_geoms=None,
|
||||
) -> dict[str, Polygon | MultiPolygon]:
|
||||
"""Merge cross-OA fragments for postcodes spanning multiple OAs."""
|
||||
"""Merge cross-OA fragments for postcodes spanning multiple OAs.
|
||||
|
||||
Args:
|
||||
all_fragments: List of (postcode, geometry) pairs.
|
||||
greenspace_tree: Optional STRtree of park/water polygons.
|
||||
greenspace_geoms: Optional list of park/water geometries (indexed by tree).
|
||||
"""
|
||||
by_postcode: dict[str, list] = defaultdict(list)
|
||||
for pc, geom in all_fragments:
|
||||
by_postcode[pc].append(geom)
|
||||
|
|
@ -80,13 +104,25 @@ def merge_fragments(
|
|||
combined = make_valid(combined)
|
||||
# Close tiny gaps between adjacent OA boundary edges (float mismatches)
|
||||
if combined.geom_type == "MultiPolygon":
|
||||
combined = combined.buffer(1.0).buffer(-1.0)
|
||||
combined = combined.buffer(5.0).buffer(-5.0)
|
||||
if not combined.is_valid:
|
||||
combined = make_valid(combined)
|
||||
# Postcodes are contiguous delivery routes — keep only the largest
|
||||
# polygon; small detached fragments are algorithm artifacts
|
||||
if combined.geom_type == "MultiPolygon":
|
||||
combined = max(combined.geoms, key=lambda g: g.area)
|
||||
combined = _largest_polygon(combined)
|
||||
# Remove artifact interior holes from INSPIRE+Voronoi+make_valid chain
|
||||
combined = _fill_holes(combined)
|
||||
# Subtract parks/water if provided
|
||||
if greenspace_tree is not None and greenspace_geoms is not None:
|
||||
from .greenspace import subtract_greenspace
|
||||
|
||||
pre_green = combined
|
||||
combined = subtract_greenspace(combined, greenspace_tree, greenspace_geoms)
|
||||
combined = _largest_polygon(combined)
|
||||
combined = _fill_holes(combined)
|
||||
# Revert if subtraction + fragment selection lost >90% of area
|
||||
if pre_green.area > 0 and combined.area / pre_green.area < 0.1:
|
||||
combined = pre_green
|
||||
merged[pc] = combined
|
||||
return merged
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue