feat(extract): Kreuzungsflächen mit amerikanischem Eckradius
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8870484233
commit
5ddcbbfc17
2 files changed
+82
No files matched your search
@@ -71,3 +71,41 @@ def ribbon(graph: Graph, k: int):
|
|||||||
return None
|
return None
|
||||||
return line.buffer(SECTION[graph.edges[k].klass].carriageway / 2,
|
return line.buffer(SECTION[graph.edges[k].klass].carriageway / 2,
|
||||||
cap_style="flat", join_style="mitre", mitre_limit=1.5)
|
cap_style="flat", join_style="mitre", mitre_limit=1.5)
|
||||||
|
|
||||||
|
|
||||||
|
# Amerikanischer Eckradius, 20 ft. Er entscheidet über den Charakter der Kreuzung: größer wirkt
|
||||||
|
# großzügig und vorstädtisch, kleiner eng und städtisch.
|
||||||
|
FILLET = 6.0
|
||||||
|
|
||||||
|
|
||||||
|
def junction_area(graph: Graph, node: int):
|
||||||
|
"""Die Fläche einer Kreuzung: die Stummel aller Arme, mit gerundeten einspringenden Ecken.
|
||||||
|
|
||||||
|
Gerundet wird durch Schließen (aufblasen, dann wieder schrumpfen) - und ausdrücklich nur
|
||||||
|
innerhalb einer Scheibe um den Knoten. Auf das ganze Netz angewandt verklebte dieselbe
|
||||||
|
Operation zwei parallele Straßen, deren Fahrbahnen weniger als 2 * FILLET auseinanderliegen.
|
||||||
|
"""
|
||||||
|
incident = graph.incident(node)
|
||||||
|
if not incident:
|
||||||
|
return None
|
||||||
|
reach = setback(graph, node)
|
||||||
|
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))
|
||||||
|
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")
|
||||||
|
return star.union(rounded.intersection(near))
|
||||||
|
|
||||||
|
|
||||||
|
def carriageway_area(graph: Graph):
|
||||||
|
"""Alle Bänder und alle Kreuzungsflächen als eine Geometrie."""
|
||||||
|
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)
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
"""Kreuzungsflächen und die Fahrbahn als Ganzes."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from shapely.geometry import Point
|
||||||
|
|
||||||
|
from leonida.roads import Edge, Graph, Node
|
||||||
|
from leonida.section import FILLET, carriageway_area, junction_area
|
||||||
|
|
||||||
|
|
||||||
|
def kreuzung(klass: str = "hauptstrasse", arm: float = 60.0) -> Graph:
|
||||||
|
return Graph(
|
||||||
|
nodes=[Node((0.0, 0.0)), Node((arm, 0.0)), Node((-arm, 0.0)), Node((0.0, arm)), Node((0.0, -arm))],
|
||||||
|
edges=[Edge(0, i, [(0.0, 0.0), p], 18.0, klass)
|
||||||
|
for i, p in ((1, (arm, 0.0)), (2, (-arm, 0.0)), (3, (0.0, arm)), (4, (0.0, -arm)))])
|
||||||
|
|
||||||
|
|
||||||
|
def test_kreuzung_deckt_den_knoten_ab():
|
||||||
|
area = junction_area(kreuzung(), 0)
|
||||||
|
assert area.contains(Point(0, 0))
|
||||||
|
|
||||||
|
|
||||||
|
def test_einspringende_ecke_ist_gerundet():
|
||||||
|
area = junction_area(kreuzung(), 0)
|
||||||
|
# Ohne Rundung endete die Fläche an der Ecke (6, 6) scharf - halbe Fahrbahn in beide
|
||||||
|
# Richtungen. Mit 6 m Radius ist die einspringende Ecke ausgefüllt.
|
||||||
|
assert area.contains(Point(6.5, 6.5))
|
||||||
|
assert FILLET == 6.0
|
||||||
|
|
||||||
|
|
||||||
|
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")])
|
||||||
|
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())
|
||||||
|
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"
|
||||||
Reference in new issue
Block a user