Borner la mémoire de la carte (OOM du Pi) et rallumer le relief orienté par défaut
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@ -901,8 +901,14 @@ def map_meta():
|
||||
infos = _layer_infos()
|
||||
keys = [i["key"] for i in infos]
|
||||
saved = _load_defaults()
|
||||
# Configuration figée depuis la carte, sinon réglages du registre (index.py)
|
||||
default_layers = saved["on"] if saved else default_layers_present(keys)
|
||||
# Configuration figée depuis la carte, sinon réglages du registre (index.py).
|
||||
# Couches figées toutes disparues (ex. passage au seul relief orienté) :
|
||||
# registre, sinon un nouveau navigateur arrive sur une carte sans LiDAR.
|
||||
saved_on = [k for k in saved["on"] if k in keys] if saved else []
|
||||
if saved and (saved_on or not saved["on"]):
|
||||
default_layers = saved_on
|
||||
else:
|
||||
default_layers = default_layers_present(keys)
|
||||
default_opacity = saved["opacity"] if saved else dict(DEFAULT_OPACITY)
|
||||
default_blend = saved["blend"] if saved else dict(DEFAULT_BLEND)
|
||||
return {
|
||||
|
||||
@ -320,6 +320,31 @@ def test_defaults_roundtrip(tmp_path, monkeypatch):
|
||||
assert mapserve.map_meta()["defaults_saved"] is False
|
||||
|
||||
|
||||
def test_defaults_obsolete_layers_fall_back(tmp_path, monkeypatch):
|
||||
"""Défauts figés sur des couches qui ne sont plus servies : registre.
|
||||
|
||||
Sinon un nouveau navigateur arrive sur une carte sans aucune couche
|
||||
LiDAR allumée (cas de la prod après le passage au seul relief orienté).
|
||||
"""
|
||||
import json
|
||||
import lidar_pipeline.mapserve as mapserve
|
||||
_setup(tmp_path, monkeypatch, layers=("relief_oriente",))
|
||||
defaults = tmp_path / ".map-defaults.json"
|
||||
monkeypatch.setattr(mapserve, "DEFAULTS_FILE", defaults)
|
||||
defaults.write_text(json.dumps({
|
||||
"order": ["aspect", "positive_openness"],
|
||||
"on": ["aspect", "positive_openness"], "opacity": {}, "blend": {},
|
||||
"base": {"on": True, "opacity": 0.85, "dark": True},
|
||||
"stack_blend": "normal"}), encoding="utf-8")
|
||||
meta = mapserve.map_meta()
|
||||
assert meta["default_layers"] == ["relief_oriente"]
|
||||
# Tout éteint volontairement (couches connues) : respecté
|
||||
defaults.write_text(json.dumps({
|
||||
"order": ["relief_oriente"], "on": [], "opacity": {}, "blend": {},
|
||||
"base": {}, "stack_blend": "normal"}), encoding="utf-8")
|
||||
assert mapserve.map_meta()["default_layers"] == []
|
||||
|
||||
|
||||
def test_defaults_sanitised(tmp_path, monkeypatch):
|
||||
"""Couches inconnues écartées, opacités bornées, fusions validées."""
|
||||
import lidar_pipeline.mapserve as mapserve
|
||||
|
||||
@ -361,6 +361,27 @@ def test_source_cache_respects_memory_budget(tmp_path, monkeypatch):
|
||||
assert tiles._source_cache == {}
|
||||
|
||||
|
||||
def test_source_cache_holds_plain_decoded_images(tmp_path):
|
||||
"""Le cache ne retient que les pixels : ni fichier ouvert ni décodeur.
|
||||
|
||||
Une image AVIF ouverte garde son décodeur (tampons libavif/dav1d) :
|
||||
~43 Mo retenus par quadrant 2500² au lieu de 25 — le conteneur du Pi
|
||||
(1 Go) était tué par l'OOM killer en navigation à fort zoom. Le budget
|
||||
compte 4 octets/pixel : PIL stocke le RGB sur 32 bits.
|
||||
"""
|
||||
from PIL import Image
|
||||
from lidar_pipeline import tiles
|
||||
tiles.clear_source_cache()
|
||||
f = tmp_path / "src.png"
|
||||
Image.new("RGB", (40, 30), (10, 20, 30)).save(str(f))
|
||||
img = tiles._open_source(str(f), f.stat().st_mtime)
|
||||
assert type(img) is Image.Image
|
||||
assert getattr(img, "fp", None) is None
|
||||
assert img.info["_bytes"] == 40 * 30 * 4
|
||||
assert img.getpixel((0, 0))[:3] == (10, 20, 30)
|
||||
tiles.clear_source_cache()
|
||||
|
||||
|
||||
def test_source_cache_keyed_by_mtime(tmp_path):
|
||||
"""Une source réécrite n'est pas resservie depuis le cache."""
|
||||
import os
|
||||
|
||||
@ -588,11 +588,16 @@ def _open_source(path_str, mtime):
|
||||
if img is not None:
|
||||
_source_cache.move_to_end(key)
|
||||
return img
|
||||
img = Image.open(str(path_str))
|
||||
img.load()
|
||||
if img.mode not in ("RGB", "RGBA"):
|
||||
img = img.convert("RGB")
|
||||
size = img.size[0] * img.size[1] * len(img.getbands())
|
||||
# Copie détachée du fichier : une image AVIF ouverte garde son décodeur
|
||||
# (tampons libavif/dav1d, ~18 Mo par quadrant 2500²) tant qu'elle vit —
|
||||
# en cache, cela doublait la mémoire réelle et faisait tuer le conteneur
|
||||
# du Pi (1 Go) par l'OOM killer en navigation à fort zoom.
|
||||
with Image.open(str(path_str)) as opened:
|
||||
opened.load()
|
||||
img = (opened.copy() if opened.mode in ("RGB", "RGBA")
|
||||
else opened.convert("RGB"))
|
||||
# PIL stocke RGB comme RGBA : 4 octets par pixel dans les deux cas.
|
||||
size = img.size[0] * img.size[1] * 4
|
||||
with _source_cache_lock:
|
||||
_source_cache[key] = img
|
||||
_source_cache[key].info["_bytes"] = size
|
||||
|
||||
Reference in New Issue
Block a user