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>
40 lines
1.5 KiB
Python
40 lines
1.5 KiB
Python
import warnings
|
|
|
|
import numpy as np
|
|
from shapely.geometry import Polygon
|
|
|
|
from .classify import CLASSES
|
|
from .vectors import mask_polygons
|
|
|
|
MIN_AREA = 12.0
|
|
RECT_COVERAGE = 0.85
|
|
|
|
|
|
def regularize(poly: Polygon) -> Polygon | None:
|
|
"""Rechteck, wenn das Polygon sein gedrehtes Hüllrechteck gut ausfüllt; sonst vereinfacht."""
|
|
poly = poly.buffer(0)
|
|
if poly.geom_type != "Polygon" or poly.area < MIN_AREA:
|
|
return None
|
|
with warnings.catch_warnings():
|
|
# Shapelys oriented_envelope (minimum_rotated_rectangle) wertet einen Winkelterm
|
|
# aus, der für bereits achsenparallele/rechtwinklige Polygone exakt 0/0 wird -
|
|
# GEOS meldet das als RuntimeWarning, obwohl das zurückgegebene Rechteck korrekt
|
|
# bleibt (geprüft: Fläche stimmt, kein NaN). Nur diese eine Property-Abfrage ist
|
|
# eingeschränkt, damit ein echter Warnhinweis an anderer Stelle sichtbar bliebe.
|
|
warnings.filterwarnings("ignore", message=r".*oriented_envelope.*", category=RuntimeWarning)
|
|
rect = poly.minimum_rotated_rectangle
|
|
if rect.area > 0 and poly.area / rect.area >= RECT_COVERAGE:
|
|
return rect
|
|
return poly.simplify(0.5)
|
|
|
|
|
|
def building_polygons(labels: np.ndarray) -> list[tuple[Polygon, bool]]:
|
|
out = []
|
|
for name, speculative in (("gebaeude", False), ("spekulativ", True)):
|
|
mask = labels == CLASSES[name]
|
|
for poly in mask_polygons(mask, min_area=MIN_AREA, simplify=0.5):
|
|
reg = regularize(poly)
|
|
if reg is not None:
|
|
out.append((reg, speculative))
|
|
return out
|