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>
76 lines
3.6 KiB
Python
76 lines
3.6 KiB
Python
from shapely.geometry import Polygon
|
||
from leonida.heights import DISTRICT_RANGES, assign_heights
|
||
|
||
SQUARE = Polygon([(0, 0), (20, 0), (20, 20), (0, 20)])
|
||
FAR = Polygon([(500, 500), (520, 500), (520, 520), (500, 520)])
|
||
|
||
def test_landmark_height_wins():
|
||
res = assign_heights([SQUARE], landmarks=[{"xyz": [10, 10, 0], "height": 180.0}],
|
||
districts=[], seed=1)
|
||
assert res[0]["height"] == 180.0
|
||
assert res[0]["source"] == "skyline"
|
||
|
||
def test_district_range_is_used_without_landmark():
|
||
res = assign_heights([SQUARE], landmarks=[],
|
||
districts=[{"name": "Downtown", "x": 5, "y": 5}], seed=1)
|
||
lo, hi = DISTRICT_RANGES["Downtown"]
|
||
assert lo <= res[0]["height"] <= hi
|
||
assert res[0]["source"] == "district"
|
||
|
||
def test_same_seed_gives_same_height():
|
||
args = dict(landmarks=[], districts=[{"name": "Downtown", "x": 5, "y": 5}], seed=42)
|
||
assert assign_heights([SQUARE], **args)[0]["height"] == assign_heights([SQUARE], **args)[0]["height"]
|
||
|
||
def test_unknown_district_falls_back_to_default():
|
||
res = assign_heights([FAR], landmarks=[], districts=[], seed=3)
|
||
lo, hi = DISTRICT_RANGES["default"]
|
||
assert lo <= res[0]["height"] <= hi
|
||
|
||
from shapely.geometry import LineString, Point
|
||
from leonida.heights import PALM_KERB, palm_points
|
||
|
||
def test_no_palm_stands_on_the_carriageway():
|
||
"""Der Versatz muss vom Fahrbahnrand her gedacht werden, nicht von einem festen Wert.
|
||
|
||
Am echten Mosaik hat die Fahrbahn an den Mittellinienpunkten p50 5,4 m, p90 11,7 m
|
||
und p95 14,0 m Halbbreite. Ein fester Versatz von 4 m setzt 60 % der Palmen auf die
|
||
Fahrbahn, einer von 11 m immer noch 12,5 % – und auf der Hälfte der Straßen stünde
|
||
die Palme dann im Nachbargebäude.
|
||
"""
|
||
road = Polygon([(0, -9), (200, -9), (200, 9), (0, 9)]) # 18 m breite Fahrbahn
|
||
line = LineString([(0, 0), (200, 0)])
|
||
sand = Polygon([(0, 20), (200, 20), (200, 40), (0, 40)])
|
||
pts = palm_points([sand], [], [line], [], [road], [], seed=1)
|
||
assert pts
|
||
assert all(road.distance(Point(x, y)) >= PALM_KERB for x, y in pts)
|
||
|
||
def test_palms_do_not_pile_up_where_centerlines_meet():
|
||
"""Mittellinien sind an Kreuzungen getrennt (Task 4). Ein Start bei Position 0 setzt
|
||
auf zwei gegenüberliegenden Ästen zwei Palmen in genau denselben Punkt."""
|
||
sand = Polygon([(-80, 20), (80, 20), (80, 40), (-80, 40)])
|
||
arms = [LineString([(0, 0), (60, 0)]), LineString([(0, 0), (-60, 0)])]
|
||
pts = palm_points([sand], [], arms, [], [], [], seed=1)
|
||
assert pts
|
||
assert len(set(pts)) == len(pts)
|
||
|
||
def test_no_palm_stands_in_the_water():
|
||
"""Der Versatz nach außen schiebt Palmen an Küstenstraßen über die Uferlinie.
|
||
Am echten Mosaik standen so 218 von 2.433 Palmen im Wasser."""
|
||
land = Polygon([(-100, -100), (100, -100), (100, 0), (-100, 0)]) # Ufer bei y = 0
|
||
road = Polygon([(-100, -12), (100, -12), (100, 0), (-100, 0)])
|
||
line = LineString([(-100, -6), (100, -6)])
|
||
sand = Polygon([(-100, 0), (100, 0), (100, 10), (-100, 10)])
|
||
pts = palm_points([sand], [], [line], [], [road], [land], seed=1)
|
||
assert pts
|
||
assert all(land.contains(Point(x, y)) for x, y in pts)
|
||
|
||
def test_park_scatter_is_deterministic_and_inside_the_park():
|
||
"""Ohne Sperrflächen liefert ein Park einfach verstreute, aber reproduzierbare
|
||
Punkte: derselbe Seed muss dieselbe Punktliste geben."""
|
||
park = Polygon([(0, 0), (50, 0), (50, 50), (0, 50)]) # 2500 m^2 -> 10 Punkte
|
||
pts_a = palm_points([], [park], [], [], [], [], seed=7)
|
||
pts_b = palm_points([], [park], [], [], [], [], seed=7)
|
||
assert pts_a
|
||
assert all(park.contains(Point(x, y)) for x, y in pts_a)
|
||
assert pts_a == pts_b
|