chunk_payload() liefert jetzt carriageway, curb, sidewalk, markings, lots, bridges und edges statt roads und centerlines - die Ausgabe fuer Task 12 (Pipeline) und Task 13/14 (Zeichnen). curb ist ein Linienzug: Bordstein hat keine eigene Flaeche, Loch und Fahrbahn teilen sich den Umriss. _line() normalisiert die Laufrichtung wie _rings() die Wicklung - anders als bei den alten centerlines duerfen curb und edges das, weil die Laternen ihre Seite seit Teil 2 aus Kantenrichtung und Gehwegpruefung waehlen, nicht mehr aus der Listenposition. Selbstpruefung zur Lauf-Stabilitaet ging ueber die zwei mitgelieferten Tests hinaus, die bridges und edges nur leer durchreichen. Ad-hoc nachgerechnet und dabei eine Luecke gefunden: Der Sortierschluessel von edges deckte nur (line, klasse) ab, nicht auch breite und fahrbahn wie bei buildings ueblich (dort alle vier Felder). Bei gleichem line+klasse, aber unterschiedlicher breite/fahrbahn, waere die Ausgabe wieder an der Listenposition haengengeblieben - in der echten Pipeline legt SECTION[klasse] beide Werte fest und kann so nicht divergieren, der Schluessel deckt jetzt trotzdem alle vier Felder ab, wie bei buildings. test_centerlines_keep_their_order_and_direction geht weg: Sie bewies die jetzt bewusst umgekehrte Zusicherung (Reihenfolge und Richtung tragen Bedeutung); centerlines selbst entfaellt. Die zwei neuen Tests aus dem Auftrag belegen das Gegenteil fuer die Nachfolgefelder. Bricht wie angekuendigt run.py (ruft chunk_payload noch mit roads=/centerlines= auf) - das repariert Task 11/12. Anders als im Auftrag angenommen bleibt die Testsuite komplett gruen (248/248): test_park_apply.py und test_park_street.py haengen an park.py's eigenem World, nicht an chunk_payload, und kein anderer Test importiert emit.py. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
142 lines
8.4 KiB
Python
142 lines
8.4 KiB
Python
from shapely.geometry import Polygon
|
|
from leonida.emit import chunk_payload, split_by_chunk, split_by_chunk_all
|
|
|
|
BIG = Polygon([(-1000, 2500), (-700, 2500), (-700, 2200), (-1000, 2200)]) # Chunk (0,0) und Nachbarn
|
|
HEIGHT = {"height": 9.0, "source": "district"}
|
|
|
|
def test_polygon_is_clipped_into_its_chunks():
|
|
parts = split_by_chunk(BIG)
|
|
assert set(parts) >= {(0, 0), (1, 0), (0, 1), (1, 1)}
|
|
assert abs(sum(p.area for p in parts.values()) - BIG.area) < 1.0
|
|
|
|
def test_coordinates_are_chunk_local_three_js():
|
|
# chunk_payload() nimmt (wie in run.py) bereits chunk-geschnittene Geometrie entgegen;
|
|
# BIG selbst überspannt vier Chunks (siehe Test oben), darum hier zuerst zuschneiden.
|
|
clipped = split_by_chunk(BIG)[(0, 0)]
|
|
payload = chunk_payload(0, 0, land=[clipped], surfaces=[], carriageway=[], curb=[], sidewalk=[],
|
|
markings=[], lots=[], bridges=[], edges=[], buildings=[], palms=[])
|
|
ring = payload["land"][0][0] # Ruling 34: [0] ist jetzt der Aussenring
|
|
assert payload["origin"] == [-1000, -2500] # three-z = -Y
|
|
assert min(x for x, _ in ring) >= -0.01
|
|
assert max(x for x, _ in ring) <= 250.01
|
|
|
|
def test_payload_matches_schema():
|
|
payload = chunk_payload(2, 3, land=[], surfaces=[], carriageway=[], curb=[], sidewalk=[], markings=[],
|
|
lots=[], bridges=[], edges=[], buildings=[], palms=[])
|
|
assert set(payload) == {"chunk", "origin", "land", "surfaces", "carriageway", "curb", "sidewalk",
|
|
"markings", "lots", "bridges", "edges", "buildings", "palms", "paths"}
|
|
assert payload["chunk"] == [2, 3]
|
|
|
|
def test_paths_are_chunk_local_rings_like_roads():
|
|
path = Polygon([(-990, 2490), (-980, 2490), (-980, 2480), (-990, 2480)])
|
|
payload = chunk_payload(0, 0, land=[], surfaces=[], carriageway=[], curb=[], sidewalk=[], markings=[],
|
|
lots=[], bridges=[], edges=[], buildings=[], palms=[], paths=[path])
|
|
(entry,) = payload["paths"]
|
|
assert set(entry) == {"poly"}
|
|
ring = entry["poly"][0]
|
|
assert (min(x for x, _ in ring), max(x for x, _ in ring)) == (10, 20)
|
|
assert (min(z for _, z in ring), max(z for _, z in ring)) == (10, 20)
|
|
|
|
def test_polygon_holes_survive_into_the_payload():
|
|
"""Ein von Strassen umschlossener Block darf nicht zu Asphalt werden. An echten Daten
|
|
gehen sonst 449.876 m2 Strassenloch und 208.657 m2 Stadtloch verloren."""
|
|
outer = Polygon([(-1000, 2250), (-800, 2250), (-800, 2450), (-1000, 2450)],
|
|
[[(-950, 2300), (-870, 2300), (-870, 2400), (-950, 2400)]])
|
|
payload = chunk_payload(0, 0, land=[outer], surfaces=[], carriageway=[], curb=[], sidewalk=[],
|
|
markings=[], lots=[], bridges=[], edges=[], buildings=[], palms=[])
|
|
assert len(payload["land"]) == 1
|
|
rings = payload["land"][0]
|
|
assert len(rings) == 2 # Aussenring und Loch
|
|
exterior, hole = rings
|
|
# Das Loch liegt innerhalb des Aussenrings, nicht daneben.
|
|
assert min(x for x, _ in exterior) < min(x for x, _ in hole)
|
|
assert max(x for x, _ in exterior) > max(x for x, _ in hole)
|
|
assert all(0 <= x <= 250 and 0 <= z <= 250 for x, z in hole)
|
|
|
|
def test_the_same_geometry_in_another_order_gives_the_same_output():
|
|
"""Die Ausgabe darf nicht am Rechenweg haengen, nur an der Geometrie.
|
|
|
|
unary_union() in park.py fasst das kartenweite Strassenpolygon neu zusammen und gibt dieselbe
|
|
Flaeche danach an anderer Listenstelle und mit anderem Ringstartpunkt zurueck. Das aenderte
|
|
88 Chunkdateien, in denen sich geometrisch nichts bewegt hatte (symmetrische Differenz der
|
|
Strassenvereinigung 0,00 m2 in allen 88). Hier steht dieselbe Welt zweimal: einmal in der
|
|
einen, einmal in der anderen Reihenfolge, und `zweite` beschreibt `a` ausserdem mit anderem
|
|
Startpunkt und anderer Wicklung."""
|
|
a = Polygon([(-990, 2490), (-980, 2490), (-980, 2480), (-990, 2480)])
|
|
b = Polygon([(-960, 2460), (-950, 2460), (-950, 2450), (-960, 2450)])
|
|
a_gedreht = Polygon([(-980, 2480), (-980, 2490), (-990, 2490), (-990, 2480)])
|
|
assert a.equals(a_gedreht) and list(a.exterior.coords) != list(a_gedreht.exterior.coords)
|
|
erste = chunk_payload(0, 0, land=[a, b], surfaces=[(a, "urban"), (b, "urban")],
|
|
carriageway=[(a, "wohnstrasse"), (b, "wohnstrasse")], curb=[], sidewalk=[],
|
|
markings=[], lots=[], bridges=[b], edges=[],
|
|
buildings=[(a, HEIGHT, False), (b, HEIGHT, True)],
|
|
palms=[(-990.0, 2490.0), (-960.0, 2460.0)], paths=[a, b])
|
|
zweite = chunk_payload(0, 0, land=[b, a_gedreht], surfaces=[(b, "urban"), (a_gedreht, "urban")],
|
|
carriageway=[(b, "wohnstrasse"), (a_gedreht, "wohnstrasse")], curb=[], sidewalk=[],
|
|
markings=[], lots=[], bridges=[b], edges=[],
|
|
buildings=[(b, HEIGHT, True), (a_gedreht, HEIGHT, False)],
|
|
palms=[(-960.0, 2460.0), (-990.0, 2490.0)], paths=[b, a_gedreht])
|
|
assert erste == zweite
|
|
|
|
|
|
def test_surfaces_keep_the_order_of_their_kinds():
|
|
"""Gegenprobe zum Sortieren: overview.py malt die Belaege in Listenreihenfolge uebereinander.
|
|
|
|
Der Parkrasen liegt unter den Feldern; sortierte man ueber die Arten hinweg, deckte der grosse
|
|
Rasen sie auf map.png wieder zu (19.573 m2 Feld allein im Flamingo Park)."""
|
|
rasen = Polygon([(-1000, 2500), (-900, 2500), (-900, 2400), (-1000, 2400)])
|
|
feld = Polygon([(-980, 2480), (-940, 2480), (-940, 2440), (-980, 2440)])
|
|
payload = chunk_payload(0, 0, land=[], surfaces=[(rasen, "park"), (feld, "field")],
|
|
carriageway=[], curb=[], sidewalk=[], markings=[], lots=[], bridges=[],
|
|
edges=[], buildings=[], palms=[])
|
|
assert [s["kind"] for s in payload["surfaces"]] == ["park", "field"]
|
|
|
|
|
|
def test_split_by_chunk_all_yields_polygons_for_a_multi_part_intersection():
|
|
"""split_by_chunk_all() ist die Variante, die run.py tatsaechlich benutzt (nicht
|
|
split_by_chunk), und hat einen MultiPolygon-Zweig, den kein anderer Test trifft: ein
|
|
Polygon, dessen Schnitt mit der Chunk-Zelle in mehrere Teile zerfaellt. Hier eine
|
|
Klammerform (Steg ausserhalb der Zelle, zwei Zinken, die je einmal hineinreichen)."""
|
|
staple = Polygon([
|
|
(-1100, 2495), (-850, 2495), (-850, 2470), (-1010, 2470),
|
|
(-1010, 2280), (-850, 2280), (-850, 2255), (-1010, 2255),
|
|
(-1100, 2255),
|
|
])
|
|
parts = list(split_by_chunk_all([staple], 0, 0))
|
|
assert len(parts) == 2
|
|
assert all(p.geom_type == "Polygon" for p in parts)
|
|
|
|
|
|
def test_neue_schichten_stehen_im_chunk():
|
|
from shapely.geometry import LineString, box
|
|
payload = chunk_payload(
|
|
0, 0,
|
|
land=[box(0, 2400, 100, 2500)], surfaces=[],
|
|
carriageway=[(box(10, 2400, 20, 2500), "wohnstrasse")],
|
|
curb=[LineString([(10, 2400), (10, 2500)])],
|
|
sidewalk=[box(20, 2400, 23, 2500)],
|
|
markings=[(box(14, 2440, 16, 2443), "gelb_gestrichelt")],
|
|
lots=[box(40, 2400, 60, 2420)],
|
|
bridges=[], edges=[(LineString([(15, 2400), (15, 2500)]), "wohnstrasse", 12.0, 7.0)],
|
|
buildings=[], palms=[])
|
|
assert payload["carriageway"][0]["klasse"] == "wohnstrasse"
|
|
assert payload["markings"][0]["art"] == "gelb_gestrichelt"
|
|
assert payload["edges"][0]["klasse"] == "wohnstrasse"
|
|
assert payload["edges"][0]["fahrbahn"] == 7.0
|
|
assert len(payload["curb"][0]) == 2, "der Bordstein ist ein Linienzug, keine Fläche"
|
|
assert "roads" not in payload and "centerlines" not in payload
|
|
|
|
|
|
def test_neue_schichten_sind_lauf_stabil():
|
|
"""Zwei Aufrufe mit vertauschter Eingabe liefern dieselbe Ausgabe (Fixrunde 3 aus Teil 1)."""
|
|
from shapely.geometry import LineString, box
|
|
a, b = box(10, 2400, 20, 2450), box(30, 2400, 40, 2450)
|
|
l1, l2 = LineString([(0, 2400), (0, 2450)]), LineString([(5, 2400), (5, 2450)])
|
|
eins = chunk_payload(0, 0, land=[], surfaces=[], carriageway=[(a, "gasse"), (b, "gasse")],
|
|
curb=[l1, l2], sidewalk=[a], markings=[(a, "zebra"), (b, "zebra")],
|
|
lots=[a, b], bridges=[], edges=[], buildings=[], palms=[])
|
|
zwei = chunk_payload(0, 0, land=[], surfaces=[], carriageway=[(b, "gasse"), (a, "gasse")],
|
|
curb=[l2, l1], sidewalk=[a], markings=[(b, "zebra"), (a, "zebra")],
|
|
lots=[b, a], bridges=[], edges=[], buildings=[], palms=[])
|
|
assert eins == zwei
|