From 77d53fb170bb73c20494fc9f55f0e05cd68ea888 Mon Sep 17 00:00:00 2001 From: Kenso Grimm Date: Fri, 18 Sep 2026 06:54:01 +0200 Subject: [PATCH] =?UTF-8?q?fix(extract):=20carriageway[].klasse=20als=20ir?= =?UTF-8?q?ref=C3=BChrendes=20Feld=20entfernt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Befund 6, Controller-Entscheidung: carriageway_area() vereinigt das ganze Straßennetz zu wenigen Zusammenhangskomponenten, und _classified() gab jeder Komponente die Klasse ihrer breitesten enthaltenen Straße - am echten Netz trugen dadurch 91,2 % der Fahrbahnfläche "boulevard", auch wo tatsächlich eine Wohnstraße lag. Kein Verbraucher las das Feld (Browser und Minimap färben die Fahrbahn einheitlich); die Klasse je Straße steht weiterhin in edges. _classified() samt Aufruf aus run.py entfernt, chunk_payload() nimmt carriageway jetzt als reine Liste von Polygonen statt (poly, klasse)- Tupeln entgegen, Sortierschlüssel entsprechend auf poly allein verkürzt. Fixtures in test_emit.py, test_overview.py, tests/geometry.test.js und tests/streaming.test.js sowie das Chunk-Format im Entwurf nachgezogen; tests/props.test.js bleibt unverändert, weil dort edges betroffen sind, nicht carriageway. --- .../specs/2026-09-16-strassen-design.md | 3 ++- tests/geometry.test.js | 4 ++-- tests/streaming.test.js | 2 +- tools/extract/leonida/emit.py | 9 ++++++-- tools/extract/run.py | 22 +------------------ tools/extract/tests/test_emit.py | 12 +++++----- tools/extract/tests/test_overview.py | 8 +++---- 7 files changed, 23 insertions(+), 37 deletions(-) diff --git a/docs/superpowers/specs/2026-09-16-strassen-design.md b/docs/superpowers/specs/2026-09-16-strassen-design.md index da9bf50..55cfb9e 100644 --- a/docs/superpowers/specs/2026-09-16-strassen-design.md +++ b/docs/superpowers/specs/2026-09-16-strassen-design.md @@ -212,7 +212,7 @@ Neue und geänderte Schlüssel: ```json { "land": [[ring, loch, loch, ...]], - "carriageway": [{ "poly": [ring, ...], "klasse": "wohnstrasse" }], + "carriageway": [{ "poly": [ring, ...] }], "curb": [{ "line": [[x, z], ...] }], "sidewalk": [{ "poly": [ring, ...] }], "markings": [{ "poly": [ring], "art": "gelb_doppelt" }], @@ -223,6 +223,7 @@ Neue und geänderte Schlüssel: ``` - `roads` entfällt und wird durch `carriageway` und `lots` ersetzt. +- `carriageway` trägt keine Klasse (Fixwelle, Befund 6): Ein Eintrag ist eine Zusammenhangskomponente des ganzen Straßennetzes und bekäme damit nur die breiteste Klasse irgendeiner enthaltenen Straße zugewiesen — am echten Netz 91,2 % der Fläche „boulevard", unabhängig davon, was dort tatsächlich liegt. Die Klasse je Straße steht in `edges`; kein Verbraucher las `carriageway[].klasse`. - `centerlines` wird zu `edges` — dieselben Linien, aber mit Klasse und Breite. Laternen, Palmenabstand und später der Verkehr lesen daraus. - `curb` ist eine Liste von Linienzügen entlang des Fahrbahnrands — die Stücke, die einen Bordstein tragen. Gassen und Brücken liefern hier nichts. Die Wand und ihre Oberkante entstehen daraus im Browser; ein Polygon wäre falsch, weil die Wand keine Grundfläche hat. - `land` behält seine Form, bekommt aber die Fahrbahnlöcher. diff --git a/tests/geometry.test.js b/tests/geometry.test.js index d80303b..b4ce228 100644 --- a/tests/geometry.test.js +++ b/tests/geometry.test.js @@ -91,7 +91,7 @@ describe("geometry", () => { chunk: [0, 0], origin: [0, 0], land: [PLOT], surfaces: [{ poly: PLOT, kind: "sand" }, { poly: PLOT, kind: "park" }], - carriageway: [{ poly: PLOT, klasse: "wohnstrasse" }], + carriageway: [{ poly: PLOT }], bridges: [{ poly: PLOT }], palms: [], buildings: [{ poly: PLOT, height: 12, source: "district", speculative: false }], @@ -254,7 +254,7 @@ describe("Straßen aus Teil 2", () => { it("legt die Fahrbahn 15 cm unter den Boden", () => { const geo = buildChunkGeometry(chunk({ - carriageway: [{ poly: FAHRBAHN, klasse: "wohnstrasse" }], + carriageway: [{ poly: FAHRBAHN }], })).carriageway; // toBeCloseTo, nicht toEqual: Die Positionen liegen als Float32 vor, und -0,15 ist dort // -0,15000000596. diff --git a/tests/streaming.test.js b/tests/streaming.test.js index b6a90c8..52738e0 100644 --- a/tests/streaming.test.js +++ b/tests/streaming.test.js @@ -246,7 +246,7 @@ describe("ChunkStreamer", () => { const square = [[[0, 0], [20, 0], [20, 20], [0, 20]]]; const chunk = { chunk: [4, 6], origin: [0, -1000], land: [square], surfaces: [], buildings: [], - carriageway: [{ poly: [[[0, 20], [20, 20], [20, 30], [0, 30]]], klasse: "wohnstrasse" }], + carriageway: [{ poly: [[[0, 20], [20, 20], [20, 30], [0, 30]]] }], curb: [[[0, 20], [20, 20]]], sidewalk: [{ poly: [[[0, 17], [20, 17], [20, 20], [0, 20]]] }], markings: [{ poly: [[[2, 25], [5, 25], [5, 25.1], [2, 25.1]]], art: "gelb_gestrichelt" }], diff --git a/tools/extract/leonida/emit.py b/tools/extract/leonida/emit.py index 09afdde..6ddd6a6 100644 --- a/tools/extract/leonida/emit.py +++ b/tools/extract/leonida/emit.py @@ -118,6 +118,12 @@ def chunk_payload(i, j, *, land, surfaces, carriageway, curb, sidewalk, markings Sortiert wird nach der gerundeten Ausgabestruktur selbst, nicht nach Flaeche, Schwerpunkt oder Bounding-Box: nur sie ist total geordnet und ohne Sonderfall, denn zwei Eintraege mit gleichem Schluessel sind dann bytegleich und ihre Reihenfolge wieder egal. + + carriageway traegt keine Klasse (Befund 6 der Fixwelle): carriageway_area() vereinigt das + ganze Strassennetz, ein Eintrag ist eine Zusammenhangskomponente und bekaeme damit nur die + breiteste Klasse irgendeiner enthaltenen Strasse zugewiesen - am echten Netz 91,2 % der + Flaeche "boulevard", unabhaengig davon, was dort tatsaechlich liegt. Die Klasse je Strasse + steht in edges; kein Verbraucher las carriageway[].klasse. """ ox = SLICE["x0"] + i * CHUNK_SIZE oy = SLICE["y1"] - j * CHUNK_SIZE @@ -126,8 +132,7 @@ def chunk_payload(i, j, *, land, surfaces, carriageway, curb, sidewalk, markings "origin": [ox, -oy], "land": sorted(_rings(p, i, j) for p in land), "surfaces": _sorted_surfaces([{"poly": _rings(p, i, j), "kind": k} for p, k in surfaces]), - "carriageway": sorted(({"poly": _rings(p, i, j), "klasse": k} for p, k in carriageway), - key=lambda c: (c["poly"], c["klasse"])), + "carriageway": sorted(({"poly": _rings(p, i, j)} for p in carriageway), key=lambda c: c["poly"]), "curb": sorted(_line(l, i, j) for l in curb), "sidewalk": sorted(({"poly": _rings(p, i, j)} for p in sidewalk), key=lambda s: s["poly"]), "markings": sorted(({"poly": _rings(p, i, j), "art": a} for p, a in markings), diff --git a/tools/extract/run.py b/tools/extract/run.py index 659f79f..da8b520 100644 --- a/tools/extract/run.py +++ b/tools/extract/run.py @@ -62,24 +62,6 @@ def chunk_markings(marks, i: int, j: int) -> list: for part in split_by_chunk_all([poly], i, j, min_area=MARKING_MIN_AREA)] -def _classified(street, graph) -> list: - """Jede Fahrbahnfläche mit der Klasse der breitesten Kante, deren Mittelpunkt in ihr liegt. - - Vorsicht beim Lesen des Felds: carriageway_area() verschmilzt das Netz zu wenigen - zusammenhängenden Teilen, und ein Teil trägt dann die breiteste Klasse aller Straßen darin. Am - echten Netz sind das 5 von 213 Teilen, aber 91 % der Fahrbahnfläche tragen "boulevard". Über - die einzelne Straße sagt das Feld also wenig — die Klasse je Straße steht in edges. Heute liest - es kein Verbraucher; Browser und Minimap färben die Fahrbahn einheitlich. - """ - mitten = [(e.klass, LineString(e.points).interpolate(0.5, normalized=True)) for e in graph.edges] - out = [] - for poly in _pieces(street.carriageway): - treffer = [klass for klass, mitte in mitten if poly.contains(mitte)] - klass = max(treffer, key=lambda k: SECTION[k].carriageway) if treffer else "wohnstrasse" - out.append((poly, klass)) - return out - - def main() -> None: cache = Path("cache") download_tiles(cache) @@ -156,7 +138,6 @@ def main() -> None: arten = dict(sorted(Counter(art for _, art in marks).items())) print(f"Straßen: {len(street.kerb)} Bordsteinstücke, {len(street.lots)} Restflächen, " f"{len(marks)} Markierungen {arten}, {palmen - len(world.palms)} Palmen an der Fahrbahn entfernt") - classified = _classified(street, world.graph) # einmal, nicht je Chunk: 5,8 s am echten Netz payloads = [] for j in range(12): @@ -166,8 +147,7 @@ def main() -> None: land=list(split_by_chunk_all(world.land, i, j)), surfaces=[(part, kind) for (p, kind) in world.surfaces for part in split_by_chunk_all([p], i, j)], - carriageway=[(part, klass) for poly, klass in classified - for part in split_by_chunk_all([poly], i, j)], + carriageway=list(split_by_chunk_all(_pieces(street.carriageway), i, j)), curb=[part for line in street.kerb for part in _clip_lines(line, chunk_box_of(i, j))], sidewalk=list(split_by_chunk_all(_pieces(street.sidewalk), i, j)), diff --git a/tools/extract/tests/test_emit.py b/tools/extract/tests/test_emit.py index 032d694..243c2ff 100644 --- a/tools/extract/tests/test_emit.py +++ b/tools/extract/tests/test_emit.py @@ -114,12 +114,12 @@ def test_the_same_geometry_in_another_order_gives_the_same_output(): 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")], - carriageway=[(a, "wohnstrasse"), (b, "wohnstrasse")], curb=[], sidewalk=[], + carriageway=[a, b], curb=[], sidewalk=[], markings=[], lots=[], bridges=[b], edges=[], 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")], - carriageway=[(b, "wohnstrasse"), (a_gedreht, "wohnstrasse")], curb=[], sidewalk=[], + carriageway=[b, a_gedreht], curb=[], sidewalk=[], markings=[], lots=[], bridges=[b], edges=[], buildings=[(b, HEIGHT, True), (a_gedreht, HEIGHT, False)], palms=[(-960.0, 2460.0), (-990.0, 2490.0)], paths=[b, a_gedreht]) @@ -159,14 +159,14 @@ def test_neue_schichten_stehen_im_chunk(): payload = chunk_payload( 0, 0, land=[box(0, 2400, 100, 2500)], surfaces=[], - carriageway=[(box(10, 2400, 20, 2500), "wohnstrasse")], + carriageway=[box(10, 2400, 20, 2500)], curb=[LineString([(10, 2400), (10, 2500)])], sidewalk=[box(20, 2400, 23, 2500)], markings=[(box(14, 2440, 16, 2443), "gelb_gestrichelt")], lots=[box(40, 2400, 60, 2420)], bridges=[], edges=[(LineString([(15, 2400), (15, 2500)]), "wohnstrasse", 12.0, 7.0)], buildings=[], palms=[]) - assert payload["carriageway"][0]["klasse"] == "wohnstrasse" + assert set(payload["carriageway"][0]) == {"poly"}, "Befund 6 der Fixwelle: klasse entfaellt ganz" assert payload["markings"][0]["art"] == "gelb_gestrichelt" assert payload["edges"][0]["klasse"] == "wohnstrasse" assert payload["edges"][0]["fahrbahn"] == 7.0 @@ -179,10 +179,10 @@ def test_neue_schichten_sind_lauf_stabil(): from shapely.geometry import LineString, box a, b = box(10, 2400, 20, 2450), box(30, 2400, 40, 2450) l1, l2 = LineString([(0, 2400), (0, 2450)]), LineString([(5, 2400), (5, 2450)]) - eins = chunk_payload(0, 0, land=[], surfaces=[], carriageway=[(a, "gasse"), (b, "gasse")], + eins = chunk_payload(0, 0, land=[], surfaces=[], carriageway=[a, b], curb=[l1, l2], sidewalk=[a], markings=[(a, "zebra"), (b, "zebra")], lots=[a, b], bridges=[], edges=[], buildings=[], palms=[]) - zwei = chunk_payload(0, 0, land=[], surfaces=[], carriageway=[(b, "gasse"), (a, "gasse")], + zwei = chunk_payload(0, 0, land=[], surfaces=[], carriageway=[b, a], curb=[l2, l1], sidewalk=[a], markings=[(b, "zebra"), (a, "zebra")], lots=[b, a], bridges=[], edges=[], buildings=[], palms=[]) assert eins == zwei diff --git a/tools/extract/tests/test_overview.py b/tools/extract/tests/test_overview.py index a2d4f9d..4cc01eb 100644 --- a/tools/extract/tests/test_overview.py +++ b/tools/extract/tests/test_overview.py @@ -23,7 +23,7 @@ def _fixture(tmp_path: Path) -> Path: "origin": [SLICE["x0"], -SLICE["y1"]], "land": [[_ring(0, 0, 250, 250)]], "surfaces": [], - "carriageway": [{"poly": [_ring(100, 0, 120, 250)], "klasse": "hauptstrasse"}], + "carriageway": [{"poly": [_ring(100, 0, 120, 250)]}], "curb": [], "sidewalk": [], "markings": [], "lots": [], "bridges": [], "edges": [], "buildings": [{"poly": [_ring(20, 20, 60, 60)], "height": 12.0, "source": "district", "speculative": False}], "palms": [[200.0, 200.0]], @@ -99,7 +99,7 @@ def test_walkable_mask_counts_only_ground_with_collision(tmp_path): "land": [], "surfaces": [{"poly": [_ring(0, 0, 40, 40)], "kind": "sand"}], "curb": [], "edges": [], "markings": [], - "carriageway": [{"poly": [_ring(100, 0, 120, 40)], "klasse": "wohnstrasse"}], + "carriageway": [{"poly": [_ring(100, 0, 120, 40)]}], "lots": [{"poly": [_ring(140, 0, 160, 40)]}], "bridges": [{"poly": [_ring(180, 0, 200, 40)]}], "sidewalk": [{"poly": [_ring(220, 0, 240, 40)]}], @@ -206,7 +206,7 @@ def _strassen_fixture(tmp_path: Path) -> Path: "land": [[_ring(0, 0, 250, 250)]], "surfaces": [], "buildings": [], "palms": [], "paths": [], "edges": [], "curb": [], "bridges": [], "lots": [{"poly": [_ring(10, 150, 60, 200)]}], - "carriageway": [{"poly": [_ring(100, 0, 112, 250)], "klasse": "hauptstrasse"}], + "carriageway": [{"poly": [_ring(100, 0, 112, 250)]}], "sidewalk": [{"poly": [_ring(112, 0, 124, 250)]}], "markings": [{"poly": [_ring(105, 0, 105.1, 250)], "art": "gelb_doppelt"}], } @@ -272,7 +272,7 @@ def test_bruecke_bleibt_im_kartenbild_als_bruecke_sichtbar(tmp_path): "origin": [SLICE["x0"], -SLICE["y1"]], "land": [], "surfaces": [], "paths": [], "curb": [], "edges": [], "markings": [], "sidewalk": [], "lots": [], - "carriageway": [{"poly": [_ring(100, 0, 120, 40)], "klasse": "hauptstrasse"}], + "carriageway": [{"poly": [_ring(100, 0, 120, 40)]}], "bridges": [{"poly": [_ring(100, 0, 120, 40)]}], "buildings": [], "palms": [], }