Files
gta6/tools/extract/tests/test_section.py
T
knsoandClaude Opus 5 322693c892 feat(extract): Bordstein an jeder Straße, auch an der Gasse
Der Bordstein ist keine Ausstattung wie ein Gehweg, sondern die Kante, an
der der Asphalt endet — und die hat auch eine Hinterhofgasse. _kerb_lines()
schneidet darum nur noch die Brücken aus dem Fahrbahnrand: Dort steckt die
Fahrbahn im Deck, eine Kante stünde in der Luft. Gehweg und Markierung
bleiben der Gasse weiterhin fern.

Das Feld kerb im Querschnitt entfällt. Es steuerte zuletzt nur noch Gehweg
und Markierung und war aus walk > 0 ableitbar; kerb_only heißt deshalb
jetzt walk_only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:43:09 +02:00

93 lines
4.8 KiB
Python

"""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, to_multipolygon, trimmed_line
def strecke(klass: str, length: float = 100.0) -> Graph:
return Graph(nodes=[Node((0.0, 0.0)), Node((length, 0.0))],
edges=[Edge(0, 1, [(0.0, 0.0), (length, 0.0)], CLASS_WIDTH[klass], klass)])
def test_fahrbahn_und_zwei_gehwege_ergeben_den_korridor():
for name, section in SECTION.items():
assert section.carriageway + 2 * section.walk == section.corridor, name
assert section.corridor == CLASS_WIDTH[name], name
def test_spuren_und_parkspuren_fuellen_die_fahrbahn():
for name, section in SECTION.items():
if name == "gasse":
continue # eine Fahrgasse ohne Spureinteilung, breiter als eine Regelspur
assert section.lanes * LANE + 2 * section.parking == section.carriageway, name
def test_nur_die_gasse_hat_keinen_bordstein():
assert [name for name, s in SECTION.items() if not s.walk] == ["gasse"]
def test_band_ist_so_breit_wie_die_fahrbahn():
band = ribbon(strecke("hauptstrasse"), 0)
x0, y0, x1, y1 = band.bounds
assert round(y1 - y0, 6) == SECTION["hauptstrasse"].carriageway
assert round(x1 - x0, 6) == 100.0, "freie Enden werden nicht zurückgeschnitten"
def test_an_einer_kreuzung_wird_zurueckgeschnitten():
graph = Graph(
nodes=[Node((0.0, 0.0)), Node((100.0, 0.0)), Node((100.0, 60.0)), Node((160.0, 0.0))],
edges=[Edge(0, 1, [(0.0, 0.0), (100.0, 0.0)], 12.0, "wohnstrasse"),
Edge(1, 2, [(100.0, 0.0), (100.0, 60.0)], 12.0, "wohnstrasse"),
Edge(1, 3, [(100.0, 0.0), (160.0, 0.0)], 12.0, "wohnstrasse")])
assert graph.degree(1) == 3
r = setback(graph, 1)
assert r == SECTION["wohnstrasse"].carriageway / 2 + 2.0
line = trimmed_line(graph, 0)
assert round(line.length, 6) == round(100.0 - r, 6)
assert line.coords[0] == (0.0, 0.0), "das freie Ende bleibt, nur das Kreuzungsende weicht"
def test_kante_ganz_innerhalb_der_kreuzung_liefert_kein_band():
# Zwei Kreuzungen (Grad 3) nur 3 m auseinander - genau der Fall, den CONTRACT_MAX in roads.py
# sonst zusammenzieht, hier aber synthetisch stehen gelassen, um trimmed_line() an diesem
# Rand zu prüfen. Der Auftrag hatte hier nur ein Kreuz aus vier Knoten mit je einem Arm an
# den Knoten 0 und 1 - das ergibt Grad 2, keine Kreuzung, und die Kante würde nicht
# zurückgeschnitten. Erst der zweite Arm an jedem der beiden Knoten macht sie zu echten
# Kreuzungen (Grad 3), deren Rückschnitt die 3 m kurze Verbindungskante ganz auffrisst.
graph = Graph(nodes=[Node((0.0, 0.0)), Node((3.0, 0.0)),
Node((0.0, 50.0)), Node((0.0, -50.0)),
Node((3.0, 50.0)), Node((3.0, -50.0))],
edges=[Edge(0, 1, [(0.0, 0.0), (3.0, 0.0)], 18.0, "hauptstrasse"),
Edge(0, 2, [(0.0, 0.0), (0.0, 50.0)], 18.0, "hauptstrasse"),
Edge(0, 3, [(0.0, 0.0), (0.0, -50.0)], 18.0, "hauptstrasse"),
Edge(1, 4, [(3.0, 0.0), (3.0, 50.0)], 18.0, "hauptstrasse"),
Edge(1, 5, [(3.0, 0.0), (3.0, -50.0)], 18.0, "hauptstrasse")])
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