Mettre à jour la pyramide pendant le rendu et réduire son stockage

Le pipeline régénère l'inventaire après chaque dalle par défaut (tous
lanceurs, sauf --no-index), avec une passe différée quand l'anti-rebond
ignore une dalle. La carte surveille l'inventaire toutes les 10 s et place
les tuiles des dalles modifiées en tête de file de maintenance : la
pyramide suit le rendu au lieu d'attendre la fin du lot.

Stockage réduit pour le Raspberry Pi : seuls les niveaux standard pairs
jusqu'à z16 sont écrits sur disque, en AVIF ; les autres, dont le niveau
le plus fin (~75 % de la pyramide), sont rendus à la volée avec un cache
mémoire, y compris en mode cache seule. L'interface ne demande que les
niveaux pairs et réduit ceux du niveau supérieur aux zooms impairs.
Mesuré sur 8 dalles : ~24 Mo de pyramide avant, 1,2 Mo après.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Antoine Jacquin
2026-09-27 02:31:11 +02:00
parent f836408d6f
commit 434b7f12fd
11 changed files with 299 additions and 35 deletions

View File

@ -28,6 +28,13 @@ TILE_SIZE = 256 # taille canonique (OSM/XYZ) ; @2x → 512
TILE_MIN_Z = 5
TILE_MAX_NATIVE_Z = 19 # 0,2 m/px ≈ résolution du z19 à la latitude 47°
TILE_DIRNAME = "index_xyz" # cache disque, sous le dossier de sortie
# Stockage réduit (petite machine) : niveaux exprimés en numérotation OSM
# standard (tuile 256 px ; une tuile @2x de niveau z vaut une 256 px de z+1).
# Seuls les niveaux PAIRS jusqu'à LIDAR_TILE_CACHE_MAX_Z sont écrits sur
# disque ; les autres sont rendus à la volée (cache mémoire borné). Mesuré sur
# 8 dalles : ~24 Mo → ~1 Mo de pyramide, le niveau le plus fin pesant ~75 %.
TILE_CACHE_MAX_Z = int(os.environ.get("LIDAR_TILE_CACHE_MAX_Z", "16") or 16)
TILE_EVEN_LEVELS = os.environ.get("LIDAR_TILE_EVEN_LEVELS", "1").strip() != "0"
WEBP_QUALITY = 78
AVIF_QUALITY = 60
AVIF_SPEED = 9 # encodage rapide (cf. rendering.AVIF_SPEED : ×7, +3 % de taille)
@ -745,6 +752,41 @@ def _write_atomic(path, data):
tmp.unlink(missing_ok=True)
def zoom_cached(z, scale=1):
"""Vrai si les tuiles de ce niveau sont stockées sur disque."""
zc = z + (1 if scale == 2 else 0)
if zc > TILE_CACHE_MAX_Z:
return False
return not (TILE_EVEN_LEVELS and zc % 2)
# Tuiles rendues à la volée (niveaux non stockés) : cache mémoire borné,
# pour qu'un aller-retour de navigation ne recalcule pas tout.
MEMORY_CACHE_BYTES = int(os.environ.get("LIDAR_TILE_MEMORY_CACHE_MB", "64")) * 1024 * 1024
_mem_tiles = OrderedDict()
_mem_tiles_bytes = [0]
_mem_tiles_lock = threading.Lock()
def _mem_get(key):
with _mem_tiles_lock:
data = _mem_tiles.get(key)
if data is not None:
_mem_tiles.move_to_end(key)
return data
def _mem_put(key, data):
with _mem_tiles_lock:
if key in _mem_tiles:
return
_mem_tiles[key] = data
_mem_tiles_bytes[0] += len(data)
while _mem_tiles_bytes[0] > MEMORY_CACHE_BYTES and _mem_tiles:
_, old = _mem_tiles.popitem(last=False)
_mem_tiles_bytes[0] -= len(old)
def zoom_supported(z, scale=1):
"""Vrai si le zoom demandé est dans la plage rendue par le serveur."""
max_z = TILE_MAX_NATIVE_Z - (1 if scale == 2 else 0)
@ -761,9 +803,10 @@ def get_tile(output_dir, layer, z, x, y, scale=1, fmt="png"):
"""
cache = tile_cache_path(output_dir, layer, z, x, y, scale, fmt)
empty = cache.with_suffix(".empty")
stored = zoom_cached(z, scale)
sources = _contributing(output_dir, layer, z, x, y, scale)
if not sources:
if not empty.exists():
if stored and not empty.exists():
_write_atomic(empty, b"")
return None
@ -772,6 +815,17 @@ def get_tile(output_dir, layer, z, x, y, scale=1, fmt="png"):
m = src.mtime()
if m and m > newest:
newest = m
if not stored:
# Niveau non stocké : rendu à la volée, cache mémoire seulement.
key = (str(output_dir), layer, z, x, y, scale, fmt, newest)
data = _mem_get(key)
if data is None:
img = render_tile(output_dir, layer, z, x, y, scale)
if img is None:
return None
data = _encode(img, fmt)
_mem_put(key, data)
return data
try:
if cache.stat().st_mtime >= newest:
return cache.read_bytes()