From f91b5ab7c6985635f07a3de4ee798ac7401d8deb Mon Sep 17 00:00:00 2001 From: Kenso Grimm Date: Thu, 17 Sep 2026 02:53:15 +0200 Subject: [PATCH] fix(extract): Schleifen-Kante bekommt zwei Stummel in junction_area() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit incident() liefert eine Schleife (a == b) nur einmal, und edge.a == node ist bei ihr immer wahr - die zweite Abgangsrichtung bekam deshalb nie einen eigenen Arm im Kreuzungsfächer. Das war kein Schönheitsfehler, sondern ein Loch in der Fahrbahn genau dort, wo das zurückgeschnittene Schleifenband auf die Kreuzungsfläche trifft: 179 von 196 Schleifen sitzen an einem Knoten mit Grad >= 3 und waren betroffen. Co-Authored-By: Claude Sonnet 5 --- tools/extract/leonida/section.py | 17 ++++++++++++----- tools/extract/tests/test_junction.py | 17 +++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/tools/extract/leonida/section.py b/tools/extract/leonida/section.py index 011b231..1f268cb 100644 --- a/tools/extract/leonida/section.py +++ b/tools/extract/leonida/section.py @@ -92,11 +92,18 @@ def junction_area(graph: Graph, node: int): arms = [] for k in incident: edge = graph.edges[k] - points = edge.points if edge.a == node else list(reversed(edge.points)) - line = LineString(points) - stub = substring(line, 0.0, min(reach + 1.0, line.length)) - arms.append(stub.buffer(SECTION[edge.klass].carriageway / 2, - cap_style="flat", join_style="mitre", mitre_limit=1.5)) + if len(edge.points) < 2: + continue # Punktkanten haben keine Richtung; LineString wirft an ihnen + # Eine Schleife hängt mit beiden Enden am selben Knoten und braucht deshalb zwei Stummel. + # incident() liefert sie nur einmal, und edge.a == node ist bei ihr immer wahr — ohne den + # zweiten Stummel bliebe an ihrem anderen Ende ein Loch in der Fahrbahn. + richtungen = ([edge.points, list(reversed(edge.points))] if edge.a == edge.b + else [edge.points if edge.a == node else list(reversed(edge.points))]) + for points in richtungen: + line = LineString(points) + stub = substring(line, 0.0, min(reach + 1.0, line.length)) + arms.append(stub.buffer(SECTION[edge.klass].carriageway / 2, + cap_style="flat", join_style="mitre", mitre_limit=1.5)) star = unary_union(arms) near = Point(graph.nodes[node].point).buffer(reach + FILLET + 2.0) rounded = star.buffer(FILLET, join_style="round").buffer(-FILLET, join_style="round") diff --git a/tools/extract/tests/test_junction.py b/tools/extract/tests/test_junction.py index 725bb73..f7589a1 100644 --- a/tools/extract/tests/test_junction.py +++ b/tools/extract/tests/test_junction.py @@ -42,3 +42,20 @@ def test_fahrbahn_enthaelt_baender_und_kreuzung(): assert area.contains(Point(0, 0)), "die Kreuzung selbst" assert area.contains(Point(40, 0)), "ein Band" assert not area.contains(Point(40, 40)), "die Ecke zwischen zwei Armen bleibt frei" + + +def test_schleife_bekommt_zwei_stummel_kein_loch_im_belag(): + # Eine Schleife hängt mit beiden Enden am selben Knoten. incident() liefert sie nur einmal, + # und edge.a == node ist bei ihr immer wahr - ohne einen zweiten Stummel für die zweite + # Abgangsrichtung bliebe zwischen dem zurückgeschnittenen Schleifenband und der + # Kreuzungsfläche ein Loch. Zwei gerade Arme sorgen dafür, dass der Knoten überhaupt als + # Kreuzung (Grad >= 3) zählt. + 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)], 18.0, "hauptstrasse"), + Edge(0, 1, [(0.0, 0.0), (-100.0, 0.0)], 18.0, "hauptstrasse"), + Edge(0, 2, [(0.0, 0.0), (0.0, -100.0)], 18.0, "hauptstrasse")]) + area = carriageway_area(graph) + # Zwischen der Kreuzungsfläche (endet bei halber Fahrbahn = 6 m) und dem zurückgeschnittenen + # Schleifenband (beginnt beim Setback = 8 m) liegt (7, 0) - ohne den zweiten Stummel außerhalb. + assert area.contains(Point(7.0, 0.0))