feat(extract): Minimap zeichnet Fahrbahn, Gehweg und Restflächen
overview.py las chunk["roads"] und item["bridge"] ohne Rückfall und riss mit Chunks im Format aus Task 10 (carriageway/sidewalk/lots/bridges) einen KeyError auf. _paint_layers() liest jetzt carriageway, sidewalk und lots getrennt; chunk["bridges"] braucht keine eigene Regel mehr, weil seine Fahrbahn schon in carriageway steckt und sein begehbarer Streifen im sidewalk. Markierungen werden nicht gezeichnet: bei 1 px je 3 m wäre eine 10-cm-Linie ein Drittel Pixel und nur Rauschen. In der Begehbarkeitsmaske sperren Fahrbahn und Restflächen jetzt aktiv, auch über der Landplatte - dort fährt Verkehr, kein Fußgänger soll dort landen. Der Gehweg trägt dafür auch ohne Landplatte, wie zuvor nur die Brücke; die alte Sonderregel für Brücken entfällt darum ersatzlos. Alle Fixtures in test_overview.py, die von Hand im alten Format standen, sind mitgezogen; eine davon musste dabei die Zusicherung wechseln (Fahrbahn auf Land ist jetzt gesperrt statt begehbar), zwei weitere kamen dazu (Restfläche und Brücke ohne Land). Der Gehweg-Streifen im neuen Prüfkörper war im Planentwurf 3 statt 12 m breit: schmaler als eine 4-m-Maskenzelle, die deshalb nie ganz hineinpasste, unabhängig von der Rundung - jetzt auf 12 m korrigiert wie die Fahrbahn. test_committed_map_files_match_the_committed_chunks ist bis Task 16 übersprungen: die ausgelieferten Chunks unter public/data/vice-a bleiben laut Plan bis dahin im alten Format, render_map() kennt das nicht mehr. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bab83bce5f
commit
f1c8371365
2 files changed
+138
-35
No files matched your search
+39
-20
@@ -28,11 +28,11 @@ WALKABLE_SCALE = 0.25 # Pixel je Meter, map-walkable.png (4 m je Pixe
|
||||
WATER = (36, 72, 104)
|
||||
LAND = (150, 162, 120)
|
||||
ROAD = (74, 74, 78)
|
||||
SIDEWALK = (206, 202, 194)
|
||||
# field, court, pool und parking: Flamingo Park nach OpenStreetMap (docs/superpowers/specs/2026-09-15-flamingo-park-design.md)
|
||||
SURFACE = {"sand": (226, 210, 150), "park": (120, 160, 90), "urban": (176, 176, 172),
|
||||
"field": (96, 176, 78), "court": (92, 138, 118), "pool": (70, 150, 220), "parking": ROAD}
|
||||
PATH = (226, 216, 190)
|
||||
BRIDGE = (196, 92, 92)
|
||||
PALM = (40, 190, 70)
|
||||
OUTLINE = (30, 30, 30)
|
||||
TALL = 120.0 # Meter, ab der ein Gebäude die hellste Färbung bekommt
|
||||
@@ -78,15 +78,24 @@ def _load_chunks(out_dir: Path) -> list[dict]:
|
||||
return [json.loads(f.read_text()) for f in sorted(out_dir.glob("chunk_*.json"))]
|
||||
|
||||
|
||||
def _paint_layers(img, chunks, scale, *, land, surface, road, bridge, building, outline=None, palm=None, path=None) -> dict:
|
||||
def _paint_layers(img, chunks, scale, *, land, surface, sidewalk, lots, road, building,
|
||||
outline=None, palm=None, path=None) -> dict:
|
||||
"""Ebene für Ebene über alle Chunks, damit nichts von einem Nachbarchunk überdeckt wird.
|
||||
|
||||
surface ist eine Funktion der Belagsart, building eine Funktion der Gebäudehöhe.
|
||||
palm=None zeichnet keine Palmen; eine Füllung None lässt Beläge oder Straßen aus.
|
||||
Reihenfolge und Füllung entsprechen exakt dem bisherigen Prüfbild, damit dessen
|
||||
Ausgabe bytegleich bleibt.
|
||||
surface ist eine Funktion der Belagsart, building eine Funktion der Gebäudehöhe; beide können
|
||||
None liefern und die Fläche so aussparen (path=None lässt ebenso die ganze Ebene aus). land,
|
||||
sidewalk, lots und road sind dagegen an jedem Aufrufort eine feste Füllung — in der
|
||||
Begehbarkeitsmaske ausdrücklich WALKABLE oder BLOCKED, denn Fahrbahn und Restflächen sollen dort
|
||||
aktiv sperren statt nur wirkungslos zu bleiben: dort fährt Verkehr, kein Fußgänger soll landen.
|
||||
Reihenfolge von hinten nach vorn: Land, Beläge, Wege, Gehweg, Restflächen, Fahrbahn, Gebäude —
|
||||
jede Ebene deckt die vorige ab.
|
||||
|
||||
chunk["markings"] wird hier nie gelesen: bei einem Pixel je Meter wäre eine 10 cm breite Linie
|
||||
ein Drittel Pixel und damit nur Rauschen. chunk["bridges"] ebenfalls nicht mehr: ihre Fahrbahn
|
||||
steckt schon in carriageway, ihr begehbarer Streifen im sidewalk — eine eigene Brücken-Regel
|
||||
braucht es hier nicht mehr.
|
||||
"""
|
||||
stats = {"land": 0, "strassen": 0, "bruecken": 0, "gebaeude": 0, "palmen": 0}
|
||||
stats = {"land": 0, "gehweg": 0, "restflaechen": 0, "strassen": 0, "gebaeude": 0, "palmen": 0}
|
||||
placed = [(chunk, chunk["origin"][0], -chunk["origin"][1]) for chunk in chunks]
|
||||
for chunk, ox, oy in placed:
|
||||
for rings in chunk["land"]:
|
||||
@@ -102,11 +111,17 @@ def _paint_layers(img, chunks, scale, *, land, surface, road, bridge, building,
|
||||
for item in chunk.get("paths", []):
|
||||
_draw(img, item["poly"], ox, oy, path, scale=scale)
|
||||
for chunk, ox, oy in placed:
|
||||
for item in chunk["roads"]:
|
||||
fill = bridge if item["bridge"] else road
|
||||
if fill is not None:
|
||||
_draw(img, item["poly"], ox, oy, fill, scale=scale)
|
||||
stats["bruecken" if item["bridge"] else "strassen"] += 1
|
||||
for item in chunk["sidewalk"]:
|
||||
_draw(img, item["poly"], ox, oy, sidewalk, scale=scale)
|
||||
stats["gehweg"] += 1
|
||||
for chunk, ox, oy in placed:
|
||||
for item in chunk["lots"]:
|
||||
_draw(img, item["poly"], ox, oy, lots, scale=scale)
|
||||
stats["restflaechen"] += 1
|
||||
for chunk, ox, oy in placed:
|
||||
for item in chunk["carriageway"]:
|
||||
_draw(img, item["poly"], ox, oy, road, scale=scale)
|
||||
stats["strassen"] += 1
|
||||
for chunk, ox, oy in placed:
|
||||
for item in chunk["buildings"]:
|
||||
_draw(img, item["poly"], ox, oy, building(item["height"]), outline=outline, scale=scale)
|
||||
@@ -124,7 +139,8 @@ def render(out_dir: Path, dst: Path) -> dict:
|
||||
size = int((SLICE["x1"] - SLICE["x0"]) * SCALE)
|
||||
img = Image.new("RGB", (size, size), WATER)
|
||||
stats = _paint_layers(img, _load_chunks(out_dir), SCALE, land=LAND, surface=SURFACE.__getitem__,
|
||||
road=ROAD, bridge=BRIDGE, building=_building_shade, outline=OUTLINE, palm=PALM, path=PATH)
|
||||
sidewalk=SIDEWALK, lots=ROAD, road=ROAD, building=_building_shade,
|
||||
outline=OUTLINE, palm=PALM, path=PATH)
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
img.save(dst)
|
||||
return stats
|
||||
@@ -133,17 +149,19 @@ def render(out_dir: Path, dst: Path) -> dict:
|
||||
def _walkable_mask(chunks: list[dict], width_m: int) -> Image.Image:
|
||||
"""Begehbarkeitsmaske mit WALKABLE_SCALE, vorsichtig aus einem 1-m-Raster verdichtet.
|
||||
|
||||
Begehbar heißt: die Figur steht dort auf Kollision. Die tragen nur die Landplatte und die
|
||||
Brücken — Straßen und Beläge sind reine Auflagen (DECALS in src/world/streaming.js) —, und
|
||||
Gebäude sind geschlossene Kästen. Ein Maskenpixel gilt nur als begehbar, wenn jeder
|
||||
Begehbar heißt: die Figur steht dort auf Kollision. Das tragen nur die Landplatte und der
|
||||
Gehweg. Fahrbahn und Restflächen sperren aktiv, auch über der Landplatte — dort fährt Verkehr,
|
||||
kein Fußgänger soll dort landen —, und Gebäude sind geschlossene Kästen. Für eine Brücke gibt es
|
||||
keine eigene Regel mehr: ihr begehbarer Streifen liegt im Gehweg, der dort die Landplatte
|
||||
ersetzt, ihre Fahrbahn sperrt wie jede andere. Ein Maskenpixel gilt nur als begehbar, wenn jeder
|
||||
Quadratmeter darunter frei ist. Ein nur teilweise freier Pixel setzte die Figur sonst in eine
|
||||
Wand oder vor die Kaimauer ins Wasser; an den ausgelieferten Daten traf das 4,3 % der Klicks
|
||||
auf Gebäude. Die Landung erfolgt in der Pixelmitte (nearestWalkable in src/ui/mapMath.js),
|
||||
also mindestens 2 m von jedem gesperrten Quadratmeter entfernt.
|
||||
"""
|
||||
fine = Image.new("L", (width_m, width_m), BLOCKED)
|
||||
_paint_layers(fine, chunks, 1.0, land=WALKABLE, surface=lambda _kind: None, road=None,
|
||||
bridge=WALKABLE, building=lambda _height: BLOCKED)
|
||||
_paint_layers(fine, chunks, 1.0, land=WALKABLE, surface=lambda _kind: None,
|
||||
sidewalk=WALKABLE, lots=BLOCKED, road=BLOCKED, building=lambda _height: BLOCKED)
|
||||
step = round(1 / WALKABLE_SCALE)
|
||||
size = width_m // step
|
||||
cells = np.asarray(fine).reshape(size, step, size, step)
|
||||
@@ -157,8 +175,9 @@ def render_map(out_dir: Path, dst_dir: Path) -> dict:
|
||||
width_m = SLICE["x1"] - SLICE["x0"]
|
||||
|
||||
picture = Image.new("RGB", (int(width_m * MAP_SCALE),) * 2, WATER)
|
||||
_paint_layers(picture, chunks, MAP_SCALE, land=LAND, surface=SURFACE.__getitem__, road=ROAD,
|
||||
bridge=BRIDGE, building=_building_shade, outline=OUTLINE, path=PATH)
|
||||
_paint_layers(picture, chunks, MAP_SCALE, land=LAND, surface=SURFACE.__getitem__,
|
||||
sidewalk=SIDEWALK, lots=ROAD, road=ROAD, building=_building_shade,
|
||||
outline=OUTLINE, path=PATH)
|
||||
|
||||
walkable = _walkable_mask(chunks, width_m)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from PIL import Image
|
||||
|
||||
import overview
|
||||
@@ -23,8 +24,8 @@ def _fixture(tmp_path: Path) -> Path:
|
||||
"origin": [SLICE["x0"], -SLICE["y1"]],
|
||||
"land": [[_ring(0, 0, 250, 250)]],
|
||||
"surfaces": [],
|
||||
"roads": [{"poly": [_ring(100, 0, 120, 250)], "bridge": False}],
|
||||
"centerlines": [],
|
||||
"carriageway": [{"poly": [_ring(100, 0, 120, 250)], "klasse": "hauptstrasse"}],
|
||||
"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]],
|
||||
}
|
||||
@@ -57,7 +58,7 @@ def test_walkable_mask_blocks_buildings_and_water(tmp_path):
|
||||
assert mask.mode == "L"
|
||||
assert mask.size == (750, 750)
|
||||
assert mask.getpixel((10, 10)) == overview.BLOCKED # Gebäude bei 40 m / 40 m
|
||||
assert mask.getpixel((27, 31)) == overview.WALKABLE # Straße bei 110 m / 125 m
|
||||
assert mask.getpixel((27, 31)) == overview.BLOCKED # Fahrbahn bei 110 m / 125 m: sperrt jetzt aktiv, auch auf Land
|
||||
assert mask.getpixel((50, 50)) == overview.WALKABLE # Land bei 200 m / 200 m
|
||||
assert mask.getpixel((375, 375)) == overview.BLOCKED # Wasser
|
||||
|
||||
@@ -86,7 +87,11 @@ def test_debug_overview_still_draws_palms(tmp_path):
|
||||
|
||||
|
||||
def test_walkable_mask_counts_only_ground_with_collision(tmp_path):
|
||||
"""Straßen und Beläge sind reine Auflagen: ohne Landplatte darunter trägt nur eine Brücke."""
|
||||
"""Ein Belag traegt weiterhin nicht, ganz ohne Landplatte im Chunk. Fahrbahn und Restflaeche
|
||||
sperren aktiv (siehe test_walkable_mask_blocks_buildings_and_water fuer denselben Befund MIT
|
||||
Landplatte); eine Bruecke fuer sich traegt jetzt nicht mehr - ihr begehbarer Streifen liegt im
|
||||
Gehweg, der die Landplatte an der Stelle ersetzt und hier zeigt, dass er das auch ohne sie tut.
|
||||
"""
|
||||
src = tmp_path / "chunks"
|
||||
src.mkdir()
|
||||
chunk = {
|
||||
@@ -94,9 +99,11 @@ def test_walkable_mask_counts_only_ground_with_collision(tmp_path):
|
||||
"origin": [SLICE["x0"], -SLICE["y1"]],
|
||||
"land": [],
|
||||
"surfaces": [{"poly": [_ring(0, 0, 40, 40)], "kind": "sand"}],
|
||||
"roads": [{"poly": [_ring(100, 0, 120, 40)], "bridge": False},
|
||||
{"poly": [_ring(200, 0, 220, 40)], "bridge": True}],
|
||||
"centerlines": [],
|
||||
"curb": [], "edges": [], "markings": [],
|
||||
"carriageway": [{"poly": [_ring(100, 0, 120, 40)], "klasse": "wohnstrasse"}],
|
||||
"lots": [{"poly": [_ring(140, 0, 160, 40)]}],
|
||||
"bridges": [{"poly": [_ring(180, 0, 200, 40)]}],
|
||||
"sidewalk": [{"poly": [_ring(220, 0, 240, 40)]}],
|
||||
"buildings": [],
|
||||
"palms": [],
|
||||
}
|
||||
@@ -105,10 +112,12 @@ def test_walkable_mask_counts_only_ground_with_collision(tmp_path):
|
||||
dst = tmp_path / "out"
|
||||
overview.render_map(src, dst)
|
||||
mask = Image.open(dst / "map-walkable.png")
|
||||
assert mask.getpixel((5, 5)) == overview.BLOCKED # Sand ohne Land bei 20 m / 20 m: keine Kollision
|
||||
assert mask.getpixel((27, 5)) == overview.BLOCKED # Straße ohne Land bei 110 m / 20 m: keine Kollision
|
||||
assert mask.getpixel((52, 5)) == overview.WALKABLE # Brücke bei 210 m / 20 m
|
||||
assert mask.getpixel((40, 5)) == overview.BLOCKED # Wasser bei 160 m / 20 m
|
||||
assert mask.getpixel((5, 5)) == overview.BLOCKED # Sand ohne Land bei 20 m / 20 m: traegt nicht
|
||||
assert mask.getpixel((27, 5)) == overview.BLOCKED # Fahrbahn ohne Land bei 108 m / 20 m: sperrt aktiv
|
||||
assert mask.getpixel((37, 5)) == overview.BLOCKED # Restflaeche ohne Land bei 148 m / 20 m: sperrt aktiv
|
||||
assert mask.getpixel((42, 5)) == overview.BLOCKED # Wasser bei 168 m / 20 m
|
||||
assert mask.getpixel((47, 5)) == overview.BLOCKED # Bruecke ohne Land bei 188 m / 20 m: traegt nicht mehr
|
||||
assert mask.getpixel((57, 5)) == overview.WALKABLE # Gehweg bei 228 m / 20 m: traegt jetzt auch ohne Land
|
||||
|
||||
|
||||
def test_walkable_mask_blocks_pixels_that_are_only_partly_free(tmp_path):
|
||||
@@ -120,8 +129,7 @@ def test_walkable_mask_blocks_pixels_that_are_only_partly_free(tmp_path):
|
||||
"origin": [SLICE["x0"], -SLICE["y1"]],
|
||||
"land": [[_ring(0, 0, 102, 250)]], # Küste bei x = 102 m, mitten in Pixel 25
|
||||
"surfaces": [],
|
||||
"roads": [],
|
||||
"centerlines": [],
|
||||
"carriageway": [], "curb": [], "sidewalk": [], "markings": [], "lots": [], "bridges": [], "edges": [],
|
||||
"buildings": [{"poly": [_ring(38, 40, 50, 50)], "height": 8.0, "source": "district", "speculative": False}],
|
||||
"palms": [],
|
||||
}
|
||||
@@ -136,12 +144,21 @@ def test_walkable_mask_blocks_pixels_that_are_only_partly_free(tmp_path):
|
||||
assert mask.getpixel((8, 11)) == overview.WALKABLE # 32..36 m: frei
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="public/data/vice-a ist bis Task 16 im alten Chunk-Format; siehe Docstring")
|
||||
def test_committed_map_files_match_the_committed_chunks(tmp_path):
|
||||
"""Kartenbild, Maske und map.json müssen aus genau den ausgelieferten Chunks stammen.
|
||||
|
||||
Ändert ein Pipeline-Lauf die Chunks, ohne dass overview.py läuft, zeigte die Minimap eine alte
|
||||
Welt und landete nach einer alten Maske. Die Laufzeitprüfung über manifest_sha256 bemerkt das
|
||||
erst im Browser, dieser Test schon beim Testlauf.
|
||||
|
||||
Übersprungen bis Task 16: render_map() liest seit dieser Aufgabe (15) nur noch carriageway,
|
||||
sidewalk und lots statt roads und centerlines, aber public/data/vice-a bleibt laut Plan (Ruling
|
||||
zu Task 12 in docs/superpowers/sdd/2026-09-16-strassen/progress.md) bis Task 16 im alten Format —
|
||||
der Pipeline-Lauf in Task 12 schreibt die Daten nur zur Messung und stellt sie danach per
|
||||
git checkout wieder her. Bis Task 16 die Welt neu erzeugt und committet, wirft dieser Test einen
|
||||
KeyError statt eines aussagekräftigen Fehlschlags; Task 16 muss dieses Skip mit den neuen Daten
|
||||
zusammen entfernen.
|
||||
"""
|
||||
shipped = Path(overview.__file__).resolve().parents[2] / "public" / "data" / "vice-a"
|
||||
overview.render_map(shipped, tmp_path)
|
||||
@@ -163,8 +180,7 @@ def test_map_picture_draws_paths_and_the_new_surface_kinds(tmp_path):
|
||||
{"poly": [_ring(50, 0, 90, 40)], "kind": "court"},
|
||||
{"poly": [_ring(0, 50, 40, 90)], "kind": "pool"},
|
||||
{"poly": [_ring(50, 50, 90, 90)], "kind": "parking"}],
|
||||
"roads": [],
|
||||
"centerlines": [],
|
||||
"carriageway": [], "curb": [], "sidewalk": [], "markings": [], "lots": [], "bridges": [], "edges": [],
|
||||
"buildings": [],
|
||||
"palms": [],
|
||||
"paths": [{"poly": [_ring(100, 0, 104, 250)]}],
|
||||
@@ -180,3 +196,71 @@ def test_map_picture_draws_paths_and_the_new_surface_kinds(tmp_path):
|
||||
assert picture.getpixel((70, 70)) == overview.ROAD # Parkplatz in Straßenfarbe
|
||||
assert picture.getpixel((102, 125)) == overview.PATH
|
||||
assert Image.open(dst / "map-walkable.png").getpixel((25, 31)) == overview.WALKABLE # Weg auf Land
|
||||
|
||||
|
||||
def _strassen_fixture(tmp_path: Path) -> Path:
|
||||
"""Chunk A1 mit den Schichten aus Teil 2. Aufbau wie _fixture() oben in dieser Datei:
|
||||
|
||||
Bei scale 1 fallen die chunk-lokalen Koordinaten von chunk_0_0 genau auf die Kartenpixel.
|
||||
|
||||
Der Gehweg ist hier 12 statt 3 m breit (anders als im Planentwurf): _draw() zeichnet Fuellung
|
||||
und Umriss in derselben Farbe (Kommentar dort erklaert, warum), damit angrenzende Flaechen keine
|
||||
Lueckenpixel lassen - an der gemeinsamen Kante mit der Fahrbahn bei x=112 gewinnt dadurch je nach
|
||||
Zeichenreihenfolge die eine oder die andere Flaeche die Grenzspalte. Bei 4 m je Maskenpixel zaehlt
|
||||
eine Zelle nur als frei, wenn alle 16 Meterzellen frei sind; ein 3 m breiter Streifen ist schmaler
|
||||
als eine Maskenzelle und kann darum nie eine ganz umschliessen, unabhaengig von der Rundung.
|
||||
"""
|
||||
chunk = {
|
||||
"chunk": [0, 0],
|
||||
"origin": [SLICE["x0"], -SLICE["y1"]],
|
||||
"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"}],
|
||||
"sidewalk": [{"poly": [_ring(112, 0, 124, 250)]}],
|
||||
"markings": [{"poly": [_ring(105, 0, 105.1, 250)], "art": "gelb_doppelt"}],
|
||||
}
|
||||
(tmp_path / "chunk_0_0.json").write_text(json.dumps(chunk))
|
||||
(tmp_path / "manifest.json").write_text('{"slice": "vice-a"}\n')
|
||||
return tmp_path
|
||||
|
||||
|
||||
def test_karte_zeichnet_fahrbahn_gehweg_und_restflaeche(tmp_path):
|
||||
src = tmp_path / "chunks"
|
||||
src.mkdir()
|
||||
_strassen_fixture(src)
|
||||
dst = tmp_path / "out"
|
||||
dst.mkdir()
|
||||
overview.render_map(src, dst)
|
||||
bild = Image.open(dst / "map.png").convert("RGB")
|
||||
assert bild.getpixel((105, 10)) == overview.ROAD, "die Fahrbahn"
|
||||
assert bild.getpixel((113, 10)) == overview.SIDEWALK, "der Gehweg daneben"
|
||||
assert bild.getpixel((30, 170)) == overview.ROAD, "die Restflaeche im Strassenton"
|
||||
|
||||
|
||||
def test_markierung_wird_nicht_gezeichnet(tmp_path):
|
||||
"""Bei 1 px je 3 m waere eine 10-cm-Linie ein Drittel Pixel und damit nur Rauschen."""
|
||||
src = tmp_path / "chunks"
|
||||
src.mkdir()
|
||||
_strassen_fixture(src)
|
||||
dst = tmp_path / "out"
|
||||
dst.mkdir()
|
||||
overview.render_map(src, dst)
|
||||
bild = Image.open(dst / "map.png").convert("RGB")
|
||||
# Genau auf der Markierung: trotzdem Fahrbahnfarbe, kein Gelb.
|
||||
assert bild.getpixel((105, 120)) == overview.ROAD
|
||||
|
||||
|
||||
def test_gehweg_ist_begehbar_die_fahrbahn_nicht(tmp_path):
|
||||
src = tmp_path / "chunks"
|
||||
src.mkdir()
|
||||
_strassen_fixture(src)
|
||||
dst = tmp_path / "out"
|
||||
dst.mkdir()
|
||||
overview.render_map(src, dst)
|
||||
maske = Image.open(dst / "map-walkable.png").convert("L")
|
||||
step = round(1 / overview.WALKABLE_SCALE)
|
||||
# x=117 statt 113: 4 m Abstand zu jeder Kante des jetzt 12 m breiten Gehwegs, genau das Muster
|
||||
# der Fahrbahnprobe unten (x=105, 4 m Abstand zu deren Kanten 100/112) - keine Grenzzelle.
|
||||
assert maske.getpixel((117 // step, 10 // step)) == overview.WALKABLE, "Gehweg"
|
||||
assert maske.getpixel((105 // step, 10 // step)) == overview.BLOCKED, "Fahrbahn"
|
||||
Reference in new issue
Block a user