buildings.py regularisiert Gebäude-Maskenpolygone (gebaeude/spekulativ) zu Rechtecken, wenn sie ihr gedrehtes Hüllrechteck zu mindestens 85 % ausfüllen, sonst zu vereinfachten Grundrissen; zu kleine oder entartete Polygone liefern None. heights.py weist Gebäuden eine Höhe zu (Wahrzeichen-Treffer schlägt Viertel-Bereich, deterministisch geseedet) und platziert Palmenpunkte entlang strandnaher Mittellinien: der Versatz vom Fahrbahnrand wird von innen nach außen gesucht (PALM_VERGE), bis eine Position sowohl PALM_KERB von der Fahrbahn als auch PALM_CLEARANCE von Gebäuden einhält; findet sich keine, entfällt die Palme. Start bei halber PALM_SPACING verhindert doppelte Palmen an Kreuzungen, wo Task 4 Mittellinien pro Ast trennt. pyproject.toml ergänzt einen gezielten filterwarnings-Eintrag: Shapelys oriented_envelope (minimum_rotated_rectangle) meldet für bereits achsen- parallele/rechtwinklige Polygone eine RuntimeWarning aus GEOS (Winkelterm wird 0/0), obwohl das Ergebnis nachweislich korrekt bleibt – u.a. löst der Briefs eigener L-Shape-Test das aus. Nur dieser eine Text+Kategorie- kombinierte Fall wird gefiltert, jede andere Warnung bleibt sichtbar. Auf dem echten Mosaik: 2141 Gebäudepolygone (258 spekulativ, 211 von regularize verworfen), Höhen 6.0-15.0 m (nur "district", da data/skyline.json und data/districts.json erst in Task 6 kommen), 2433 Palmenpunkte. Straßen- und Mittellinienzahlen (475 Stücke/40 Brücken, 2323 Linien/Median 27 m) decken sich exakt mit den in Task 4 unabhängig ermittelten Referenzwerten. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
41 lines
1.6 KiB
Python
41 lines
1.6 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
|
|
simple = poly.simplify(1.0)
|
|
return simple if simple.area >= MIN_AREA else None
|
|
|
|
|
|
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
|