"""Die Straße als Mulde: Loch in der Bodenplatte, Fahrbahn darunter, Bordstein dazwischen. Warum die Fahrbahn nach unten und nicht der Gehweg nach oben (Entwurf, Abschnitt "Die Mulde"): Läge der Gehweg 15 cm hoch, hätte er überall dort eine Kante ins Nichts, wo kein Haus anschließt - an jedem Park, jeder Baulücke, jedem Strandzugang. So bleibt die ganze Welt auf y = 0, und der Bordstein ist genau das, was er in Wirklichkeit ist: die Wand zwischen Gehweg und Fahrbahn. """ from __future__ import annotations from dataclasses import dataclass, field from shapely.geometry import LineString, Polygon from shapely.ops import unary_union from .roads import Graph from .section import SECTION, WALK_MAX, carriageway_area, ribbon # Die Tiefe steht hier als dokumentierter Wert; gebaut wird sie im Browser (geometry.js). DEPTH = 0.15 # Kürzere Bordsteinstücke sind Rechenreste an Kreuzungsecken, keine Kante, die jemand sieht. KERB_MIN = 2.0 # Ab hier ist ein Reststück eine Fläche (Parkplatz, Vorplatz, Rampe) und keine Zacke. LOT_MIN = 200.0 @dataclass class Street: carriageway: object kerb: list = field(default_factory=list) sidewalk: object = None lots: list = field(default_factory=list) def build_street(graph: Graph, corridor, bridges) -> Street: """Fahrbahn, Bordsteinlinien, Gehweg und Restflächen aus Graph und gemessenem Korridor.""" carriage = carriageway_area(graph) return Street( carriageway=carriage, kerb=_kerb_lines(graph, carriage, bridges), sidewalk=_sidewalk(carriage, corridor), lots=_lots(corridor, carriage), ) def _sidewalk(carriage, corridor): """Der Streifen zwischen Fahrbahnkante und Korridorrand. Ein einziger Puffer für alle Klassen reicht: Weil am Korridor beschnitten wird, bleibt bei 12 m Korridor und 7 m Fahrbahn genau der 2,5-m-Gehweg der Wohnstraße übrig, bei 18 m und 12 m genau die 3,0 m der Hauptstraße. Die Gasse füllt ihren Korridor ganz aus und bekommt dadurch von selbst keinen Gehweg. """ return carriage.buffer(WALK_MAX, join_style="round").intersection(corridor).difference(carriage) def _kerb_lines(graph: Graph, carriage, bridges) -> list[LineString]: """Die Fahrbahnkante, ohne die Stücke an Gassen und auf Brücken.""" ohne = [band.buffer(0.2) for k, edge in enumerate(graph.edges) if not SECTION[edge.klass].kerb and (band := ribbon(graph, k)) is not None] ohne += [b.buffer(0.2) for b in bridges] rand = carriage.boundary if ohne: rand = rand.difference(unary_union(ohne)) teile = getattr(rand, "geoms", [rand]) return [g for g in teile if g.geom_type == "LineString" and g.length >= KERB_MIN] def _lots(corridor, carriage) -> list[Polygon]: rest = corridor.difference(carriage) return [g for g in getattr(rest, "geoms", [rest]) if g.geom_type == "Polygon" and g.area >= LOT_MIN] def punch(polygons, carriage) -> list[Polygon]: """Die Fahrbahn aus Bodenplatte oder Belag herausschneiden.""" out = [] for poly in polygons: rest = poly.difference(carriage) out += [g for g in getattr(rest, "geoms", [rest]) if g.geom_type == "Polygon" and g.area > 0.5] return out