From ee07a6bf2a684610340bc43d1011c2fb5047f864 Mon Sep 17 00:00:00 2001 From: Kenso Grimm Date: Thu, 17 Sep 2026 03:35:59 +0200 Subject: [PATCH] =?UTF-8?q?fix(extract):=20carriageway=5Farea()=20liefert?= =?UTF-8?q?=20immer=20MultiPolygon,=20Kreuzungstest=20sch=C3=A4rfen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zwei Befunde aus der Prüfung: unary_union() vereinte eine einzelne zusammenhängende Kreuzung zu einem einfachen Polygon statt des zugesagten MultiPolygon (bricht an kleinen Prüfkörpern, nicht am echten Netz mit seinen 213 Teilen - deshalb bisher unbemerkt). Normalisiert jetzt auf MultiPolygon, mit Testzusicherung an der zusammenhängenden kreuzung()-Fixture. test_zwei_parallele_strassen_verschmelzen_nicht bewachte nichts: alle vier Knoten hatten Grad 1, junction_area() lief nie. Fixture bekommt jetzt an beiden Straßen einen Querarm (Grad 3), damit das Schließen tatsächlich ausgeführt wird, während die Straßen getrennt bleiben. Co-Authored-By: Claude Sonnet 5 --- tools/extract/leonida/section.py | 10 ++++++++-- tools/extract/tests/test_junction.py | 23 +++++++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/tools/extract/leonida/section.py b/tools/extract/leonida/section.py index 1f268cb..cfe4a68 100644 --- a/tools/extract/leonida/section.py +++ b/tools/extract/leonida/section.py @@ -10,7 +10,7 @@ from __future__ import annotations from dataclasses import dataclass -from shapely.geometry import LineString, Point +from shapely.geometry import LineString, MultiPolygon, Point from shapely.ops import substring, unary_union from .roads import CLASS_WIDTH, Graph @@ -115,4 +115,10 @@ def carriageway_area(graph: Graph): parts = [band for k in range(len(graph.edges)) if (band := ribbon(graph, k)) is not None] parts += [area for node in range(len(graph.nodes)) if graph.degree(node) >= 3 and (area := junction_area(graph, node)) is not None] - return unary_union(parts) + vereint = unary_union(parts) + if vereint.is_empty: + return MultiPolygon() + # Zusage der Schnittstelle: immer MultiPolygon. unary_union() liefert bei zusammenhängender + # Eingabe ein einfaches Polygon, und Task 7 iteriert über .geoms — das bricht dann an kleinen + # Prüfkörpern, aber nicht am echten Netz, also genau da, wo niemand damit rechnet. + return vereint if vereint.geom_type == "MultiPolygon" else MultiPolygon([vereint]) diff --git a/tools/extract/tests/test_junction.py b/tools/extract/tests/test_junction.py index f7589a1..fce28e9 100644 --- a/tools/extract/tests/test_junction.py +++ b/tools/extract/tests/test_junction.py @@ -29,16 +29,31 @@ def test_einspringende_ecke_ist_gerundet(): def test_zwei_parallele_strassen_verschmelzen_nicht(): # Zwei Wohnstraßen mit 10 m Abstand der Mittellinien: Fahrbahn je 7 m, dazwischen 3 m Lücke. - # Ein global angewandtes Schließen mit 6 m würde sie zu einer Fläche verkleben. - graph = Graph(nodes=[Node((0.0, 0.0)), Node((100.0, 0.0)), Node((0.0, 10.0)), Node((100.0, 10.0))], - edges=[Edge(0, 1, [(0.0, 0.0), (100.0, 0.0)], 12.0, "wohnstrasse"), - Edge(2, 3, [(0.0, 10.0), (100.0, 10.0)], 12.0, "wohnstrasse")]) + # Je ein Querarm macht aus jeder Straße eine echte Kreuzung (Grad 3) - ohne ihn hätte keiner + # der vier Knoten Grad >= 3, junction_area() liefe nie, und der Test bewachte nichts (siehe + # Fixbericht). Ein auf die ganze Karte statt je Knoten angewandtes Schließen würde die beiden + # Fahrbahnen trotzdem verkleben, weil ihr Abstand (3 m) unter 2 * FILLET (12 m) liegt - + # Gegenprobe dazu ebenfalls im Fixbericht. + graph = Graph( + nodes=[Node((0.0, 0.0)), Node((100.0, 0.0)), Node((50.0, 0.0)), Node((50.0, -50.0)), + Node((0.0, 10.0)), Node((100.0, 10.0)), Node((50.0, 10.0)), Node((50.0, 60.0))], + edges=[Edge(0, 2, [(0.0, 0.0), (50.0, 0.0)], 12.0, "wohnstrasse"), + Edge(2, 1, [(50.0, 0.0), (100.0, 0.0)], 12.0, "wohnstrasse"), + Edge(2, 3, [(50.0, 0.0), (50.0, -50.0)], 12.0, "wohnstrasse"), + Edge(4, 6, [(0.0, 10.0), (50.0, 10.0)], 12.0, "wohnstrasse"), + Edge(6, 5, [(50.0, 10.0), (100.0, 10.0)], 12.0, "wohnstrasse"), + Edge(6, 7, [(50.0, 10.0), (50.0, 60.0)], 12.0, "wohnstrasse")]) + assert graph.degree(2) == 3 and graph.degree(6) == 3, "beide Straßen brauchen eine echte Kreuzung" area = carriageway_area(graph) assert len(area.geoms) == 2, "getrennte Straßen bleiben getrennt" def test_fahrbahn_enthaelt_baender_und_kreuzung(): area = carriageway_area(kreuzung()) + # Schnittstellenzusage: immer MultiPolygon, auch wenn - wie hier - eine einzelne + # zusammenhängende Kreuzung unary_union() zu einem einfachen Polygon vereint. Task 7 + # iteriert über .geoms; das bricht sonst genau an kleinen, zusammenhängenden Prüfkörpern. + assert area.geom_type == "MultiPolygon" 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"