fix(extract): Kreuzungsstummel schmaeler wachsen lassen, GeometryCollection-Absturz beheben

_walk_source() nahm bisher die ganze junction_area() eines Knotens auf, sobald
irgendein anliegender Arm Bordstein hat - auch den Anteil, der zu einer dort
einmuendenden Gasse gehoert. Der Gehweg wuchs dadurch die volle Laenge des
Kreuzungsstummels die Gasse hinauf (bis zu setback+1, beim Boulevard 11 m)
statt nur um die Ecke. _kerbless_zone() bekommt den extra-Parameter zurueck
(_kerb_lines() und _walk_source() rufen sie beide mit 0.2 auf) und beschneidet
jetzt auch den Kreuzungsanteil der Gehwegquelle.

Gemessen: an einer T-Kreuzung (2 Gassen + 1 Wohnstrasse) verschwindet der
fehlerhafte Gassenstummel-Gehweg vollstaendig (53,11 -> 0,0 m2). An einer
4-Wege-Kreuzung mit 3 Gassen + 1 Boulevard reicht der Fix nicht aus: der Rest
in der Nische zwischen zwei Gassenstummeln schrumpft nur auf 27,14 statt der
erwarteten rund 2 m2, und seine maximale Ausdehnung von der Kreuzungsmitte
(11,14 m) liegt weit ueber der als Grenze genannten 3-4 m - dokumentiert als
bekannter, nicht ausreichend geloester Punkt, siehe Fixbericht.

Unabhaengig davon: MultiPolygon([result]) wirft TypeError, wenn result eine
GeometryCollection ist (intersection()/difference() liefern das gelegentlich
an Beruehrstellen, mit einer entarteten Nulllinie neben echten Polygonen).
Neuer Helfer to_multipolygon() in section.py filtert auf echte Polygone,
statt daran abzustuerzen; carriageway_area() und _sidewalk() nutzen ihn beide.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-17 06:52:30 +02:00
1 parent ca5710730e
commit 685ac666e6
4 files changed
+127 -28

No files matched your search

+25 -1
View File
@@ -1,8 +1,10 @@
"""Querschnitte und Fahrbahnbänder."""
from __future__ import annotations
from shapely.geometry import GeometryCollection, LineString, box
from leonida.roads import CLASS_WIDTH, Edge, Graph, Node
from leonida.section import LANE, SECTION, ribbon, setback, trimmed_line
from leonida.section import LANE, SECTION, ribbon, setback, to_multipolygon, trimmed_line
def strecke(klass: str, length: float = 100.0) -> Graph:
@@ -66,3 +68,25 @@ def test_kante_ganz_innerhalb_der_kreuzung_liefert_kein_band():
assert graph.degree(0) == 3 and graph.degree(1) == 3
assert trimmed_line(graph, 0) is None
assert ribbon(graph, 0) is None
def test_geometriesammlung_wird_zu_reinem_multipolygon():
# Fixrunde 4 zu Task 7: intersection()/difference() liefern nicht immer sauber ein
# (Multi)Polygon zurück - an einer Berührstelle kann eine GeometryCollection mit einer
# zusätzlichen entarteten Linie oder einem Punkt herauskommen (am Gehweg gemeldet).
# MultiPolygon(geoms) nimmt aber nur Polygone entgegen und wirft eine TypeError, sobald ein
# Bestandteil davon abweicht - carriageway_area() und _sidewalk() (street.py) stürzten daran
# ab. Hier direkt erzwungen (zwei Polygone plus eine entartete Nulllinie), weil sich die
# Geometriesammlung über echte Fahrbahnrechnungen trotz mehrerer Versuche nicht zuverlässig
# nachbauen ließ - GEOS bereinigt Berührstellen bei Polygon-Operationen erstaunlich robust.
entartet = GeometryCollection([box(0, 0, 10, 10), box(20, 0, 30, 10), LineString([(15, 5), (15, 5)])])
assert entartet.geom_type == "GeometryCollection"
ergebnis = to_multipolygon(entartet)
assert ergebnis.geom_type == "MultiPolygon"
assert len(ergebnis.geoms) == 2, "nur die zwei echten Flächen, die Nulllinie fällt weg"
assert round(ergebnis.area) == 200
def test_leere_geometrie_wird_zu_leerem_multipolygon():
assert to_multipolygon(GeometryCollection()).geom_type == "MultiPolygon"
assert to_multipolygon(GeometryCollection()).is_empty