from shapely.geometry import LineString, Point, box from shapely.ops import unary_union from leonida.park import Street, World, apply_street from leonida.roads import MIN_EDGE, Edge, Graph, Node STREET = Street(start=(150.0, 660.0), end=(150.0, 320.0), width=12.0, remove_buildings=((155.0, 615.0),), alley_cut=((90.0, 639.0), (110.0, 639.0), (110.0, 341.0), (90.0, 341.0))) def world(): """Querstraßen bei y 640–660 und 320–340, dazwischen eine Gasse bei x 95–105.""" removed = box(140, 600, 170, 630) return World( land=[box(-100, 200, 300, 800)], surfaces=[(box(0, 340, 95, 640), "urban"), (box(105, 340, 200, 640).difference(removed), "urban")], roads=[(unary_union([box(0, 640, 200, 660), box(0, 320, 200, 340), box(95, 340, 105, 640)]), False)], graph=Graph(nodes=[Node((0.0, 650.0)), Node((200.0, 650.0)), Node((0.0, 330.0)), Node((200.0, 330.0)), Node((100.0, 650.0)), Node((100.0, 330.0))], edges=[Edge(0, 1, [(0.0, 650.0), (200.0, 650.0)], 12.0, "wohnstrasse"), Edge(2, 3, [(0.0, 330.0), (200.0, 330.0)], 12.0, "wohnstrasse"), Edge(4, 5, [(100.0, 650.0), (100.0, 330.0)], 6.0, "gasse")]), buildings=[(removed, {"height": 20.9, "source": "district"}, False), (box(20, 400, 60, 450), {"height": 10.8, "source": "district"}, False), (box(84, 450, 93, 470), {"height": 8.0, "source": "district"}, False)], palms=[(150.0, 500.0), (60.0, 500.0), (140.0, 500.0)], ) def on_road(result, x, y): return any(poly.contains(Point(x, y)) for poly, _ in result.roads) def test_buildings_under_the_street_go_and_the_others_stay(): result = apply_street(world(), STREET) assert [b[0].bounds for b in result.buildings] == [(20.0, 400.0, 60.0, 450.0), (84.0, 450.0, 93.0, 470.0)] def test_alley_is_cut_and_the_straight_street_joins_both_cross_streets(): result = apply_street(world(), STREET) assert not on_road(result, 100, 500) assert on_road(result, 150, 500) and not on_road(result, 143, 500) assert on_road(result, 100, 650) and on_road(result, 100, 330) assert on_road(result, 150, 650) and on_road(result, 150, 330) def test_alley_centerline_shrinks_to_its_stubs_and_the_street_gets_its_own(): """Früher (MIN_CENTERLINE=15) verschwand die geschnittene Gasse ganz, weil ihre beiden Enden nur 11 m lang sind. cut_edges() kennt nur noch MIN_EDGE (5 m): der alte Grund - Laternen wählten ihre Straßenseite über den Listenindex und liefen an der Kreuzung sonst ins Leere - entfällt, sobald eine spätere Aufgabe die Seite aus der Geometrie bestimmt statt aus der Liste. Die beiden 11-m-Enden bleiben deshalb jetzt als eigene, kurze Kanten stehen. """ result = apply_street(world(), STREET) lines = [LineString(e.points) for e in result.graph.edges] assert sorted(round(line.length) for line in lines if line.length < 15) == [11, 11] assert all(line.distance(Point(100, 500)) > 40 for line in lines) assert any(e.points == [tuple(STREET.start), tuple(STREET.end)] for e in result.graph.edges) assert all(line.length >= MIN_EDGE for line in lines) def test_freed_ground_becomes_urban_but_not_under_the_street_buildings_or_old_urban(): result = apply_street(world(), STREET) new = [poly for poly, kind in result.surfaces[2:] if kind == "urban"] assert any(poly.contains(Point(100, 500)) for poly in new) # alte Gasse assert any(poly.contains(Point(165, 615)) for poly in new) # alter Grundriss neben der Straße assert not any(poly.contains(Point(150, 615)) for poly in new) # unter der neuen Straße assert not any(poly.contains(Point(91, 460)) for poly in new) # stehendes Gebäude assert not any(poly.contains(Point(92, 500)) for poly in new) # war schon Stadtfläche def test_palms_on_the_new_street_go(): assert apply_street(world(), STREET).palms == [(60.0, 500.0), (140.0, 500.0)]