diff --git a/tools/extract/leonida/marking.py b/tools/extract/leonida/marking.py index a978055..15783a3 100644 --- a/tools/extract/leonida/marking.py +++ b/tools/extract/leonida/marking.py @@ -119,20 +119,34 @@ def junction_marks(graph: Graph, node: int) -> list[tuple[Polygon, str]]: along = _unit(origin, points[1] if len(points) > 1 else points[0]) across = (-along[1], along[0]) halb = SECTION[edge.klass].carriageway / 2 - # Balken längs der Fahrtrichtung, quer über die Fahrbahn verteilt. - offset = -halb + BAR / 2 - while offset <= halb - BAR / 2 + 1e-9: - mitte = (origin[0] + across[0] * offset, origin[1] + across[1] * offset) - out.append((_rect(mitte, along, across, reach, CROSSWALK_DEPTH, BAR), "zebra")) - offset += 2 * BAR - # Haltelinie quer, nur über die rechte Hälfte aus Sicht der Zufahrt. "across" ist schon - # als links von "along" definiert (dieselbe Konvention wie offset_curve() in _offset(): - # positiv = links). Die Zufahrt fährt zum Knoten hin, also gegen "along" - und die Umkehr - # der Fahrtrichtung vertauscht links und rechts. Rechts der Einfahrt ist deshalb +across, - # ohne weiteres Vorzeichen: eine Probe mit offset_curve() auf der Einfahrtlinie bestätigt - # das (Fixrunde zu Task 9 - der Auftrag hatte hier -across und lag damit auf der Gegenspur). - mitte = (origin[0] + across[0] * halb / 2, origin[1] + across[1] * halb / 2) - out.append((_rect(mitte, along, across, reach + STOP_SETBACK, STOP_DEPTH, halb), "haltelinie")) + # reach kommt vom breitesten Arm der ganzen Kreuzung, gilt hier aber für diesen einen + # Arm - ein kurzer Arm an einer sonst breiten Kreuzung reicht oft nicht bis dorthin. Am + # echten Netz sind 38 von 1.508 geprüften Armstummeln zu kurz für die Haltelinie, + # im schlimmsten Fall um 10,6 m (Fixrunde 1 des Koordinators zu Task 9; auf die + # tatsächlichen Zufahrten umgerechnet, inklusive der doppelt zählenden Schleifen aus der + # vorigen Fixrunde, sind es 44 von 1.536). Ohne diese Prüfung ragte die Markierung über + # das Ende der Kante hinaus und läge neben der Straße statt darauf. Zebrastreifen und + # Haltelinie brauchen unterschiedlich viel Platz, deshalb getrennt geprüft statt + # gemeinsam verworfen. + länge = LineString(points).length + if länge >= reach + CROSSWALK_DEPTH: + # Balken längs der Fahrtrichtung, quer über die Fahrbahn verteilt. + offset = -halb + BAR / 2 + while offset <= halb - BAR / 2 + 1e-9: + mitte = (origin[0] + across[0] * offset, origin[1] + across[1] * offset) + out.append((_rect(mitte, along, across, reach, CROSSWALK_DEPTH, BAR), "zebra")) + offset += 2 * BAR + if länge >= reach + STOP_SETBACK + STOP_DEPTH: + # Haltelinie quer, nur über die rechte Hälfte aus Sicht der Zufahrt. "across" ist + # schon als links von "along" definiert (dieselbe Konvention wie offset_curve() in + # _offset(): positiv = links). Die Zufahrt fährt zum Knoten hin, also gegen "along" - + # und die Umkehr der Fahrtrichtung vertauscht links und rechts. Rechts der Einfahrt + # ist deshalb +across, ohne weiteres Vorzeichen: eine Probe mit offset_curve() auf + # der Einfahrtlinie bestätigt das (Fixrunde zu Task 9 - der Auftrag hatte hier + # -across und lag damit auf der Gegenspur). + mitte = (origin[0] + across[0] * halb / 2, origin[1] + across[1] * halb / 2) + out.append((_rect(mitte, along, across, reach + STOP_SETBACK, STOP_DEPTH, halb), + "haltelinie")) return out diff --git a/tools/extract/tests/test_marking_junction.py b/tools/extract/tests/test_marking_junction.py index 824050c..b6a38c5 100644 --- a/tools/extract/tests/test_marking_junction.py +++ b/tools/extract/tests/test_marking_junction.py @@ -39,13 +39,20 @@ def test_zebra_liegt_am_kreuzungsrand(): def test_haltelinie_liegt_hinter_dem_zebrastreifen(): rand = setback(kreuzung(), 0) halt = [p for p, art in junction_marks(kreuzung(), 0) if art == "haltelinie"] - ost = next(p for p in halt if p.centroid.x > 0 and abs(p.centroid.y) < 20) + # 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"] - ost = next(p for p in halt if p.centroid.x > 0 and abs(p.centroid.y) < 20) + # 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). @@ -88,3 +95,22 @@ def test_schleife_bekommt_haltelinie_fuer_beide_richtungen(): 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"