Files
gta6/tools/extract/tests/test_heights.py
T
knsoandClaude Sonnet 5 42f2a4415d 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>
2026-09-12 16:14:56 +02:00

76 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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