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>
53 lines
1.9 KiB
Python
53 lines
1.9 KiB
Python
"""Kanten an einer Sperrfläche schneiden und neue Kanten anhängen."""
|
|
from __future__ import annotations
|
|
|
|
from shapely.geometry import LineString, box
|
|
|
|
from leonida.roads import Edge, Graph, MIN_EDGE, Node, attach, cut_edges
|
|
|
|
|
|
def strecke() -> Graph:
|
|
return Graph(nodes=[Node((0.0, 0.0)), Node((100.0, 0.0))],
|
|
edges=[Edge(0, 1, [(0.0, 0.0), (100.0, 0.0)], 12.0, "wohnstrasse")])
|
|
|
|
|
|
def test_schnitt_in_der_mitte_gibt_zwei_kanten():
|
|
out = cut_edges(strecke(), box(40, -5, 60, 5))
|
|
assert len(out.edges) == 2
|
|
längen = sorted(round(LineString(e.points).length) for e in out.edges)
|
|
assert längen == [40, 40]
|
|
|
|
|
|
def test_reste_unter_der_mindestlaenge_fallen_weg():
|
|
# Schnitt bei 90..96: übrig bleiben 90 m; das 4-m-Stück von 96 bis 100 liegt unter MIN_EDGE
|
|
# und fällt weg. (Ein Schnitt bis ans Streckenende, z.B. 96..104, verschluckt den Rest schon
|
|
# geometrisch mit - das würde den MIN_EDGE-Filter gar nicht prüfen, weil dann kein Stück unter
|
|
# der Grenze übrig bliebe, das erst noch verworfen werden müsste.)
|
|
out = cut_edges(strecke(), box(90, -5, 96, 5))
|
|
assert len(out.edges) == 1
|
|
assert round(LineString(out.edges[0].points).length) == 90
|
|
assert MIN_EDGE == 5.0
|
|
|
|
|
|
def test_ganz_verdeckte_kante_verschwindet():
|
|
assert cut_edges(strecke(), box(-10, -10, 110, 10)).edges == []
|
|
|
|
|
|
def test_unberuehrte_kante_bleibt_wie_sie_ist():
|
|
out = cut_edges(strecke(), box(200, -5, 220, 5))
|
|
assert out.edges[0].points == [(0.0, 0.0), (100.0, 0.0)]
|
|
assert out.edges[0].klass == "wohnstrasse"
|
|
|
|
|
|
def test_attach_findet_einen_nahen_knoten():
|
|
graph = strecke()
|
|
assert attach(graph, (100.0, 3.0)) == 1, "3 m entfernt: derselbe Knoten"
|
|
assert len(graph.nodes) == 2
|
|
|
|
|
|
def test_attach_legt_sonst_einen_neuen_knoten_an():
|
|
graph = strecke()
|
|
neu = attach(graph, (100.0, 40.0))
|
|
assert neu == 2
|
|
assert graph.nodes[neu].point == (100.0, 40.0)
|