reach kommt vom breitesten Arm der ganzen Kreuzung, wurde aber fuer jeden Arm gleich verwendet. An einer Kreuzung, wo ein kurzer Arm auf einen breiten trifft, reicht der kurze Arm dann nicht mehr bis reach (Zebrastreifen) oder bis reach + STOP_SETBACK + STOP_DEPTH (Haltelinie) - die Markierung entstand trotzdem und ragte ueber das Kantenende hinaus. junction_arms() in section.py schuetzt sich dagegen laengst; junction_marks() jetzt auch, mit getrennter Pruefung je Markierung, weil beide unterschiedlich viel Platz brauchen. Am echten Netz: 38 von 1.508 Armstummeln sind zu kurz fuer die Haltelinie, groesster Ueberstand 10,6 m (Fixrunde 1 des Koordinators). Auf tatsaechliche Zufahrten umgerechnet (Schleifen zaehlen doppelt) sind es 44 von 1.536 - die Differenz zur Kantenzahl war anfangs unklar, ist jetzt aber nachgerechnet und im Kommentar festgehalten. markings(graph) sinkt dadurch von 22.586 auf 22.336 Flaechen (-206 Zebrabalken, -44 Haltelinien) - genau das vorher falsch platzierte Material. Dazu zwei bisher uebersehene Stellen mit demselben zu lockeren Testfilter (abs(centroid.y) < 20) auf die halbe Fahrbahnbreite verengt, wie schon an den zwei Schwesterstellen der vorigen Fixrunde - die Haltelinie des Suedarms erfuellte die lockere Bedingung ebenfalls, next() traf bisher nur zufaellig zuerst auf den Ostarm. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
117 lines
6.6 KiB
Python
117 lines
6.6 KiB
Python
"""Haltelinien und Zebrastreifen."""
|
|
from __future__ import annotations
|
|
|
|
from collections import Counter
|
|
|
|
from leonida.roads import Edge, Graph, Node
|
|
from leonida.marking import CROSSWALK_DEPTH, junction_marks, markings
|
|
from leonida.section import SECTION, setback
|
|
|
|
|
|
def kreuzung(klassen=("hauptstrasse",) * 4, arm: float = 80.0) -> Graph:
|
|
punkte = [(arm, 0.0), (-arm, 0.0), (0.0, arm), (0.0, -arm)]
|
|
return Graph(nodes=[Node((0.0, 0.0))] + [Node(p) for p in punkte],
|
|
edges=[Edge(0, i + 1, [(0.0, 0.0), p], SECTION[k].corridor, k)
|
|
for i, (p, k) in enumerate(zip(punkte, klassen))])
|
|
|
|
|
|
def test_jeder_arm_bekommt_zebra_und_haltelinie():
|
|
zahl = Counter(art for _, art in junction_marks(kreuzung(), 0))
|
|
assert zahl["haltelinie"] == 4, "eine je Zufahrt"
|
|
assert zahl["zebra"] > 4 * 5, "je Arm mehrere Balken"
|
|
|
|
|
|
def test_zebra_liegt_am_kreuzungsrand():
|
|
rand = setback(kreuzung(), 0)
|
|
zebra = [p for p, art in junction_marks(kreuzung(), 0) if art == "zebra"]
|
|
# Der Arm nach Osten: Balken zwischen Kreuzungsrand und Kreuzungsrand plus Tiefe. Die
|
|
# Querbedingung muss enger sein als die halbe Fahrbahnbreite (6 m): Bei |y| < 20 träfe sie
|
|
# auch Nord- und Südarm-Balken nahe der Ecke (deren eigene Querachse dort ohnehin liegt), die
|
|
# aber bei x um 8-11 m liegen wie der Ostarm selbst - falsches Grün durch Verwechslung, keine
|
|
# echte Prüfung (Fixrunde zu Task 9).
|
|
halb = SECTION["hauptstrasse"].carriageway / 2
|
|
ost = [p for p in zebra if p.centroid.x > 0 and abs(p.centroid.y) < halb]
|
|
assert ost, "der Ostarm hat Balken"
|
|
for poly in ost:
|
|
assert rand - 0.1 <= poly.bounds[0] <= rand + CROSSWALK_DEPTH
|
|
|
|
|
|
def test_haltelinie_liegt_hinter_dem_zebrastreifen():
|
|
rand = setback(kreuzung(), 0)
|
|
halt = [p for p, art in junction_marks(kreuzung(), 0) if art == "haltelinie"]
|
|
# Wie bei test_zebra_liegt_am_kreuzungsrand: |y| < 20 wäre zu locker. Die Haltelinie des
|
|
# Südarms erfüllt sie ebenfalls (centroid bei x=3, |y|=12,5 < 20) - next() traf bisher nur
|
|
# zufällig zuerst auf den Ostarm, weil er in "echte" zuerst kommt, nicht weil der Filter ihn
|
|
# tatsächlich isoliert (Fixrunde 1 des Koordinators zu Task 9, vom Prüfer nachgerechnet).
|
|
halb = SECTION["hauptstrasse"].carriageway / 2
|
|
ost = next(p for p in halt if p.centroid.x > 0 and abs(p.centroid.y) < halb)
|
|
assert ost.bounds[0] > rand + CROSSWALK_DEPTH, "erst Zebrastreifen, dann Haltelinie"
|
|
|
|
|
|
def test_haltelinie_deckt_nur_die_rechte_haelfte():
|
|
halt = [p for p, art in junction_marks(kreuzung(), 0) if art == "haltelinie"]
|
|
# Derselbe zu lockere Filter wie oben - siehe dortigen Kommentar.
|
|
halb = SECTION["hauptstrasse"].carriageway / 2
|
|
ost = next(p for p in halt if p.centroid.x > 0 and abs(p.centroid.y) < halb)
|
|
# Zufahrt von Osten nach Westen, also Fahrtrichtung (-1, 0): Rechts davon liegt y > 0 - per
|
|
# offset_curve()-Konvention nachgerechnet (negativ = rechts der Linienrichtung), nicht nur
|
|
# behauptet. Der Auftrag ging von y < 0 aus; das war die Gegenspur (Fixrunde zu Task 9).
|
|
assert ost.centroid.y > 0
|
|
assert round(ost.bounds[3] - ost.bounds[1], 1) == round(SECTION["hauptstrasse"].carriageway / 2, 1)
|
|
|
|
|
|
def test_arm_der_eine_gasse_ist_bekommt_keinen_zebrastreifen():
|
|
graph = kreuzung(("hauptstrasse", "hauptstrasse", "hauptstrasse", "gasse"))
|
|
marks = junction_marks(graph, 0)
|
|
# Wie bei test_zebra_liegt_am_kreuzungsrand: |x| < 20 wäre zu locker und träfe auch Ost- und
|
|
# Westarm-Balken nahe der Ecke, obwohl deren Querachse (|x| bis 9,5 m für Zebra, 12,5 m für
|
|
# die Haltelinie) klar außerhalb der Südarm-Fahrbahnhälfte liegt (Fixrunde zu Task 9).
|
|
halb = SECTION["hauptstrasse"].carriageway / 2
|
|
süd = [art for p, art in marks if p.centroid.y < -1 and abs(p.centroid.x) < halb]
|
|
assert süd == [], "der Gassenarm bleibt blank"
|
|
|
|
|
|
def test_kreuzung_aus_lauter_gassen_bekommt_nichts():
|
|
assert junction_marks(kreuzung(("gasse",) * 4), 0) == []
|
|
|
|
|
|
def test_markings_sammelt_laengs_und_kreuzung():
|
|
arten = {art for _, art in markings(kreuzung())}
|
|
assert "gelb_doppelt" in arten and "zebra" in arten and "haltelinie" in arten
|
|
|
|
|
|
def test_schleife_bekommt_haltelinie_fuer_beide_richtungen():
|
|
# Wie test_schleife_bekommt_zwei_stummel_kein_loch_im_belag in test_junction.py: Eine
|
|
# Schleife hängt mit beiden Enden am selben Knoten. graph.incident() liefert sie nur einmal,
|
|
# aber sie stellt zwei getrennte Abgangsrichtungen dar (Verkehr kann von beiden Seiten in die
|
|
# Kreuzung einfahren) - am echten Netz betrifft das 28 von 690 Kreuzungen mit einer
|
|
# bordsteintragenden Schleife. Ohne die zweite Richtung fehlten Haltelinie und Zebrastreifen
|
|
# auf der einen Seite der Schleife komplett.
|
|
graph = Graph(
|
|
nodes=[Node((0.0, 0.0)), Node((-100.0, 0.0)), Node((0.0, -100.0))],
|
|
edges=[Edge(0, 0, [(0.0, 0.0), (0.0, 20.0), (20.0, 20.0), (20.0, 0.0), (0.0, 0.0)],
|
|
SECTION["hauptstrasse"].corridor, "hauptstrasse"),
|
|
Edge(0, 1, [(0.0, 0.0), (-100.0, 0.0)], SECTION["hauptstrasse"].corridor, "hauptstrasse"),
|
|
Edge(0, 2, [(0.0, 0.0), (0.0, -100.0)], SECTION["hauptstrasse"].corridor, "hauptstrasse")])
|
|
zahl = Counter(art for _, art in junction_marks(graph, 0))
|
|
assert zahl["haltelinie"] == 4, "zwei gerade Arme plus zwei Richtungen der Schleife"
|
|
|
|
|
|
def test_kurzer_arm_an_breiter_kreuzung_bleibt_ohne_markierung():
|
|
# reach = setback() kommt vom breitesten anliegenden Arm (hier Boulevard, halbe Fahrbahn 9 m)
|
|
# und gilt für alle Zufahrten gleich - ein kurzer Arm reicht dann nicht mehr bis dorthin. Am
|
|
# echten Netz sind 38 von 1.508 geprüften Armstummeln zu kurz für die Haltelinie (44 von 1.536
|
|
# tatsächlichen Zufahrten, die Schleifen aus der vorigen Fixrunde zählen doppelt), im
|
|
# schlimmsten Fall um 10,6 m (Fixrunde 1 des Koordinators zu Task 9). Der Nordarm hier ist nur
|
|
# 10 m lang; reach allein liegt schon bei 11 m, also klar dahinter - jede Markierung, die
|
|
# trotzdem entstünde, läge neben der Straße.
|
|
graph = Graph(
|
|
nodes=[Node((0.0, 0.0)), Node((100.0, 0.0)), Node((0.0, 10.0))],
|
|
edges=[Edge(0, 1, [(0.0, 0.0), (100.0, 0.0)], SECTION["boulevard"].corridor, "boulevard"),
|
|
Edge(0, 2, [(0.0, 0.0), (0.0, 10.0)], SECTION["hauptstrasse"].corridor, "hauptstrasse")])
|
|
marks = junction_marks(graph, 0)
|
|
jenseits_des_kurzen_arms = [p for p, _ in marks if p.centroid.y > 10.0]
|
|
assert jenseits_des_kurzen_arms == [], "der 10 m lange Nordarm bekommt nichts, reach ist 11 m"
|
|
arten = {art for _, art in marks}
|
|
assert {"zebra", "haltelinie"} <= arten, "der breite, lange Ostarm behält beides"
|