feat(extract): Minimap zeichnet Parkwege, Felder, Plätze, Pool und Parkplatz

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
knsoandClaude Sonnet 5 committed 2026-09-16 08:20:44 +02:00
1 parent 0e11c54338
commit e675e62a16
2 files changed
+41 -4

No files matched your search

+11 -4
View File
@@ -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) WALKABLE_SCALE = 0.25 # Pixel je Meter, map-walkable.png (4 m je Pixel)
WATER = (36, 72, 104) WATER = (36, 72, 104)
LAND = (150, 162, 120) LAND = (150, 162, 120)
SURFACE = {"sand": (226, 210, 150), "park": (120, 160, 90), "urban": (176, 176, 172)}
ROAD = (74, 74, 78) 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) BRIDGE = (196, 92, 92)
PALM = (40, 190, 70) PALM = (40, 190, 70)
OUTLINE = (30, 30, 30) 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"))] 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. """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. 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"]) fill = surface(item["kind"])
if fill is not None: if fill is not None:
_draw(img, item["poly"], ox, oy, fill, scale=scale) _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 chunk, ox, oy in placed:
for item in chunk["roads"]: for item in chunk["roads"]:
fill = bridge if item["bridge"] else road 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) size = int((SLICE["x1"] - SLICE["x0"]) * SCALE)
img = Image.new("RGB", (size, size), WATER) img = Image.new("RGB", (size, size), WATER)
stats = _paint_layers(img, _load_chunks(out_dir), SCALE, land=LAND, surface=SURFACE.__getitem__, 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) dst.parent.mkdir(parents=True, exist_ok=True)
img.save(dst) img.save(dst)
return stats 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) picture = Image.new("RGB", (int(width_m * MAP_SCALE),) * 2, WATER)
_paint_layers(picture, chunks, MAP_SCALE, land=LAND, surface=SURFACE.__getitem__, road=ROAD, _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) walkable = _walkable_mask(chunks, width_m)
+30
View File
@@ -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.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 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()) 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