diff --git a/tools/extract/leonida/emit.py b/tools/extract/leonida/emit.py index 6bc4b1a..2b26b9d 100644 --- a/tools/extract/leonida/emit.py +++ b/tools/extract/leonida/emit.py @@ -74,11 +74,18 @@ def _sorted_surfaces(entries: list[dict]) -> list[dict]: def _line(line, i: int, j: int) -> list[list[float]]: """Ein Linienzug in Chunk-Koordinaten, richtungsunabhängig festgelegt. - normalize() dreht eine Linie in eine feste Laufrichtung. Das ist hier anders als bei den - früheren centerlines erwünscht: Die Laternen wählen ihre Seite seit Teil 2 aus der - Kantenrichtung *und* prüfen gegen die Gehwegfläche, hängen also nicht mehr an der - Listenposition (siehe props.js). Damit darf die Ausgabe sortiert und normalisiert werden, - und ein Lauf erzeugt wieder Datei für Datei dasselbe. + normalize() dreht eine Linie in eine feste Laufrichtung. Normalisieren und Sortieren ist für + curb und edges nur zulässig, solange keine Laterne ihre Straßenseite aus der Listenposition + wählt, sondern aus der Geometrie - Kantenrichtung und eine Prüfung gegen die Gehwegfläche. + Bei den früheren centerlines war das nicht so, deshalb blieben sie unsortiert und + unnormalisiert. + + Eingelöst wird die Voraussetzung erst mit einer geometriebasierten Nachfolgefunktion in + props.js (vorgesehen als sidewalkLampPoints(), als Ablösung von vergeLampPoints()). Heute + liest dort noch niemand curb oder edges: vergeLampPoints() wählt die Seite weiterhin über den + geraden/ungeraden Index (Zeile 107) und läuft auf chunk.centerlines/chunk.roads. Bis zur + Ablösung ist das hier deshalb folgenlos - nicht weil die Bedingung gälte, sondern weil noch + kein Verbraucher zuhört. """ ox = SLICE["x0"] + i * CHUNK_SIZE oy = SLICE["y1"] - j * CHUNK_SIZE diff --git a/tools/extract/tests/test_emit.py b/tools/extract/tests/test_emit.py index 209a4e0..0531971 100644 --- a/tools/extract/tests/test_emit.py +++ b/tools/extract/tests/test_emit.py @@ -139,3 +139,55 @@ def test_neue_schichten_sind_lauf_stabil(): curb=[l2, l1], sidewalk=[a], markings=[(b, "zebra"), (a, "zebra")], lots=[b, a], bridges=[], edges=[], buildings=[], palms=[]) assert eins == zwei + + +def test_edges_sortierschluessel_deckt_alle_felder_ab(): + """Regressionsschutz fuer den Fund aus Fixrunde 1: Zwei Kanten mit gleicher Linie und + gleicher Klasse, aber unterschiedlicher Breite, duerfen nicht an der Eingabereihenfolge + haengen bleiben. Mit dem Schluessel (line, klasse) allein - wie im urspruenglichen + Auftragscode - loest keiner der beiden Faelle den Tie auf, sorted() bleibt dann stabil und + reicht die Eingabereihenfolge unveraendert durch; erst breite/fahrbahn im Schluessel machen + die zwei Eintraege wieder unterscheidbar und damit total geordnet.""" + from shapely.geometry import LineString + linie = LineString([(70, 2400), (70, 2450)]) + schmal = (linie, "gasse", 6.0, 6.0) + breit = (linie, "gasse", 99.0, 99.0) + eins = chunk_payload(0, 0, land=[], surfaces=[], carriageway=[], curb=[], sidewalk=[], + markings=[], lots=[], bridges=[], edges=[schmal, breit], + buildings=[], palms=[]) + zwei = chunk_payload(0, 0, land=[], surfaces=[], carriageway=[], curb=[], sidewalk=[], + markings=[], lots=[], bridges=[], edges=[breit, schmal], + buildings=[], palms=[]) + assert eins == zwei + + +def test_bridges_sind_lauf_stabil(): + """Gegenprobe zu test_neue_schichten_sind_lauf_stabil, die bridges leer laesst: zwei + Bruecken in vertauschter Reihenfolge muessen dieselbe Ausgabe ergeben.""" + a = Polygon([(10, 2400), (20, 2400), (20, 2450), (10, 2450)]) + b = Polygon([(30, 2400), (40, 2400), (40, 2450), (30, 2450)]) + eins = chunk_payload(0, 0, land=[], surfaces=[], carriageway=[], curb=[], sidewalk=[], + markings=[], lots=[], bridges=[a, b], edges=[], buildings=[], palms=[]) + zwei = chunk_payload(0, 0, land=[], surfaces=[], carriageway=[], curb=[], sidewalk=[], + markings=[], lots=[], bridges=[b, a], edges=[], buildings=[], palms=[]) + assert eins == zwei + + +def test_curb_und_edges_sind_richtungsunabhaengig(): + """Die Zusicherung, auf der die ganze Normalisierung in _line() beruht (siehe dessen + Docstring): eine Linie und ihre exakte Umkehrung muessen fuer curb wie fuer edges dieselbe + Ausgabe ergeben. Einzeln geprueft (nur curb bzw. nur edges besetzt), damit ein kuenftiger + Bruch das betroffene Feld benennt statt nur "irgendwas stimmt nicht".""" + from shapely.geometry import LineString + hin = LineString([(10, 2400), (18, 2470), (22, 2500)]) + zurueck = LineString(list(reversed(list(hin.coords)))) + rest = dict(land=[], surfaces=[], carriageway=[], sidewalk=[], markings=[], lots=[], + bridges=[], buildings=[], palms=[]) + + eins_curb = chunk_payload(0, 0, curb=[hin], edges=[], **rest) + zwei_curb = chunk_payload(0, 0, curb=[zurueck], edges=[], **rest) + assert eins_curb["curb"] == zwei_curb["curb"] + + eins_edges = chunk_payload(0, 0, curb=[], edges=[(hin, "wohnstrasse", 12.0, 7.0)], **rest) + zwei_edges = chunk_payload(0, 0, curb=[], edges=[(zurueck, "wohnstrasse", 12.0, 7.0)], **rest) + assert eins_edges["edges"] == zwei_edges["edges"]