From 857121e5fb37546ec54515ea8f70fb4c4dca34c4 Mon Sep 17 00:00:00 2001 From: KNSO Date: Sat, 12 Sep 2026 17:28:35 +0200 Subject: [PATCH] =?UTF-8?q?tools:=20L=C3=B6cher=20in=20der=20Gesamtansicht?= =?UTF-8?q?=20korrekt=20zusammensetzen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ein Loch mit Wasserfarbe zu übermalen war falsch — der Innenhof eines Blocks liegt auf Stadtfläche, nicht im Wasser. Die Fläche wird jetzt über eine Maske eingesetzt, sodass durch die Löcher die Ebene darunter bleibt. Co-Authored-By: Claude Opus 5 --- tools/extract/overview.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/tools/extract/overview.py b/tools/extract/overview.py index 7f6e0d0..dc2e8cf 100644 --- a/tools/extract/overview.py +++ b/tools/extract/overview.py @@ -30,17 +30,32 @@ def _to_px(ring, ox, oy): return [((x + ox - SLICE["x0"]) * SCALE, (SLICE["y1"] - (oy - z)) * SCALE) for x, z in ring] -def _draw(draw, rings, ox, oy, fill, outline=None): - """Außenring füllen, Löcher wieder mit Wasserfarbe ausstanzen.""" - draw.polygon(_to_px(rings[0], ox, oy), fill=fill, outline=outline) +def _draw(img, rings, ox, oy, fill, outline=None): + """Fläche über eine Maske einsetzen, damit durch die Löcher die Ebene darunter bleibt. + + Ein Loch mit Wasserfarbe zu übermalen wäre falsch: der Innenhof eines Blocks liegt auf + Stadtfläche, nicht im Wasser. + """ + outer = _to_px(rings[0], ox, oy) + xs = [p[0] for p in outer] + ys = [p[1] for p in outer] + x0, y0 = int(min(xs)) - 1, int(min(ys)) - 1 + x1, y1 = int(max(xs)) + 2, int(max(ys)) + 2 + if x1 <= x0 or y1 <= y0: + return + mask = Image.new("L", (x1 - x0, y1 - y0), 0) + md = ImageDraw.Draw(mask) + md.polygon([(x - x0, y - y0) for x, y in outer], fill=255, outline=255) for hole in rings[1:]: - draw.polygon(_to_px(hole, ox, oy), fill=WATER) + md.polygon([(x - x0, y - y0) for x, y in _to_px(hole, ox, oy)], fill=0) + img.paste(fill, (x0, y0), mask) + if outline: + ImageDraw.Draw(img).polygon(outer, fill=None, outline=outline) def render(out_dir: Path, dst: Path) -> dict: size = int((SLICE["x1"] - SLICE["x0"]) * SCALE) img = Image.new("RGB", (size, size), WATER) - draw = ImageDraw.Draw(img) chunks = [json.loads(f.read_text()) for f in sorted(out_dir.glob("chunk_*.json"))] stats = {"land": 0, "strassen": 0, "bruecken": 0, "gebaeude": 0, "palmen": 0} @@ -48,27 +63,27 @@ def render(out_dir: Path, dst: Path) -> dict: for chunk in chunks: ox, oy = chunk["origin"][0], -chunk["origin"][1] for rings in chunk["land"]: - _draw(draw, rings, ox, oy, LAND) + _draw(img, rings, ox, oy, LAND) stats["land"] += 1 for chunk in chunks: ox, oy = chunk["origin"][0], -chunk["origin"][1] for surface in chunk["surfaces"]: - _draw(draw, surface["poly"], ox, oy, SURFACE[surface["kind"]]) + _draw(img, surface["poly"], ox, oy, SURFACE[surface["kind"]]) for chunk in chunks: ox, oy = chunk["origin"][0], -chunk["origin"][1] for road in chunk["roads"]: - _draw(draw, road["poly"], ox, oy, BRIDGE if road["bridge"] else ROAD) + _draw(img, road["poly"], ox, oy, BRIDGE if road["bridge"] else ROAD) stats["bruecken" if road["bridge"] else "strassen"] += 1 for chunk in chunks: ox, oy = chunk["origin"][0], -chunk["origin"][1] for building in chunk["buildings"]: t = min(1.0, building["height"] / TALL) shade = (int(70 + 170 * t), int(70 + 120 * t), int(80 + 60 * t)) - _draw(draw, building["poly"], ox, oy, shade, outline=(30, 30, 30)) + _draw(img, building["poly"], ox, oy, shade, outline=(30, 30, 30)) stats["gebaeude"] += 1 for x, z in chunk["palms"]: px, py = _to_px([(x, z)], ox, oy)[0] - draw.ellipse([px - 1.6, py - 1.6, px + 1.6, py + 1.6], fill=PALM) + ImageDraw.Draw(img).ellipse([px - 1.6, py - 1.6, px + 1.6, py + 1.6], fill=PALM) stats["palmen"] += 1 dst.parent.mkdir(parents=True, exist_ok=True)