Files
gta6/tools/extract/tests/test_committed_roads.py
T
knsoandClaude Sonnet 5 35d69c3e6c test(extract): toothless Gassen-Gehweg-Probe entfernt, Flaechenvaliditaet abgesichert
Nachpruefung der Fixwelle, drei Punkte:

test_gehweg_waechst_auch_bei_engem_winkel_nicht_in_die_gasse
(test_street.py) besteht identisch (0,0000 m²) unter dem aktuellen Code,
unter der verworfenen Fassung aus Fixrunde 4 und sogar unter einem
komplett ungefilterten walk_source = carriageway_area(graph) - nachgerechnet
mit einer Wegwerf-Kopie aller drei Varianten. Ursache: SECTION["gasse"].corridor
== SECTION["gasse"].carriageway, weshalb .difference(carriage) am Ende von
_sidewalk() den geprueften Streifen (die eigene Gassen-Fahrbahn) immer
loescht, unabhaengig davon, was _walk_source() liefert. Die Eigenschaft
selbst bleibt durch die drei benachbarten Tests
(test_gasse_bekommt_weder_gehweg_noch_bordstein,
test_gehweg_waechst_nicht_ueber_den_gassenstummel_an_geteilter_kreuzung,
test_gehwegrest_an_kreuzung_mit_mehreren_gassen_bleibt_aus) abgesichert,
die gegen dieselbe verworfene Fassung mit 53,11 bzw. 53,30 m² hart
durchfallen. Ein Test, der nicht fehlschlagen kann, taeuscht Deckung vor,
die es nicht gibt - entfernt statt umdimensioniert, weil eine Geometrie
mit nur einem Gassenarm die eigentliche Fixrunde-4-Nische (zwischen ZWEI
benachbarten Gassenstummeln) strukturell nie nachstellen kann.

test_schleife_die_auf_sich_selbst_zurueckfuehrt_bekommt_keine_markierung_daneben
(test_marking_junction.py) galt in der Nachpruefung ebenfalls als
unterscheidungslos (100% Fahrbahnanteil auch mit altem Code). Mit den
byte-genauen historischen Fassungen von junction_marks() (git show
4bf670e / ca01141) direkt in die bestehende Testfunktion eingesetzt
(Monkeypatch) zeigt sich das Gegenteil: Beide Vorstufen schlagen rot an
(schlechtester Anteil 64,9% bzw. 85,3%, an Haltelinie und aeusserstem
Zebrabalken), nur der aktuelle Code besteht. Der Test bleibt unveraendert,
mit einem Kommentar zur Gegenpruefung.

Neu: test_alle_flaechenschichten_sind_gueltige_polygone
(test_committed_roads.py) - Waechter aus Befund 5 (die alte
Koordinatenrundung erzeugte acht Selbstschnitte, ohne dass ein Test es
merkte). Prueft ueber alle 144 Chunks und alle sieben Flaechenschichten
(carriageway, land, sidewalk, surfaces, lots, bridges, paths), dass jeder
Ring ein gueltiges Polygon ergibt. Heute gruen (4.190 Flaechen, 0
ungueltige); rot nachgewiesen gegen den Datenstand vor Commit a397892
(9 ungueltige Polygone in 6 Chunks, per git show ohne Codeaenderung
nachgestellt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-18 09:07:27 +02:00

132 lines
6.8 KiB
Python

"""Die ausgelieferten Straßen, festgehalten (Entwurf, Abschnitt Tests: Integrationslauf).
Der Test liest die committeten Chunks, nicht den Lauf — die Kacheln liegen nicht im Repository.
Die Zahlen sind beim Neuerzeugen gemessen. Ändert sich die Extraktion absichtlich, gehören die neuen
Zahlen hierher, mit einem Satz im Commit, warum. Die Kreuzungszahl fehlt, weil die Chunks keine
Knoten tragen; sie steht in den Messwerten des Entwurfs.
"""
from __future__ import annotations
import json
from collections import Counter
from pathlib import Path
from shapely.geometry import LineString, Point, Polygon
from shapely.ops import unary_union
from shapely.validation import explain_validity
ROOT = Path(__file__).resolve().parents[3] / "public" / "data" / "vice-a"
# Die sieben Flächenschichten eines Chunks (Entwurf, Abschnitt "Chunk-Format"). `land` liegt roh
# als Liste von Ringlisten vor ([ring, loch, ...]), die übrigen sechs als Liste von
# {"poly": [ring, loch, ...]}-Objekten - _flaechen_polygone() unten gleicht das an.
AREA_LAYERS = ["carriageway", "land", "sidewalk", "surfaces", "lots", "bridges", "paths"]
def _flaechen_polygone(chunk: dict, layer: str) -> list[Polygon]:
entries = chunk.get(layer) or []
ringlisten = entries if layer == "land" else [e["poly"] for e in entries]
return [Polygon(r[0], r[1:]) for r in ringlisten]
def _chunks() -> list[dict]:
return [json.loads(p.read_text(encoding="utf-8")) for p in sorted(ROOT.glob("chunk_*.json"))]
def _flaeche(chunks, key) -> float:
return sum(Polygon(i["poly"][0], i["poly"][1:]).area for c in chunks for i in c.get(key, []))
def test_kantenstuecke_je_klasse():
assert dict(Counter(e["klasse"] for c in _chunks() for e in c["edges"])) == {
"boulevard": 270, "wohnstrasse": 537, "gasse": 1010, "hauptstrasse": 401,
}
def test_markierungsstuecke_je_art():
assert dict(Counter(m["art"] for c in _chunks() for m in c["markings"])) == {
"gelb_doppelt": 1216, "gelb_gestrichelt": 2370, "haltelinie": 1555,
"weiss_gestrichelt": 2066, "weiss_rand": 1219, "zebra": 14846,
}
def test_restflaechen_und_bordsteinstuecke():
chunks = _chunks()
assert sum(len(c["lots"]) for c in chunks) == 279 # „lots" aus „Stücke, je Chunk gezählt"
assert sum(len(c["curb"]) for c in chunks) == 1415 # „curb" aus „Stücke, je Chunk gezählt"
def test_flaechen_und_bordsteinlaenge():
chunks = _chunks()
assert round(_flaeche(chunks, "carriageway") / 1e6, 4) == 0.9076 # aus „Flächen km²"
assert round(_flaeche(chunks, "sidewalk") / 1e6, 4) == 0.2987
assert round(_flaeche(chunks, "lots") / 1e6, 4) == 0.0967
laenge = sum(LineString(line).length for c in chunks for line in c["curb"])
assert round(laenge / 1e3, 2) == 120.28 # aus „Bordstein km"
def test_keine_palme_auf_der_fahrbahn():
for c in _chunks():
fahrbahn = [Polygon(i["poly"][0], i["poly"][1:]) for i in c["carriageway"]]
auf = [p for p in c["palms"] if any(f.contains(Point(p)) for f in fahrbahn)]
assert auf == [], f"Chunk {c['chunk']}"
def test_markierung_liegt_nicht_wesentlich_ausserhalb_der_fahrbahn():
"""Befund 10 der Fixwelle: Vor Befund 3 lagen 452,65 m² Markierungsfläche (1,03 % von
43.906 m²) außerhalb der Fahrbahn, weil "along" in junction_marks() nur aus dem ersten
Segment einer Zufahrt kam. Rechnerisch nachgewiesen gegen die damals ausgelieferten Daten
(Commit 0ad6eee, vor dieser Fixwelle) - mit dem hier verwendeten Maß (Fläche von markings
minus der Vereinigung von carriageway, je Chunk summiert) maß dieser Befund dort 452,65 m²
und wäre an jeder Schranke unter diesem Wert rot gewesen.
Befund 3 behebt den Hauptteil davon (auf 110,06 m² = 0,25 %, siehe Fixwelle-Bericht), aber
nicht den ganzen Rest: Ein Bogen kann zwischen Streckenlänge reach und
reach + STOP_SETBACK + STOP_DEPTH noch einmal abknicken, was ein einzelner Richtungsstrahl
nicht mehr auffängt - das verlangte die vom Befund selbst genannte, größere Alternative ("die
Markierung entlang der tatsächlichen Kante aufbauen"), die diese Fixwelle nicht umsetzt. Die
Schranke hier ist deshalb *kein* reiner Rundungswert, sondern hält den mit Befund 3
erreichten, im Fixwelle-Bericht offengelegten Stand fest (mit etwas Luft nach oben) und
schlägt bei einer echten Verschlechterung an.
"""
gesamt_ausserhalb = 0.0
for c in _chunks():
fahrbahn = unary_union([Polygon(i["poly"][0], i["poly"][1:]) for i in c.get("carriageway", [])])
for m in c.get("markings", []):
poly = Polygon(m["poly"][0], m["poly"][1:])
gesamt_ausserhalb += poly.difference(fahrbahn).area if not fahrbahn.is_empty else poly.area
assert gesamt_ausserhalb < 130.0, (
f"{gesamt_ausserhalb:.2f} m² Markierungsfläche liegen außerhalb der Fahrbahn "
"(Fixwelle-Stand: 110,06 m²) - das ist mehr als der dokumentierte Rest, nicht nur Rundung"
)
def test_alle_flaechenschichten_sind_gueltige_polygone():
"""Wächter aus der Nachprüfung der Fixwelle (zu Befund 5, emit.py): Die alte
Koordinatenrundung (jede Zahl unabhängig auf 2 Stellen) konnte einen Ring selbst schneiden -
acht Fälle in den damals ausgelieferten Chunks (carriageway 3, land 2, sidewalk 2, surfaces
1), ohne dass ein Test es bemerkte. set_precision() behebt das (s. emit.py), aber bis zu
diesem Test hielt nichts fest, dass es dabei bleibt: Eine künftige Änderung an der Rundung
oder an set_precision() selbst könnte denselben Fehler wieder einschleppen, unbemerkt wie
beim ersten Mal.
Prüft darum über alle 144 Chunks und alle sieben Flächenschichten (`carriageway`, `land`,
`sidewalk`, `surfaces`, `lots`, `bridges`, `paths`), dass jeder Ring ein gültiges Polygon
ergibt. Am committeten Stand: 4.190 Flächen geprüft, 0 ungültige (nachgemessen). Rot gegen den
Datenstand vor Commit a397892 (Koordinatenrundung statt set_precision): 9 ungültige Polygone in
6 Chunks (dieselben acht Polygone wie im Befund - carriageway 3, land 2, sidewalk 2, surfaces 1
-, plus ein neuntes, vorbestehendes `paths`-Polygon in Chunk (10, 6), das set_precision() als
Nebeneffekt mitheilt) - mit einer Kopie der damaligen Chunk-Dateien nachgestellt (`git show
83140f8:...`), nicht mit dem heutigen Code.
"""
chunks = _chunks()
gesamt = 0
ungueltig = []
for c in chunks:
for layer in AREA_LAYERS:
for poly in _flaechen_polygone(c, layer):
gesamt += 1
if not poly.is_valid:
ungueltig.append((tuple(c["chunk"]), layer, explain_validity(poly)))
assert gesamt > 4000, f"nur {gesamt} Flächen geprüft - fehlt eine Schicht oder ein Chunk?"
assert ungueltig == [], f"{len(ungueltig)} von {gesamt} Flächen sind kein gültiges Polygon: {ungueltig}"