Files
gta6/tools/extract/tests/test_park_street.py
T
knsoandClaude Sonnet 5 8bc8ca19fc feat(extract): Flamingo Park hängt seine Straße an den Graphen
World.centerlines wird zu World.graph: apply_street() und apply_park() schneiden und
verlängern jetzt den Straßengraphen statt einer losen Liste von Mittellinien, mit den neuen
roads.cut_edges()/attach() als Gegenstück zum bestehenden Schneiden der Straßenflächen.
apply_park() schneidet dabei einmal am Parkviereck (nicht je Sperrfläche - das schneidet
tatsächlich nichts, was der Parkumriss nicht schon geschnitten hätte). check_corners() bleibt
unverändert gegen world.roads (den Korridor) messen, mit einem erklärenden Docstring-Absatz und
einer Regressionswache dafür in den Tests.

MIN_CENTERLINE (15 m) entfällt zugunsten von MIN_EDGE (5 m): Der alte Grund - Laternen wählten
ihre Straßenseite über den Listenindex - fällt weg, sobald eine spätere Aufgabe die Seite aus
der Geometrie bestimmt. test_park_street.py dokumentiert das jetzt: die beiden 11-m-Enden der
geschnittenen Gasse bleiben deshalb stehen, statt wie vorher zu verschwinden.

Beim Nachrechnen der Prüfkörper aus dem Auftrag fielen zwei echte Fehler auf: Der Reste-unter-
Mindestlänge-Test schnitt ursprünglich bis übers Streckenende hinaus, sodass der Rest schon
geometrisch verschwand und der MIN_EDGE-Filter nie geprüft wurde - jetzt schneidet er innerhalb
der Strecke, sodass tatsächlich ein zu kurzes Stück verworfen wird. Und die neue
Korridor-Regressionswache verglich (12-7)/2 > 2.5, was mit den echten Maßen immer falsch ist
(2.5 > 2.5); korrigiert auf >=, denn die Halbdifferenz trifft die Toleranz exakt.

Ausserdem musste eine Test-Fixture in test_park_apply.py von einem 5-m-Reststück (exakt an der
neuen MIN_EDGE-Grenze, hätte also entgegen der ursprünglichen Testabsicht überlebt) auf 3 m
verkürzt werden, damit der Test weiterhin prüft, dass echte Kurzreste wegfallen.

run.py bleibt unangetastet: Es ist bereits durch eine frühere Aufgabe gebrochen und wird von der
nächsten Aufgabe repariert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 10:41:08 +02:00

75 lines
4.0 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 LineString, Point, box
from shapely.ops import unary_union
from leonida.park import Street, World, apply_street
from leonida.roads import MIN_EDGE, Edge, Graph, Node
STREET = Street(start=(150.0, 660.0), end=(150.0, 320.0), width=12.0,
remove_buildings=((155.0, 615.0),),
alley_cut=((90.0, 639.0), (110.0, 639.0), (110.0, 341.0), (90.0, 341.0)))
def world():
"""Querstraßen bei y 640–660 und 320–340, dazwischen eine Gasse bei x 95–105."""
removed = box(140, 600, 170, 630)
return World(
land=[box(-100, 200, 300, 800)],
surfaces=[(box(0, 340, 95, 640), "urban"), (box(105, 340, 200, 640).difference(removed), "urban")],
roads=[(unary_union([box(0, 640, 200, 660), box(0, 320, 200, 340), box(95, 340, 105, 640)]), False)],
graph=Graph(nodes=[Node((0.0, 650.0)), Node((200.0, 650.0)), Node((0.0, 330.0)), Node((200.0, 330.0)),
Node((100.0, 650.0)), Node((100.0, 330.0))],
edges=[Edge(0, 1, [(0.0, 650.0), (200.0, 650.0)], 12.0, "wohnstrasse"),
Edge(2, 3, [(0.0, 330.0), (200.0, 330.0)], 12.0, "wohnstrasse"),
Edge(4, 5, [(100.0, 650.0), (100.0, 330.0)], 6.0, "gasse")]),
buildings=[(removed, {"height": 20.9, "source": "district"}, False),
(box(20, 400, 60, 450), {"height": 10.8, "source": "district"}, False),
(box(84, 450, 93, 470), {"height": 8.0, "source": "district"}, False)],
palms=[(150.0, 500.0), (60.0, 500.0), (140.0, 500.0)],
)
def on_road(result, x, y):
return any(poly.contains(Point(x, y)) for poly, _ in result.roads)
def test_buildings_under_the_street_go_and_the_others_stay():
result = apply_street(world(), STREET)
assert [b[0].bounds for b in result.buildings] == [(20.0, 400.0, 60.0, 450.0), (84.0, 450.0, 93.0, 470.0)]
def test_alley_is_cut_and_the_straight_street_joins_both_cross_streets():
result = apply_street(world(), STREET)
assert not on_road(result, 100, 500)
assert on_road(result, 150, 500) and not on_road(result, 143, 500)
assert on_road(result, 100, 650) and on_road(result, 100, 330)
assert on_road(result, 150, 650) and on_road(result, 150, 330)
def test_alley_centerline_shrinks_to_its_stubs_and_the_street_gets_its_own():
"""Früher (MIN_CENTERLINE=15) verschwand die geschnittene Gasse ganz, weil ihre beiden Enden
nur 11 m lang sind. cut_edges() kennt nur noch MIN_EDGE (5 m): der alte Grund - Laternen
wählten ihre Straßenseite über den Listenindex und liefen an der Kreuzung sonst ins Leere -
entfällt, sobald eine spätere Aufgabe die Seite aus der Geometrie bestimmt statt aus der
Liste. Die beiden 11-m-Enden bleiben deshalb jetzt als eigene, kurze Kanten stehen.
"""
result = apply_street(world(), STREET)
lines = [LineString(e.points) for e in result.graph.edges]
assert sorted(round(line.length) for line in lines if line.length < 15) == [11, 11]
assert all(line.distance(Point(100, 500)) > 40 for line in lines)
assert any(e.points == [tuple(STREET.start), tuple(STREET.end)] for e in result.graph.edges)
assert all(line.length >= MIN_EDGE for line in lines)
def test_freed_ground_becomes_urban_but_not_under_the_street_buildings_or_old_urban():
result = apply_street(world(), STREET)
new = [poly for poly, kind in result.surfaces[2:] if kind == "urban"]
assert any(poly.contains(Point(100, 500)) for poly in new) # alte Gasse
assert any(poly.contains(Point(165, 615)) for poly in new) # alter Grundriss neben der Straße
assert not any(poly.contains(Point(150, 615)) for poly in new) # unter der neuen Straße
assert not any(poly.contains(Point(91, 460)) for poly in new) # stehendes Gebäude
assert not any(poly.contains(Point(92, 500)) for poly in new) # war schon Stadtfläche
def test_palms_on_the_new_street_go():
assert apply_street(world(), STREET).palms == [(60.0, 500.0), (140.0, 500.0)]