diff --git a/tools/extract/overview.py b/tools/extract/overview.py index 3eb2304..2488323 100644 --- a/tools/extract/overview.py +++ b/tools/extract/overview.py @@ -27,8 +27,11 @@ MAP_SCALE = 1.0 # Pixel je Meter, map.png WALKABLE_SCALE = 0.25 # Pixel je Meter, map-walkable.png (4 m je Pixel) WATER = (36, 72, 104) LAND = (150, 162, 120) -SURFACE = {"sand": (226, 210, 150), "park": (120, 160, 90), "urban": (176, 176, 172)} ROAD = (74, 74, 78) +# 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) @@ -75,7 +78,7 @@ 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) -> dict: +def _paint_layers(img, chunks, scale, *, land, surface, road, bridge, 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. @@ -94,6 +97,10 @@ def _paint_layers(img, chunks, scale, *, land, surface, road, bridge, building, fill = surface(item["kind"]) if fill is not None: _draw(img, item["poly"], ox, oy, fill, scale=scale) + if path is not None: + for chunk, ox, oy in placed: + 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 @@ -117,7 +124,7 @@ 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) + road=ROAD, bridge=BRIDGE, building=_building_shade, outline=OUTLINE, palm=PALM, path=PATH) dst.parent.mkdir(parents=True, exist_ok=True) img.save(dst) return stats @@ -151,7 +158,7 @@ def render_map(out_dir: Path, dst_dir: Path) -> dict: 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) + bridge=BRIDGE, building=_building_shade, outline=OUTLINE, path=PATH) walkable = _walkable_mask(chunks, width_m) diff --git a/tools/extract/tests/test_overview.py b/tools/extract/tests/test_overview.py index 44844a7..c1d0edd 100644 --- a/tools/extract/tests/test_overview.py +++ b/tools/extract/tests/test_overview.py @@ -150,3 +150,33 @@ def test_committed_map_files_match_the_committed_chunks(tmp_path): assert (fresh.size, fresh.mode) == (committed.size, committed.mode), name assert fresh.tobytes() == committed.tobytes(), f"{name} ist veraltet: cd tools/extract && uv run python overview.py" assert json.loads((tmp_path / "map.json").read_text()) == json.loads((shipped / "map.json").read_text()) + + +def test_map_picture_draws_paths_and_the_new_surface_kinds(tmp_path): + src = tmp_path / "chunks" + src.mkdir() + chunk = { + "chunk": [0, 0], + "origin": [SLICE["x0"], -SLICE["y1"]], + "land": [[_ring(0, 0, 250, 250)]], + "surfaces": [{"poly": [_ring(0, 0, 40, 40)], "kind": "field"}, + {"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": [], + "buildings": [], + "palms": [], + "paths": [{"poly": [_ring(100, 0, 104, 250)]}], + } + (src / "chunk_0_0.json").write_text(json.dumps(chunk)) + (src / "manifest.json").write_text('{"slice": "vice-a"}\n') + dst = tmp_path / "out" + overview.render_map(src, dst) + picture = Image.open(dst / "map.png") + assert picture.getpixel((20, 20)) == overview.SURFACE["field"] + assert picture.getpixel((70, 20)) == overview.SURFACE["court"] + assert picture.getpixel((20, 70)) == overview.SURFACE["pool"] + 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