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>
25 lines
974 B
Python
25 lines
974 B
Python
import numpy as np
|
|
from shapely.geometry import Polygon
|
|
from leonida.buildings import building_polygons, regularize
|
|
from leonida.classify import CLASSES
|
|
|
|
def test_rough_rectangle_snaps_to_rectangle():
|
|
poly = Polygon([(0, 0), (10.4, 0.3), (10.1, 20.2), (-0.2, 19.8)])
|
|
out = regularize(poly)
|
|
assert len(out.exterior.coords) == 5 # geschlossenes Rechteck
|
|
assert abs(out.area - 205) < 25
|
|
|
|
def test_l_shape_is_kept_not_boxed():
|
|
poly = Polygon([(0, 0), (30, 0), (30, 10), (10, 10), (10, 30), (0, 30)])
|
|
out = regularize(poly)
|
|
assert out.area < 0.8 * out.minimum_rotated_rectangle.area
|
|
|
|
def test_tiny_polygon_is_dropped():
|
|
assert regularize(Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])) is None
|
|
|
|
def test_two_buildings_are_separated():
|
|
labels = np.full((60, 60), CLASSES["stadt"], dtype=np.uint8)
|
|
labels[10:25, 10:25] = CLASSES["gebaeude"]
|
|
labels[35:50, 35:50] = CLASSES["gebaeude"]
|
|
assert len(building_polygons(labels)) == 2
|