Bordstein 184,33 km statt 120,28 km (+53 %) in 1.341 Stücken je Chunk statt 1.415 — mehr Länge in weniger Stücken, weil der Rand jetzt durchläuft, statt an jeder Gasse unterbrochen zu werden. Abdeckung des Fahrbahnrands, an den fertigen Chunks nachgemessen: Gasse 7,0 % → 83,1 %, Wohnstraße 87,9 % → 88,0 %, Hauptstraße 87,2 % → 87,3 %, Boulevard 86,3 % → 86,3 %. Fahrbahn (0,9076 km²), Gehweg (0,2987 km²), Restflächen (0,0967 km²) und Markierungen (23.272 Stücke) bleiben, wie sie waren. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
132 lines
6.8 KiB
Python
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) == 1341 # „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) == 184.33 # 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}"
|