feat(extract): Pipeline baut Straßen aus dem Graphen

run.py baut jetzt aus dem Straßengraphen (Tasks 3-11) statt aus den nachgezeichneten
Straßenrändern: Fahrbahn, Bordstein, Gehweg und Restflächen aus build_street(),
amerikanische Markierungen aus markings(). fit_to_carriageway() bringt Bodenplatte und
Beläge auf dasselbe Loch wie die Fahrbahn und hält Palmen dabei von ihr fern - nach
derselben Regel, mit der apply_street() sie schon von der Jefferson Avenue fernhält.

split_by_chunk_all() bekommt dafür eine parametrisierbare Splittergrenze: Ein Strich der
gestrichelten Linien ist 3 m x 10 cm = 0,3 m² und wäre an der bisherigen 0,5-m²-Grenze für
Flächen verschwunden. Markierungen laufen deshalb über den neuen Helfer chunk_markings()
mit eigener, kleinerer Grenze; alle anderen Aufrufer bleiben bei 0,5 m².

Am echten Netz gemessen: 1.537 Knoten, 1.790 Kanten, 830 Bordsteinstücke, 171
Restflächen, 22.394 Markierungen, 56 Palmen an der Fahrbahn entfernt. Fahrbahn-, Gehweg-
und Restflächen sowie die Bordsteinlänge treffen die vorab am echten Netz gemessenen
Erwartungswerte auf 0,03-0,3 % genau.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-17 19:43:16 +02:00
1 parent 892558fc50
commit 2201be37bd
3 files changed
+181 -15

No files matched your search

@@ -0,0 +1,73 @@
"""Die Reihenfolge, in der die Straßen entstehen — ohne den vollen Lauf."""
from __future__ import annotations
from shapely.geometry import box
from shapely.ops import unary_union
from leonida.emit import chunk_box_of
from leonida.park import World
from leonida.roads import Edge, Graph, Node
from leonida.street import build_street, punch
from run import chunk_markings, fit_to_carriageway
def _gerade_wohnstrasse():
"""100 m Wohnstraße entlang y = 0: Fahrbahn 7 m breit, also von y = -3,5 bis 3,5."""
graph = Graph(nodes=[Node((0.0, 0.0)), Node((100.0, 0.0))],
edges=[Edge(0, 1, [(0.0, 0.0), (100.0, 0.0)], 12.0, "wohnstrasse")])
return graph, build_street(graph, box(0, -6, 100, 6), bridges=[])
def _welt(graph, *, surfaces=(), palms=()):
return World(land=[box(-20, -50, 120, 50)], surfaces=list(surfaces), roads=[], graph=graph,
buildings=[], palms=list(palms))
def test_boden_und_belag_bekommen_dasselbe_loch():
"""Vergisst run.py eine der beiden Schichten, liegt dort ein Belag über der Mulde oder die
Bodenplatte schließt sie."""
graph, street = _gerade_wohnstrasse()
platte = box(-20, -50, 120, 50)
welt = _welt(graph, surfaces=[(box(-20, -50, 50, 50), "urban"), (box(50, -50, 120, 50), "park")])
out = fit_to_carriageway(welt, street.carriageway)
boden = unary_union(out.land)
assert boden.intersection(street.carriageway).area < 0.01, "die Fahrbahn liegt im Loch"
loch = street.carriageway.intersection(platte).area
assert abs(platte.area - boden.area - loch) < 0.01, "das Loch ist nicht größer als die Fahrbahn"
for kind in ("urban", "park"):
belag = unary_union([poly for poly, k in out.surfaces if k == kind])
assert belag.intersection(street.carriageway).area < 0.01, f"kein Belag {kind} über der Mulde"
assert abs(unary_union([poly for poly, _ in out.surfaces]).area - boden.area) < 0.01, "dasselbe Loch"
def test_gehweg_liegt_nicht_im_loch():
graph, street = _gerade_wohnstrasse()
land = unary_union(punch([box(-20, -50, 120, 50)], street.carriageway))
assert street.sidewalk.difference(land).area < 0.5, "der Gehweg liegt auf der Bodenplatte"
def test_keine_palme_auf_oder_dicht_an_der_fahrbahn():
"""Entwurf, Ablauf Schritt 7: Palmen werden nach dem Straßenbau neu gesetzt. Am echten Netz
standen ohne diesen Schritt 24 Palmen auf der Fahrbahn und 32 weitere näher als PALM_KERB."""
graph, street = _gerade_wohnstrasse()
welt = _welt(graph, palms=[(50.0, 0.0), (50.0, 4.5), (50.0, 5.5)])
out = fit_to_carriageway(welt, street.carriageway)
assert out.palms == [(50.0, 5.5)], "auf der Fahrbahn und 1 m daneben weg, 2 m daneben bleibt"
def test_striche_ueberleben_den_zuschnitt():
"""Ein Strich der gestrichelten Mitte ist 3 m × 10 cm = 0,3 m². Mit der Splittergrenze der
Flächen (0,5 m²) fiel am echten Netz jeder einzelne der 4.363 Striche weg."""
zelle = chunk_box_of(0, 0)
x, y = zelle.centroid.x, zelle.centroid.y
stuecke = chunk_markings([(box(x, y, x + 3.0, y + 0.1), "gelb_gestrichelt")], 0, 0)
assert [art for _, art in stuecke] == ["gelb_gestrichelt"]
assert abs(stuecke[0][0].area - 0.3) < 1e-6
def test_strich_ueber_der_chunkgrenze_wird_geteilt_nicht_verworfen():
zelle = chunk_box_of(0, 0)
grenze, y = zelle.bounds[2], zelle.centroid.y # rechte Kante von Chunk (0, 0)
strich = [(box(grenze - 1.0, y, grenze + 2.0, y + 0.1), "weiss_gestrichelt")]
assert [round(poly.area, 6) for poly, _ in chunk_markings(strich, 0, 0)] == [0.1]
assert [round(poly.area, 6) for poly, _ in chunk_markings(strich, 1, 0)] == [0.2]