fix(extract): Gehweglücke an gewöhnlichen Kreuzungsecken geschlossen

Befund 7 (seit Task 7 geparkt): carriageway_area() rundet die einspringenden
Ecken einer Kreuzung mit FILLET=6,0 (Schließen: aufblasen, dann wieder
schrumpfen), die Gehwegquelle in _walk_source() blieb seit Fixrunde 5
dagegen ungerundet. Eine reine Dilatation der Quelle um WALK_MAX=3,0
zeichnet eine Reflexecke nicht nach - zwischen der gerundeten Fahrbahn und
dem ungerundeten Gehweg blieb an jeder gewöhnlichen Kreuzung (mehrere Arme
mit Bordstein, keine Gasse beteiligt) eine kleine Lücke, zu klein für
LOT_MIN und damit ein unsichtbares Loch im Boden statt einer Restfläche.
Gemessen 0,26-2,2 m² je Ecke bei 690 Kreuzungen.

junction_area() (section.py) bekommt einen kerb_only-Schalter und rundet
damit wahlweise nur den Kreuzungsanteil der bordsteintragenden Arme;
_walk_source() ruft sie jetzt statt des rohen junction_arms(kerb_only=True)
auf. Der Unterschied zu Fixrunde 4 (die dieselbe Rundung auf ALLE Arme
anwandte und damit Gassen mit rundete): Eine Gasse liefert gar keinen Arm
in die zu schließende Fläche, kann also strukturell keine Nische mit einem
Nachbararm bilden - unabhängig vom Winkel.

Nachgemessen an einer T-Kreuzung aus drei Wohnstraßen (derselbe großzügige
Korridor wie in den bestehenden Gassen-Tests, carriageway_area()+3 m): vor
der Korrektur 0,256 m² Lücke je der beiden Ecken (0,5114 m² zusammen), exakt
im berichteten Bereich; danach 0,0 m². Eine zweite, an sich schon bei
Fixrunde 5 bestehende Kontrolle: Gasse in einem engen Winkel (10-30°)
zwischen zwei Wohnstraßen-Armen - der Gehweg bleibt für jeden geprüften
Winkel bei 0,0 m² im Gassenkorridor. Alle bestehenden Gassen-Sicherungen in
test_street.py (fünf Fixrunden Vorarbeit) bleiben grün.
This commit is contained in:
knso committed 2026-09-18 07:04:30 +02:00
1 parent 77d53fb170
commit 2605f95951
3 files changed
+89 -15

No files matched your search

+59
View File
@@ -1,6 +1,8 @@
"""Die Mulde: Löcher in der Bodenplatte, Bordsteinlinien, Gehwegflächen, Restflächen."""
from __future__ import annotations
import math
from shapely.geometry import LineString, Point, box
from shapely.ops import unary_union
@@ -227,3 +229,60 @@ def test_gehwegrest_an_kreuzung_mit_mehreren_gassen_bleibt_aus():
# von rund WALK_MAX + halbe Gassenbreite (~6 m Ausdehnung); 1,0 m² lässt Spielraum für
# künftige, geometrisch bedingte Rundungsreste, ohne die Kernaussage zu verwässern.
assert rest.area < 1.0, "die Nische zwischen zwei Gassenstummeln bleibt frei von Gehweg"
def test_gehweg_schliesst_die_luecke_an_gewoehnlicher_kreuzungsecke():
"""Befund 7 der Fixwelle: An einer gewöhnlichen Kreuzung (mehrere Arme mit Bordstein, keine
Gasse beteiligt) klaffte zwischen der gerundeten Fahrbahnecke (carriageway_area() rundet mit
FILLET=6,0) und der ungerundeten Gehwegquelle eine kleine Lücke - eine reine Dilatation der
Quelle um WALK_MAX=3,0 zeichnet eine Reflexecke nicht nach. Am echten Netz 0,26-2,2 m² je
Ecke bei 690 Kreuzungen; hier reproduziert an einer T-Kreuzung aus drei Wohnstraßen-Armen,
mit demselben großzügigen Korridor wie in den Gassen-Tests oben (carriageway_area() + 3 m).
Vor der Korrektur maß jede der beiden Ecken exakt 0,256 m² Lücke (52 gerundete Doppelpunkte
davon zusammen 0,5114 m²) - innerhalb des berichteten Bereichs.
_walk_source() rundet den Kreuzungsanteil jetzt mit derselben Schließung wie carriageway_area()
(junction_area(kerb_only=True)), sodass Fahrbahn- und Gehwegquelle an der Ecke wieder
zueinander passen.
"""
graph = Graph(
nodes=[Node((0.0, 0.0)), Node((-50.0, 0.0)), Node((50.0, 0.0)), Node((0.0, -50.0))],
edges=[Edge(0, 1, [(0.0, 0.0), (-50.0, 0.0)], SECTION["wohnstrasse"].corridor, "wohnstrasse"),
Edge(0, 2, [(0.0, 0.0), (50.0, 0.0)], SECTION["wohnstrasse"].corridor, "wohnstrasse"),
Edge(0, 3, [(0.0, 0.0), (0.0, -50.0)], SECTION["wohnstrasse"].corridor, "wohnstrasse")])
corridor = carriageway_area(graph).buffer(3.0, join_style="round")
street = build_street(graph, corridor, bridges=[])
# Der rohe Rest (vor dem LOT_MIN-Filter aus _lots()): An einer gewöhnlichen Ecke darf davon
# nichts übrig bleiben, sonst wird die Lücke später als zu kleines Reststück verworfen und
# bleibt ein unsichtbares Loch im Boden.
roh_rest = corridor.difference(street.carriageway).difference(street.sidewalk)
ecken = Point(0, 0).buffer(15.0)
luecke = sum(g.area for g in getattr(roh_rest, "geoms", [roh_rest])
if not g.is_empty and g.intersects(ecken) and g.area < LOT_MIN)
assert luecke < 0.05, f"{luecke:.4f} m² Lücke an den Kreuzungsecken, sollte geschlossen sein"
def test_gehweg_waechst_auch_bei_engem_winkel_nicht_in_die_gasse():
"""Gegenprobe zu obigem Befund-7-Fix, am schärfsten denkbaren Fall: Die Rundung des
Kreuzungsanteils darf nicht wieder (wie in Fixrunde 4) über eine benachbarte Gasse
hinwegreichen, auch wenn die Gasse in einem enges Winkel zwischen zwei Armen mit Bordstein
liegt - der Fall, in dem eine Schließung am ehesten eine Nische neben der Gasse mitfüllen
könnte. Zwei Wohnstraßen-Arme bei 0° und 40°, eine Gasse dazwischen, bei 10° bis 30° in 5°-
Schritten durchprobiert: In jedem Fall bleibt der Gehweg außerhalb des Gassenkorridors, weil
die Gasse selbst nie einen Arm in die zu schließende Fläche liefert (kerb_only=True).
"""
for grad in (10.0, 15.0, 20.0, 25.0, 30.0):
rad = math.radians(grad)
a = (60.0, 0.0)
b = (60.0 * math.cos(math.radians(40.0)), 60.0 * math.sin(math.radians(40.0)))
g = (60.0 * math.cos(rad), 60.0 * math.sin(rad))
graph = Graph(
nodes=[Node((0.0, 0.0)), Node(a), Node(b), Node(g)],
edges=[Edge(0, 1, [(0.0, 0.0), a], SECTION["wohnstrasse"].corridor, "wohnstrasse"),
Edge(0, 2, [(0.0, 0.0), b], SECTION["wohnstrasse"].corridor, "wohnstrasse"),
Edge(0, 3, [(0.0, 0.0), g], SECTION["gasse"].corridor, "gasse")])
corridor = carriageway_area(graph).buffer(3.0, join_style="round")
street = build_street(graph, corridor, bridges=[])
gassen_korridor = LineString([(0.0, 0.0), g]).buffer(SECTION["gasse"].corridor / 2, cap_style="flat")
leck = street.sidewalk.intersection(gassen_korridor).area
assert leck < 0.01, f"{leck:.4f} m² Gehweg im Gassenkorridor bei {grad}° Gassenwinkel"