fix(extract): Chunk-Ausgabe lauf-stabil, Eckenprüfung in beide Richtungen
emit.py normalisiert jede ausgegebene Fläche und sortiert die Listen nach der Ausgabestruktur selbst. unary_union() in apply_street() gab das kartenweite Straßenpolygon mit anderem Ringstartpunkt und an anderer Listenstelle zurück und änderte damit 88 Chunkdateien, in denen sich geometrisch nichts bewegt hatte (symmetrische Differenz 0,00 m²). Belagsarten und Mittellinien behalten ihre Reihenfolge: overview.py malt die Beläge in Listenreihenfolge übereinander, und vergeLampPoints() hängt an Reihenfolge und Laufrichtung der Mittellinien — Sortieren verschöbe dort 1.848 von 3.921 Laternen auf die andere Straßenseite. check_corners() misst gegen den Straßenrand statt gegen die Straßenfläche. Alle vier Ecken liegen in der Fahrbahn, ihr Flächenabstand ist 0,000 m; die alte Prüfung schlug deshalb nur an, wenn eine Straße sich zurückzieht. Eine nach innen verbreiterte Straße hätte apply_park() still auf das veraltete Viereck zurückgeschnitten. Die Parkwege werden am Parkviereck abgeschnitten — die runden Kappen von LineString.buffer() lagen mit 95,96 m² darüber hinaus, rund 41 m² davon sichtbar als Betonzungen auf den umgebenden Belägen. load_elements() prüft den OSM-Abzug so streng wie die Parkdatei, mit Dateiname statt nacktem KeyError. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a3504df43d
commit
3c74202b5e
10 files changed
+333
-102
No files matched your search
@@ -1,7 +1,8 @@
|
||||
from shapely.geometry import Polygon
|
||||
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)
|
||||
@@ -48,6 +49,56 @@ def test_polygon_holes_survive_into_the_payload():
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user