feat(extract): Koordinaten, Kachel-Download und Mosaik
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2280617d9e
commit
3d112e98cd
7 files changed
+596
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,28 @@
|
||||
"""Umrechnung zwischen gtadb-Weltkoordinaten, Kacheln und Mosaikpixeln.
|
||||
|
||||
Spiegelt src/world/coords.js. Beide Seiten müssen dieselben Werte liefern.
|
||||
"""
|
||||
TILE_PX = 256
|
||||
WORLD_HALF = 16384
|
||||
CHUNK_SIZE = 250
|
||||
SLICE = {"x0": -1000, "x1": 2000, "y0": -500, "y1": 2500}
|
||||
|
||||
|
||||
def tile_for_world(x: float, y: float) -> tuple[int, int]:
|
||||
return (int((WORLD_HALF - y) // TILE_PX), int((x + WORLD_HALF) // TILE_PX))
|
||||
|
||||
|
||||
def slice_tiles() -> list[tuple[int, int]]:
|
||||
r0, c0 = tile_for_world(SLICE["x0"], SLICE["y1"])
|
||||
r1, c1 = tile_for_world(SLICE["x1"] - 1, SLICE["y0"] + 1)
|
||||
return [(r, c) for r in range(r0, r1 + 1) for c in range(c0, c1 + 1)]
|
||||
|
||||
|
||||
def mosaic_origin_px() -> tuple[int, int]:
|
||||
"""Pixelversatz der Ausschnittsecke innerhalb des ungeschnittenen Kachelrasters."""
|
||||
r0, c0 = tile_for_world(SLICE["x0"], SLICE["y1"])
|
||||
return (SLICE["x0"] + WORLD_HALF - c0 * TILE_PX, (WORLD_HALF - SLICE["y1"]) - r0 * TILE_PX)
|
||||
|
||||
|
||||
def pixel_to_world(px: int, py: int) -> tuple[float, float]:
|
||||
return (SLICE["x0"] + px, SLICE["y1"] - py)
|
||||
@@ -0,0 +1,59 @@
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from .coords import SLICE, TILE_PX, mosaic_origin_px, slice_tiles
|
||||
|
||||
COMMIT = "4093e8c874e480d47c38b37fe7a404f8b0c5275e"
|
||||
BASE = f"https://raw.githubusercontent.com/rolux/gtadb.org/{COMMIT}/maps/tiles/6/yanis%2C15/5"
|
||||
UA = {"User-Agent": "leonida-extract/0.1 (local hobby project)"}
|
||||
|
||||
|
||||
def download_tiles(cache: Path) -> None:
|
||||
"""Lädt alle Kacheln des Ausschnitts; vorhandene werden übersprungen."""
|
||||
cache.mkdir(parents=True, exist_ok=True)
|
||||
missing = []
|
||||
for row, col in slice_tiles():
|
||||
name = f"5,{row},{col}.jpg"
|
||||
dst = cache / name
|
||||
if dst.exists():
|
||||
continue
|
||||
for attempt in range(3):
|
||||
try:
|
||||
req = urllib.request.Request(f"{BASE}/{name}", headers=UA)
|
||||
dst.write_bytes(urllib.request.urlopen(req, timeout=30).read())
|
||||
break
|
||||
except (urllib.error.URLError, TimeoutError):
|
||||
time.sleep(2 ** attempt)
|
||||
else:
|
||||
missing.append(name)
|
||||
if missing:
|
||||
raise RuntimeError(f"{len(missing)} Kacheln fehlen: {missing[:5]} ...")
|
||||
|
||||
|
||||
def assemble(load_tile) -> np.ndarray:
|
||||
"""Setzt die Kacheln zusammen und schneidet auf den Ausschnitt zu."""
|
||||
tiles = slice_tiles()
|
||||
r0 = min(r for r, _ in tiles)
|
||||
c0 = min(c for _, c in tiles)
|
||||
rows = max(r for r, _ in tiles) - r0 + 1
|
||||
cols = max(c for _, c in tiles) - c0 + 1
|
||||
full = np.zeros((rows * TILE_PX, cols * TILE_PX, 3), dtype=np.uint8)
|
||||
for row, col in tiles:
|
||||
y, x = (row - r0) * TILE_PX, (col - c0) * TILE_PX
|
||||
full[y:y + TILE_PX, x:x + TILE_PX] = load_tile(row, col)
|
||||
ox, oy = mosaic_origin_px()
|
||||
w = SLICE["x1"] - SLICE["x0"]
|
||||
h = SLICE["y1"] - SLICE["y0"]
|
||||
return full[oy:oy + h, ox:ox + w]
|
||||
|
||||
|
||||
def load_mosaic(cache: Path) -> np.ndarray:
|
||||
def load_tile(row: int, col: int) -> np.ndarray:
|
||||
with Image.open(cache / f"5,{row},{col}.jpg") as img:
|
||||
return np.asarray(img.convert("RGB"))
|
||||
return assemble(load_tile)
|
||||
Reference in new issue
Block a user