fix(extract): carriageway_area() liefert immer MultiPolygon, Kreuzungstest schärfen

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 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-17 03:35:59 +02:00
1 parent f91b5ab7c6
commit ee07a6bf2a
2 files changed
+27 -6

No files matched your search

+8 -2
View File
@@ -10,7 +10,7 @@ from __future__ import annotations
from dataclasses import dataclass 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 shapely.ops import substring, unary_union
from .roads import CLASS_WIDTH, Graph 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 = [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 parts += [area for node in range(len(graph.nodes)) if graph.degree(node) >= 3
and (area := junction_area(graph, node)) is not None] 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])
+19 -4
View File
@@ -29,16 +29,31 @@ def test_einspringende_ecke_ist_gerundet():
def test_zwei_parallele_strassen_verschmelzen_nicht(): 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. # 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. # Je ein Querarm macht aus jeder Straße eine echte Kreuzung (Grad 3) - ohne ihn hätte keiner
graph = Graph(nodes=[Node((0.0, 0.0)), Node((100.0, 0.0)), Node((0.0, 10.0)), Node((100.0, 10.0))], # der vier Knoten Grad >= 3, junction_area() liefe nie, und der Test bewachte nichts (siehe
edges=[Edge(0, 1, [(0.0, 0.0), (100.0, 0.0)], 12.0, "wohnstrasse"), # Fixbericht). Ein auf die ganze Karte statt je Knoten angewandtes Schließen würde die beiden
Edge(2, 3, [(0.0, 10.0), (100.0, 10.0)], 12.0, "wohnstrasse")]) # 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) area = carriageway_area(graph)
assert len(area.geoms) == 2, "getrennte Straßen bleiben getrennt" assert len(area.geoms) == 2, "getrennte Straßen bleiben getrennt"
def test_fahrbahn_enthaelt_baender_und_kreuzung(): def test_fahrbahn_enthaelt_baender_und_kreuzung():
area = carriageway_area(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(0, 0)), "die Kreuzung selbst"
assert area.contains(Point(40, 0)), "ein Band" assert area.contains(Point(40, 0)), "ein Band"
assert not area.contains(Point(40, 40)), "die Ecke zwischen zwei Armen bleibt frei" assert not area.contains(Point(40, 40)), "die Ecke zwischen zwei Armen bleibt frei"