"""Die Mulde: Löcher in der Bodenplatte, Bordsteinlinien, Gehwegflächen, Restflächen.""" from __future__ import annotations from shapely.geometry import LineString, Point, box from shapely.ops import unary_union from leonida.roads import Edge, Graph, Node from leonida.section import SECTION from leonida.street import LOT_MIN, build_street, punch def strasse(klass: str = "wohnstrasse") -> Graph: return Graph(nodes=[Node((0.0, 0.0)), Node((100.0, 0.0))], edges=[Edge(0, 1, [(0.0, 0.0), (100.0, 0.0)], SECTION[klass].corridor, klass)]) def korridor(klass: str = "wohnstrasse"): halb = SECTION[klass].corridor / 2 return box(0, -halb, 100, halb) def test_gehweg_fuellt_genau_den_rest_des_korridors(): street = build_street(strasse(), korridor(), bridges=[]) section = SECTION["wohnstrasse"] # 100 m lang, zwei Streifen von 2,5 m. Die Enden sind offen, deshalb genau die Fläche. assert round(street.sidewalk.area) == round(100 * 2 * section.walk) assert street.sidewalk.intersection(street.carriageway).area < 0.01 assert street.sidewalk.geom_type == "MultiPolygon" def test_gasse_bekommt_weder_gehweg_noch_bordstein(): street = build_street(strasse("gasse"), korridor("gasse"), bridges=[]) assert street.sidewalk.area < 0.01 assert street.kerb == [] # Der Grenzfall für die MultiPolygon-Zusage: ein leeres Ergebnis käme ohne Normalisierung # als leeres Polygon zurück, nicht als leeres MultiPolygon (wie carriageway_area() es seit # Task 6 zusagt) - Task 10/12 iterieren über .geoms und würden daran brechen. assert street.sidewalk.geom_type == "MultiPolygon" def test_bordstein_laeuft_entlang_der_fahrbahnkante(): street = build_street(strasse(), korridor(), bridges=[]) gesamt = sum(line.length for line in street.kerb) # Zwei Längsseiten von 100 m; die kurzen Stirnseiten sind mit 7 m kaum der Rede wert. assert 190 < gesamt < 220 def test_bruecken_bekommen_keinen_bordstein(): bridge = box(40, -10, 60, 10) street = build_street(strasse(), korridor(), bridges=[bridge]) for line in street.kerb: assert not bridge.buffer(-0.5).intersects(line), "über der Brücke bleibt die Kante frei" def test_loch_hat_genau_die_form_der_fahrbahn(): platte = box(-50, -50, 150, 50) street = build_street(strasse(), korridor(), bridges=[]) rest = punch([platte], street.carriageway) assert len(rest) == 1 assert not rest[0].contains(Point(50, 0)), "die Fahrbahnmitte ist ausgeschnitten" assert rest[0].contains(Point(50, 20)), "daneben bleibt die Platte" assert abs(unary_union(rest).area + street.carriageway.area - platte.area) < 1.0 def test_kleine_reststuecke_fallen_weg_grosse_bleiben(): # Ein Korridor mit einer 30 x 30 m großen Ausbuchtung (ein Parkplatz) und einem Splitter. # Der Splitter braucht eine Lücke zum Korridorrand: Läge er wie die Ausbuchtung direkt an # y = 6 an, verschmölze schon unary_union() ihn mit ihr, weil beide dieselbe durchgehende # Kante berühren - dann bliebe für LOT_MIN unten gar nichts mehr zu filtern übrig. weit = unary_union([korridor(), box(40, 6, 70, 36), box(10, 8, 12, 9)]) street = build_street(strasse(), weit, bridges=[]) assert all(lot.area >= LOT_MIN for lot in street.lots) assert any(lot.area > 500 for lot in street.lots), "der Parkplatz bleibt" # Nur die Ausbuchtung (30x30 minus den 0,5 m Gehwegsaum an ihrer Unterkante = 885 m²) bleibt. # Die beiden Seitenstreifen des Basiskorridors (je 250 m²) sind jetzt korrekt als Gehweg # erkannt, nicht mehr als zusätzliche Restfläche gezählt - genau der Fehler aus Fixrunde 1, # Punkt 1 (_lots() zog vorher nur die Fahrbahn ab, nicht auch den Gehwegstreifen). assert len(street.lots) == 1, "der 2 m² große Splitter ist herausgefallen, die Seitenstreifen sind Gehweg" def test_gerade_strasse_ohne_ausbuchtung_hat_keine_restflaeche(): # Fixrunde 1, Punkt 1: "Korridor minus Fahrbahn" allein ist bei jeder Klasse wörtlich der # Gehweg (SECTION garantiert carriageway + 2*walk == corridor). Ohne den Gehwegabzug in # _lots() wären die beiden 2,5-m-Seitenstreifen (je 250 m², ab 80 m Länge über LOT_MIN) # eine zweite, asphaltfarbene Restfläche genau dort, wo schon der Gehweg liegt. street = build_street(strasse(), korridor(), bridges=[]) assert street.lots == [] def test_bordstein_frei_an_kurzer_gasseneinmuendung(): # Fixrunde 1, Punkt 2, mit einer nachgerechneten Korrektur der Gassenlänge (siehe Bericht): # Bei der vom Auftrag genannten 30 m langen Gasse liefert ribbon() *kein* None (Setback 5,5 m, # 30-5,5=24,5 >= 1,0) - ribbon() schneidet dort ganz regulär zurück, und der alte Ausschluss # deckt die Gasse bereits vollständig ab. Der Fehler braucht eine Gasse kürzer als # setback+1=6,5 m: Erst dann liefert trimmed_line() None, der alte Code fügt für diese Kante # überhaupt keinen Ausschluss ein (der Walrus-Filter "is not None" schlägt fehl), und der # Bordstein läuft quer über die Einmündung, statt an ihr abgesenkt zu sein. graph = Graph(nodes=[Node((0.0, 0.0)), Node((50.0, 0.0)), Node((100.0, 0.0)), Node((50.0, -5.0))], edges=[Edge(0, 1, [(0.0, 0.0), (50.0, 0.0)], SECTION["wohnstrasse"].corridor, "wohnstrasse"), Edge(1, 2, [(50.0, 0.0), (100.0, 0.0)], SECTION["wohnstrasse"].corridor, "wohnstrasse"), Edge(1, 3, [(50.0, 0.0), (50.0, -5.0)], SECTION["gasse"].corridor, "gasse")]) assert graph.degree(1) == 3 street = build_street(graph, box(-20, -40, 120, 20), bridges=[]) gassenkorridor = LineString([(50.0, 0.0), (50.0, -5.0)]).buffer(SECTION["gasse"].carriageway / 2) for line in street.kerb: assert not gassenkorridor.intersects(line), "die Zufahrt bleibt frei, kein Bordstein quer davor" def test_pflasterflaeche_neben_der_gasse_ist_restflaeche_kein_gehweg(): # Fixrunde 2: die Gasse hat laut SECTION überhaupt keinen Gehweg (walk=0.0). Eine echte, # 3 m tiefe, 100 m lange Pflasterfläche direkt neben ihr - z.B. ein durchgehender Hofstreifen, # den die Kartierung mit zur Straßenfläche zählt - darf nicht zu Gehweg werden, nur weil # WALK_MAX=3.0 dort zufällig genau hinreicht. Sie muss als Restfläche erscheinen. pflaster = box(0, 3, 100, 6) weit = unary_union([korridor("gasse"), pflaster]) street = build_street(strasse("gasse"), weit, bridges=[]) assert street.sidewalk.area < 0.01, "die Gasse bekommt so wenig Gehweg wie sonst: keinen" assert any(lot.area > 250 for lot in street.lots), "die Pflasterfläche bleibt als Restfläche" def test_fahrbahn_gehweg_und_rest_ergeben_wieder_den_korridor(): # Die drei Teile dürfen sich weder überlappen noch eine Lücke lassen - unabhängig vom # LOT_MIN-Filter in street.lots prüfen wir hier den *rohen* Rest (vor dem Filter), denn nur # der ergänzt Fahrbahn und Gehweg exakt wieder zum Korridor. weit = unary_union([korridor(), box(40, 6, 70, 36), box(10, 8, 12, 9)]) street = build_street(strasse(), weit, bridges=[]) roh = weit.difference(street.carriageway).difference(street.sidewalk) assert street.carriageway.intersection(street.sidewalk).area < 1e-6 assert street.carriageway.intersection(roh).area < 1e-6 assert street.sidewalk.intersection(roh).area < 1e-6 bilanz = street.carriageway.area + street.sidewalk.area + roh.area assert abs(bilanz - weit.area) < 1e-6