fix(extract): carriageway[].klasse als irreführendes Feld entfernt

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.
This commit is contained in:
knso committed 2026-09-18 06:54:01 +02:00
1 parent a397892847
commit 77d53fb170
7 files changed
+23 -37

No files matched your search

@@ -212,7 +212,7 @@ Neue und geänderte Schlüssel:
```json ```json
{ {
"land": [[ring, loch, loch, ...]], "land": [[ring, loch, loch, ...]],
"carriageway": [{ "poly": [ring, ...], "klasse": "wohnstrasse" }], "carriageway": [{ "poly": [ring, ...] }],
"curb": [{ "line": [[x, z], ...] }], "curb": [{ "line": [[x, z], ...] }],
"sidewalk": [{ "poly": [ring, ...] }], "sidewalk": [{ "poly": [ring, ...] }],
"markings": [{ "poly": [ring], "art": "gelb_doppelt" }], "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. - `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. - `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. - `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. - `land` behält seine Form, bekommt aber die Fahrbahnlöcher.
+2 -2
View File
@@ -91,7 +91,7 @@ describe("geometry", () => {
chunk: [0, 0], origin: [0, 0], chunk: [0, 0], origin: [0, 0],
land: [PLOT], land: [PLOT],
surfaces: [{ poly: PLOT, kind: "sand" }, { poly: PLOT, kind: "park" }], surfaces: [{ poly: PLOT, kind: "sand" }, { poly: PLOT, kind: "park" }],
carriageway: [{ poly: PLOT, klasse: "wohnstrasse" }], carriageway: [{ poly: PLOT }],
bridges: [{ poly: PLOT }], bridges: [{ poly: PLOT }],
palms: [], palms: [],
buildings: [{ poly: PLOT, height: 12, source: "district", speculative: false }], 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", () => { it("legt die Fahrbahn 15 cm unter den Boden", () => {
const geo = buildChunkGeometry(chunk({ const geo = buildChunkGeometry(chunk({
carriageway: [{ poly: FAHRBAHN, klasse: "wohnstrasse" }], carriageway: [{ poly: FAHRBAHN }],
})).carriageway; })).carriageway;
// toBeCloseTo, nicht toEqual: Die Positionen liegen als Float32 vor, und -0,15 ist dort // toBeCloseTo, nicht toEqual: Die Positionen liegen als Float32 vor, und -0,15 ist dort
// -0,15000000596. // -0,15000000596.
+1 -1
View File
@@ -246,7 +246,7 @@ describe("ChunkStreamer", () => {
const square = [[[0, 0], [20, 0], [20, 20], [0, 20]]]; const square = [[[0, 0], [20, 0], [20, 20], [0, 20]]];
const chunk = { const chunk = {
chunk: [4, 6], origin: [0, -1000], land: [square], surfaces: [], buildings: [], 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]]], curb: [[[0, 20], [20, 20]]],
sidewalk: [{ poly: [[[0, 17], [20, 17], [20, 20], [0, 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" }], markings: [{ poly: [[[2, 25], [5, 25], [5, 25.1], [2, 25.1]]], art: "gelb_gestrichelt" }],
+7 -2
View File
@@ -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 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 Bounding-Box: nur sie ist total geordnet und ohne Sonderfall, denn zwei Eintraege mit gleichem
Schluessel sind dann bytegleich und ihre Reihenfolge wieder egal. 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 ox = SLICE["x0"] + i * CHUNK_SIZE
oy = SLICE["y1"] - j * 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], "origin": [ox, -oy],
"land": sorted(_rings(p, i, j) for p in land), "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]), "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), "carriageway": sorted(({"poly": _rings(p, i, j)} for p in carriageway), key=lambda c: c["poly"]),
key=lambda c: (c["poly"], c["klasse"])),
"curb": sorted(_line(l, i, j) for l in curb), "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"]), "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), "markings": sorted(({"poly": _rings(p, i, j), "art": a} for p, a in markings),
+1 -21
View File
@@ -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)] 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: def main() -> None:
cache = Path("cache") cache = Path("cache")
download_tiles(cache) download_tiles(cache)
@@ -156,7 +138,6 @@ def main() -> None:
arten = dict(sorted(Counter(art for _, art in marks).items())) arten = dict(sorted(Counter(art for _, art in marks).items()))
print(f"Straßen: {len(street.kerb)} Bordsteinstücke, {len(street.lots)} Restflächen, " 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") 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 = [] payloads = []
for j in range(12): for j in range(12):
@@ -166,8 +147,7 @@ def main() -> None:
land=list(split_by_chunk_all(world.land, i, j)), land=list(split_by_chunk_all(world.land, i, j)),
surfaces=[(part, kind) for (p, kind) in world.surfaces surfaces=[(part, kind) for (p, kind) in world.surfaces
for part in split_by_chunk_all([p], i, j)], for part in split_by_chunk_all([p], i, j)],
carriageway=[(part, klass) for poly, klass in classified carriageway=list(split_by_chunk_all(_pieces(street.carriageway), i, j)),
for part in split_by_chunk_all([poly], i, j)],
curb=[part for line in street.kerb curb=[part for line in street.kerb
for part in _clip_lines(line, chunk_box_of(i, j))], for part in _clip_lines(line, chunk_box_of(i, j))],
sidewalk=list(split_by_chunk_all(_pieces(street.sidewalk), i, j)), sidewalk=list(split_by_chunk_all(_pieces(street.sidewalk), i, j)),
+6 -6
View File
@@ -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)]) 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) 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")], 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=[], markings=[], lots=[], bridges=[b], edges=[],
buildings=[(a, HEIGHT, False), (b, HEIGHT, True)], buildings=[(a, HEIGHT, False), (b, HEIGHT, True)],
palms=[(-990.0, 2490.0), (-960.0, 2460.0)], paths=[a, b]) 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")], 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=[], markings=[], lots=[], bridges=[b], edges=[],
buildings=[(b, HEIGHT, True), (a_gedreht, HEIGHT, False)], buildings=[(b, HEIGHT, True), (a_gedreht, HEIGHT, False)],
palms=[(-960.0, 2460.0), (-990.0, 2490.0)], paths=[b, a_gedreht]) 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( payload = chunk_payload(
0, 0, 0, 0,
land=[box(0, 2400, 100, 2500)], surfaces=[], 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)])], curb=[LineString([(10, 2400), (10, 2500)])],
sidewalk=[box(20, 2400, 23, 2500)], sidewalk=[box(20, 2400, 23, 2500)],
markings=[(box(14, 2440, 16, 2443), "gelb_gestrichelt")], markings=[(box(14, 2440, 16, 2443), "gelb_gestrichelt")],
lots=[box(40, 2400, 60, 2420)], lots=[box(40, 2400, 60, 2420)],
bridges=[], edges=[(LineString([(15, 2400), (15, 2500)]), "wohnstrasse", 12.0, 7.0)], bridges=[], edges=[(LineString([(15, 2400), (15, 2500)]), "wohnstrasse", 12.0, 7.0)],
buildings=[], palms=[]) 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["markings"][0]["art"] == "gelb_gestrichelt"
assert payload["edges"][0]["klasse"] == "wohnstrasse" assert payload["edges"][0]["klasse"] == "wohnstrasse"
assert payload["edges"][0]["fahrbahn"] == 7.0 assert payload["edges"][0]["fahrbahn"] == 7.0
@@ -179,10 +179,10 @@ def test_neue_schichten_sind_lauf_stabil():
from shapely.geometry import LineString, box from shapely.geometry import LineString, box
a, b = box(10, 2400, 20, 2450), box(30, 2400, 40, 2450) a, b = box(10, 2400, 20, 2450), box(30, 2400, 40, 2450)
l1, l2 = LineString([(0, 2400), (0, 2450)]), LineString([(5, 2400), (5, 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")], curb=[l1, l2], sidewalk=[a], markings=[(a, "zebra"), (b, "zebra")],
lots=[a, b], bridges=[], edges=[], buildings=[], palms=[]) 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")], curb=[l2, l1], sidewalk=[a], markings=[(b, "zebra"), (a, "zebra")],
lots=[b, a], bridges=[], edges=[], buildings=[], palms=[]) lots=[b, a], bridges=[], edges=[], buildings=[], palms=[])
assert eins == zwei assert eins == zwei
+4 -4
View File
@@ -23,7 +23,7 @@ def _fixture(tmp_path: Path) -> Path:
"origin": [SLICE["x0"], -SLICE["y1"]], "origin": [SLICE["x0"], -SLICE["y1"]],
"land": [[_ring(0, 0, 250, 250)]], "land": [[_ring(0, 0, 250, 250)]],
"surfaces": [], "surfaces": [],
"carriageway": [{"poly": [_ring(100, 0, 120, 250)], "klasse": "hauptstrasse"}], "carriageway": [{"poly": [_ring(100, 0, 120, 250)]}],
"curb": [], "sidewalk": [], "markings": [], "lots": [], "bridges": [], "edges": [], "curb": [], "sidewalk": [], "markings": [], "lots": [], "bridges": [], "edges": [],
"buildings": [{"poly": [_ring(20, 20, 60, 60)], "height": 12.0, "source": "district", "speculative": False}], "buildings": [{"poly": [_ring(20, 20, 60, 60)], "height": 12.0, "source": "district", "speculative": False}],
"palms": [[200.0, 200.0]], "palms": [[200.0, 200.0]],
@@ -99,7 +99,7 @@ def test_walkable_mask_counts_only_ground_with_collision(tmp_path):
"land": [], "land": [],
"surfaces": [{"poly": [_ring(0, 0, 40, 40)], "kind": "sand"}], "surfaces": [{"poly": [_ring(0, 0, 40, 40)], "kind": "sand"}],
"curb": [], "edges": [], "markings": [], "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)]}], "lots": [{"poly": [_ring(140, 0, 160, 40)]}],
"bridges": [{"poly": [_ring(180, 0, 200, 40)]}], "bridges": [{"poly": [_ring(180, 0, 200, 40)]}],
"sidewalk": [{"poly": [_ring(220, 0, 240, 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)]], "land": [[_ring(0, 0, 250, 250)]],
"surfaces": [], "buildings": [], "palms": [], "paths": [], "edges": [], "curb": [], "surfaces": [], "buildings": [], "palms": [], "paths": [], "edges": [], "curb": [],
"bridges": [], "lots": [{"poly": [_ring(10, 150, 60, 200)]}], "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)]}], "sidewalk": [{"poly": [_ring(112, 0, 124, 250)]}],
"markings": [{"poly": [_ring(105, 0, 105.1, 250)], "art": "gelb_doppelt"}], "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"]], "origin": [SLICE["x0"], -SLICE["y1"]],
"land": [], "surfaces": [], "paths": [], "curb": [], "edges": [], "markings": [], "land": [], "surfaces": [], "paths": [], "curb": [], "edges": [], "markings": [],
"sidewalk": [], "lots": [], "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)]}], "bridges": [{"poly": [_ring(100, 0, 120, 40)]}],
"buildings": [], "palms": [], "buildings": [], "palms": [],
} }