112 lines
4.1 KiB
Python
112 lines
4.1 KiB
Python
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from PIL import Image
|
|
|
|
import overview
|
|
from leonida.coords import SLICE
|
|
|
|
|
|
def _ring(x0, z0, x1, z1):
|
|
return [[x0, z0], [x1, z0], [x1, z1], [x0, z1], [x0, z0]]
|
|
|
|
|
|
def _fixture(tmp_path: Path) -> Path:
|
|
"""Ein einziger Chunk A1 (Nordwest): Land, eine Straße, ein Gebäude, eine Palme.
|
|
|
|
Chunk-lokale Koordinaten von chunk_0_0 fallen bei scale 1 genau auf die Kartenpixel,
|
|
weil der Chunk-Ursprung die Nordwest-Ecke des Ausschnitts ist.
|
|
"""
|
|
chunk = {
|
|
"chunk": [0, 0],
|
|
"origin": [SLICE["x0"], -SLICE["y1"]],
|
|
"land": [[_ring(0, 0, 250, 250)]],
|
|
"surfaces": [],
|
|
"roads": [{"poly": [_ring(100, 0, 120, 250)], "bridge": False}],
|
|
"centerlines": [],
|
|
"buildings": [{"poly": [_ring(20, 20, 60, 60)], "height": 12.0, "source": "district", "speculative": False}],
|
|
"palms": [[200.0, 200.0]],
|
|
}
|
|
(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_map_picture_has_one_pixel_per_metre_and_no_palms(tmp_path):
|
|
src = tmp_path / "chunks"
|
|
src.mkdir()
|
|
_fixture(src)
|
|
dst = tmp_path / "out"
|
|
overview.render_map(src, dst)
|
|
picture = Image.open(dst / "map.png")
|
|
assert picture.size == (3000, 3000)
|
|
assert picture.getpixel((110, 125)) == overview.ROAD
|
|
assert picture.getpixel((40, 40)) == overview._building_shade(12.0)
|
|
assert picture.getpixel((200, 200)) == overview.LAND # Palme wird nicht gezeichnet
|
|
assert picture.getpixel((1500, 1500)) == overview.WATER
|
|
|
|
|
|
def test_walkable_mask_blocks_buildings_and_water(tmp_path):
|
|
src = tmp_path / "chunks"
|
|
src.mkdir()
|
|
_fixture(src)
|
|
dst = tmp_path / "out"
|
|
overview.render_map(src, dst)
|
|
mask = Image.open(dst / "map-walkable.png")
|
|
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((50, 50)) == overview.WALKABLE # Land bei 200 m / 200 m
|
|
assert mask.getpixel((375, 375)) == overview.BLOCKED # Wasser
|
|
|
|
|
|
def test_map_json_carries_scale_slice_and_manifest_fingerprint(tmp_path):
|
|
src = tmp_path / "chunks"
|
|
src.mkdir()
|
|
_fixture(src)
|
|
dst = tmp_path / "out"
|
|
overview.render_map(src, dst)
|
|
info = json.loads((dst / "map.json").read_text())
|
|
assert info["scale"] == 1.0
|
|
assert info["walkable_scale"] == 0.25
|
|
assert info["slice"] == SLICE
|
|
assert info["manifest_sha256"] == hashlib.sha256((src / "manifest.json").read_bytes()).hexdigest()
|
|
|
|
|
|
def test_debug_overview_still_draws_palms(tmp_path):
|
|
src = tmp_path / "chunks"
|
|
src.mkdir()
|
|
_fixture(src)
|
|
dst = tmp_path / "debug.png"
|
|
stats = overview.render(src, dst)
|
|
assert stats["palmen"] == 1
|
|
assert Image.open(dst).getpixel((100, 100)) == overview.PALM # 200 m bei 0,5 px/m
|
|
|
|
|
|
def test_walkable_mask_marks_roads_bridges_and_surfaces_without_land(tmp_path):
|
|
"""Jede begehbare Ebene für sich, ohne Land darunter: sonst verdeckt das Land einen Fehler."""
|
|
src = tmp_path / "chunks"
|
|
src.mkdir()
|
|
chunk = {
|
|
"chunk": [0, 0],
|
|
"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": [],
|
|
"buildings": [],
|
|
"palms": [],
|
|
}
|
|
(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)
|
|
mask = Image.open(dst / "map-walkable.png")
|
|
assert mask.getpixel((5, 5)) == overview.WALKABLE # Sand bei 20 m / 20 m
|
|
assert mask.getpixel((27, 5)) == overview.WALKABLE # Straße bei 110 m / 20 m
|
|
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
|