fix(extract): Palmen nicht mehr im Wasser, regularize() nicht mehr doppelt vereinfacht

Fix-Runde 1, vier Befunde aus dem Review (Rulings 25-28):

Befund A (Critical): der Versatz nach außen konnte eine Palme über die
Uferlinie ins Wasser schieben (218 von 2.433 auf dem echten Mosaik).
palm_points() bekommt jetzt land_polys (vor seed), _verge_point() verwirft
einen Kandidaten, der nicht auf Land liegt.

Befund B (Important): regularize() vereinfachte im Rückfallzweig ein zweites
Mal und gröber (1,0 m statt der 0,5 m, die mask_polygons() schon angewandt
hatte) und prüfte erneut gegen MIN_AREA - das drückte kleine, treppenrandige
Grundrisse unter die Schwelle (202 von 2.085 gebaeude-, 9 von 267
spekulativ-Kandidaten). Der Rückfallzweig vereinfacht jetzt nur noch einmal,
mit 0,5 m, ohne zweite Flächenprüfung.

Befund C: der globale filterwarnings-Eintrag in pyproject.toml (Fix für die
oriented_envelope-RuntimeWarning aus der letzten Runde) war zu weit gefasst
und hätte auch eine echte künftige Division durch Null versteckt. Er gehört
nur auf den einen Test, der minimum_rotated_rectangle selbst aufruft -
pyproject.toml zurück auf den Stand davor, @pytest.mark.filterwarnings auf
test_l_shape_is_kept_not_boxed.

Befund D (Minor): vier doppelte Palmenpunkte entfernt (dict.fromkeys vor dem
return), zwei bisher ungetestete Zweige abgedeckt (spekulativ=True/False,
Park-Streuung mit Determinismus-Check).

Test zuerst für A und B, beide vor dem jeweiligen Fix rot gesehen (echte
Assertion-Fehler, keine Signatur-Fehler). 31/31 Tests grün, weiterhin ohne
Warnungen.

Auf dem echten Mosaik: 2.352 Gebäude (vorher 2.141, +211 - exakt die Zahl,
die Befund B vorher fallen ließ; 0 von regularize verworfen, siehe Bericht
für die Erklärung), 267 spekulativ. 2.133 Palmenpunkte (Referenz des
Koordinators von 2.176 war mit dem alten, kleineren Gebäudebestand
gemessen - mit dem isoliert nachgebauten alten Bestand komme ich auf 2.175,
die verbleibende Differenz von 1 ist im Bericht dokumentiert statt verschwiegen).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-12 16:14:56 +02:00
1 parent 6de4b81942
commit 42f2a4415d
5 files changed
+73 -17

No files matched your search

+1 -2
View File
@@ -25,8 +25,7 @@ def regularize(poly: Polygon) -> Polygon | None:
rect = poly.minimum_rotated_rectangle
if rect.area > 0 and poly.area / rect.area >= RECT_COVERAGE:
return rect
simple = poly.simplify(1.0)
return simple if simple.area >= MIN_AREA else None
return poly.simplify(0.5)
def building_polygons(labels: np.ndarray) -> list[tuple[Polygon, bool]]:
+10 -5
View File
@@ -55,8 +55,9 @@ PALM_KERB = 1.5 # Meter Abstand zum Fahrbahnrand
PALM_VERGE = (6.0, 8.0, 10.0, 12.0, 14.0, 16.0)
def _verge_point(line, p, side: str, roads, buildings):
"""Erster Versatz von innen nach außen, der Fahrbahn und Gebäude freihält."""
def _verge_point(line, p, side: str, roads, buildings, land):
"""Erster Versatz von innen nach außen, der Fahrbahn und Gebäude freihält und
auf Land bleibt (Ruling 26: sonst schiebt der Versatz Küstenpalmen ins Wasser)."""
for dist in PALM_VERGE:
off = line.parallel_offset(dist, side)
if off.is_empty:
@@ -66,16 +67,20 @@ def _verge_point(line, p, side: str, roads, buildings):
continue
if buildings is not None and q.distance(buildings) < PALM_CLEARANCE:
continue
if land is not None and not land.contains(q):
continue
return [(round(q.x, 2), round(q.y, 2))]
return []
def palm_points(sand_polys, park_polys, road_lines, building_polys, road_polys, seed: int):
def palm_points(sand_polys, park_polys, road_lines, building_polys, road_polys,
land_polys, seed: int):
"""Palmen entlang strandnaher Mittellinien und verstreut in Parks."""
from shapely.ops import unary_union
sand = unary_union(sand_polys) if sand_polys else None
roads = unary_union(list(road_polys)) if road_polys else None
buildings = unary_union(list(building_polys)) if building_polys else None
land = unary_union(land_polys) if land_polys else None
blocked = unary_union([g for g in (roads, buildings) if g is not None]) \
if (roads is not None or buildings is not None) else None
points = []
@@ -88,7 +93,7 @@ def palm_points(sand_polys, park_polys, road_lines, building_polys, road_polys,
while pos <= line.length:
p = line.interpolate(pos)
for side in ("left", "right"):
points.extend(_verge_point(line, p, side, roads, buildings))
points.extend(_verge_point(line, p, side, roads, buildings, land))
pos += PALM_SPACING
for park in park_polys:
minx, miny, maxx, maxy = park.bounds
@@ -99,4 +104,4 @@ def palm_points(sand_polys, park_polys, road_lines, building_polys, road_polys,
q = Point(minx + u * (maxx - minx), miny + v * (maxy - miny))
if park.contains(q) and (blocked is None or q.distance(blocked) > PALM_CLEARANCE):
points.append((round(q.x, 2), round(q.y, 2)))
return points
return list(dict.fromkeys(points))