from shapely.geometry import LineString, 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=[], roads=[], centerlines=[], 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=[], roads=[], centerlines=[], buildings=[], palms=[]) assert set(payload) == {"chunk", "origin", "land", "surfaces", "roads", "centerlines", "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=[], roads=[], centerlines=[], 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=[], roads=[], centerlines=[], 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")], roads=[(a, False), (b, True)], centerlines=[], 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")], roads=[(b, True), (a_gedreht, False)], centerlines=[], 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")], roads=[], centerlines=[], buildings=[], palms=[]) assert [s["kind"] for s in payload["surfaces"]] == ["park", "field"] def test_centerlines_keep_their_order_and_direction(): """Die zweite Gegenprobe: Mittellinien werden bewusst nicht sortiert und nicht normalisiert. vergeLampPoints() in src/world/props.js setzt jede Laterne abwechselnd links und rechts der Fahrbahn (gerader Index links, ungerader rechts) und dreht sie in Laufrichtung der Linie. Sortieren verschob an den ausgelieferten Chunks 1.848 von 3.921 Laternen auf die andere Strassenseite; normalize() wuerde zusaetzlich jede zweite Linie umdrehen.""" spaet = LineString([(-900, 2400), (-950, 2400)]) frueh = LineString([(-990, 2490), (-960, 2490)]) payload = chunk_payload(0, 0, land=[], surfaces=[], roads=[], centerlines=[spaet, frueh], buildings=[], palms=[]) assert payload["centerlines"] == [[[100, 100], [50, 100]], [[10, 10], [40, 10]]] 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)