fix(extract): Gehwegquelle nur noch aus bordsteintragenden Armstummeln, ungerundet
Zuschneiden (Fixrunde 4) war der falsche Hebel: Egal wie breit die abgezogene Zone, die Nische zwischen zwei Gassenstummeln entsteht erst durch die FILLET-Rundung in junction_area() und liegt abseits der Gassenachse - ein linearer Abzug entlang dieser Achse kann sie grundsaetzlich nicht erreichen. Die Stummelbildung aus junction_area() in einen eigenen Helfer gezogen, junction_arms(graph, node, kerb_only=False), mit einem Filter fuer Kanten ohne Bordstein. junction_area() nutzt ihn ungefiltert und rundet wie bisher - verhaelt sich dadurch identisch (913.566,7 m² in 213 Teilen am echten Netz, bit-genau nachgemessen). _walk_source() in street.py nimmt jetzt nur noch die rohen, ungerundeten Armstummel mit Bordstein (kerb_only=True) als Quelle, ohne den kerbless_zone-Abzug aus Fixrunde 4 - die Nische entsteht dadurch gar nicht erst, weil die Kreuzung ohne Rundung nur noch ueber ihre eigenen, bordsteintragenden Arme in die Quelle eingeht. Gemessen: an der Kreuzung mit drei Gassen und einem Boulevard verschwindet der Rest in der Nische vollstaendig (0,0 statt der 27,14 m² aus Fixrunde 4) - weiter als die fuer diese Runde erwartete Obergrenze von rund 6 m Ausdehnung. Kriterium 4 damit erfuellt, keine weitere Variante noetig. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
685ac666e6
commit
f2f5db94fe
3 files changed
+60
-60
No files matched your search
@@ -78,21 +78,18 @@ def ribbon(graph: Graph, k: int):
|
||||
FILLET = 6.0
|
||||
|
||||
|
||||
def junction_area(graph: Graph, node: int):
|
||||
"""Die Fläche einer Kreuzung: die Stummel aller Arme, mit gerundeten einspringenden Ecken.
|
||||
def junction_arms(graph: Graph, node: int, kerb_only: bool = False) -> list:
|
||||
"""Die Armstummel einer Kreuzung, je auf ihre eigene Fahrbahnbreite gepuffert.
|
||||
|
||||
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.
|
||||
Zweimal gebraucht (Fixrunde 5 zu Task 7): junction_area() vereinigt alle und rundet die
|
||||
einspringenden Ecken; die Gehwegquelle in street.py nimmt nur die Arme mit Bordstein, ohne
|
||||
Rundung. Deshalb hier einmal, statt die Stummelbildung zu wiederholen.
|
||||
"""
|
||||
incident = graph.incident(node)
|
||||
if not incident:
|
||||
return None
|
||||
reach = setback(graph, node)
|
||||
arms = []
|
||||
for k in incident:
|
||||
arme = []
|
||||
for k in graph.incident(node):
|
||||
edge = graph.edges[k]
|
||||
if len(edge.points) < 2:
|
||||
if len(edge.points) < 2 or (kerb_only and not SECTION[edge.klass].kerb):
|
||||
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
|
||||
@@ -101,10 +98,23 @@ def junction_area(graph: Graph, node: int):
|
||||
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)
|
||||
stumpf = substring(line, 0.0, min(reach + 1.0, line.length))
|
||||
arme.append(stumpf.buffer(SECTION[edge.klass].carriageway / 2,
|
||||
cap_style="flat", join_style="mitre", mitre_limit=1.5))
|
||||
return arme
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
if not graph.incident(node):
|
||||
return None
|
||||
reach = setback(graph, node)
|
||||
star = unary_union(junction_arms(graph, node))
|
||||
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))
|
||||
|
||||
@@ -14,7 +14,7 @@ from shapely.geometry import LineString, MultiPolygon, Polygon
|
||||
from shapely.ops import unary_union
|
||||
|
||||
from .roads import Graph
|
||||
from .section import SECTION, WALK_MAX, carriageway_area, junction_area, ribbon, to_multipolygon
|
||||
from .section import SECTION, WALK_MAX, carriageway_area, junction_arms, ribbon, to_multipolygon
|
||||
|
||||
# Die Tiefe steht hier als dokumentierter Wert; gebaut wird sie im Browser (geometry.js).
|
||||
DEPTH = 0.15
|
||||
@@ -47,26 +47,19 @@ def build_street(graph: Graph, corridor, bridges) -> Street:
|
||||
def _walk_source(graph: Graph):
|
||||
"""Die Fahrbahnteile, an denen ein Gehweg entstehen darf.
|
||||
|
||||
Alle Kanten mit Bordstein, dazu der Kreuzungsanteil jedes Knotens, an dem mindestens ein
|
||||
solcher Arm hängt - aber ohne den Gassenstummel darin (Fixrunde 4): junction_area() liefert
|
||||
die ganze Kreuzungsfläche als ein Stück, auch den Anteil der Gasse, die dort einmündet. Ohne
|
||||
den Abzug wüchse der Gehweg die volle Länge dieses Stummels (bis zu setback+1, beim Boulevard
|
||||
11 m) die Gasse hinauf, statt nur um die Ecke herum. Dieselbe schmale Fuge wie beim Bordstein
|
||||
(_kerbless_zone, extra=0,2) schneidet nur den Gassenanteil heraus; die Kreuzung selbst bleibt
|
||||
Quelle, ein kurzer Rest an der Einmündungsecke bleibt und soll bleiben - das ist die Ecke,
|
||||
nicht die Gasse. Vorher wuchs der Gehweg aus der gesamten Fahrbahn, und die Klassen ohne
|
||||
Bordstein wurden hinterher über eine Reichweitenzone wieder abgezogen — die kennt aber ihre
|
||||
Nachbarschaft nicht und nahm dabei auch den Gehweg einer Wohnstraße mit, die zufällig nah an
|
||||
einer Gasse lag. Wer von der richtigen Seite her wächst, braucht diesen zweiten Abzug nicht.
|
||||
Alle Kanten mit Bordstein, dazu an jeder Kreuzung nur die Armstummel mit Bordstein - roh,
|
||||
ohne die Eckrundung von junction_area() (Fixrunde 5): Die volle, gerundete Kreuzungsfläche
|
||||
brachte in Fixrunde 4 immer noch zu viel Gasse mit. Der Grund war nicht die Zuschnittbreite,
|
||||
sondern die Rundung selbst - sie füllt die einspringende Ecke zwischen zwei benachbarten
|
||||
Gassenstummeln zu einer Nische auf, die abseits der Gassenachse liegt und die ein linearer
|
||||
Abzug entlang dieser Achse deshalb nie erreicht. Ohne die Rundung entsteht die Nische gar
|
||||
nicht erst: Die Quelle ist exakt die Fahrbahn mit Bordstein, ungerundet, und der Gehweg
|
||||
wächst von dort WALK_MAX weit - um die Ecke herum, aber nie eine Gasse hinauf.
|
||||
"""
|
||||
teile = [band for k, edge in enumerate(graph.edges)
|
||||
if SECTION[edge.klass].kerb and (band := ribbon(graph, k)) is not None]
|
||||
ohne = _kerbless_zone(graph, 0.2)
|
||||
teile += [flaeche if ohne is None else flaeche.difference(ohne)
|
||||
for node in range(len(graph.nodes))
|
||||
if graph.degree(node) >= 3
|
||||
and any(SECTION[graph.edges[k].klass].kerb for k in graph.incident(node))
|
||||
and (flaeche := junction_area(graph, node)) is not None]
|
||||
teile += [arm for node in range(len(graph.nodes)) if graph.degree(node) >= 3
|
||||
for arm in junction_arms(graph, node, kerb_only=True)]
|
||||
return unary_union(teile) if teile else None
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user