Rendre la carte autonome, télécharger la pyramide en fond, refermer les trous de zoom et borner les workers GPU par la VRAM

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Antoine Jacquin
2026-09-27 11:23:51 +02:00
parent 934e2dab55
commit 147a469ddc
10 changed files with 586 additions and 37 deletions

View File

@ -1032,3 +1032,140 @@ def test_remote_fed_map_keeps_sources_locally(tmp_path, monkeypatch):
while mapserve._bg["prefetch"]:
mapserve._bg_process_one()
assert all(s_.path.exists() for s_ in [thumb] + quads) and not full.path.exists()
def _bg_reset(monkeypatch, mapserve):
import collections
for key, val in (("queue", collections.deque()), ("queued", set()),
("snapshot", None), ("prefetch", collections.deque()),
("stats", {"rendues": 0, "a_jour": 0, "vides": 0, "scans": 0,
"dernier_scan": None, "ajouts_dernier_scan": 0})):
monkeypatch.setitem(mapserve._bg, key, val)
def test_background_downloads_tiles_from_upstream(tmp_path, monkeypatch):
"""Maintenance de fond + LIDAR_MAPS_URL : les tuiles de la pyramide sont
TÉLÉCHARGÉES en tâche de fond (pas seulement à l'affichage), sans rendu
local — la carte se remplit même sans visite et reste servie ensuite
sans l'amont."""
import lidar_pipeline.mapserve as mapserve
from lidar_pipeline import tiles
_setup(tmp_path, monkeypatch)
_bg_reset(monkeypatch, mapserve)
monkeypatch.setattr(mapserve, "MAPS_URL", "http://amont:8975")
AVIF = b"\x00\x00\x00\x20ftypavif-TUILE-AMONT"
calls = []
monkeypatch.setattr(mapserve, "_fetch_upstream",
lambda rel: calls.append(rel) or AVIF)
def no_render(*a, **k):
raise AssertionError("rendu local inutile : la tuile vient de l'amont")
monkeypatch.setattr(tiles, "get_tile", no_render)
z = 13
x, y = _tile_of_cell(1054, 6882, z)
mapserve._bg_enqueue([("aspect", z, x, y)])
assert mapserve._bg_process_one() is True
assert calls == [f"tiles/aspect/{z}/{x}/{y}@2x.avif"]
cache = tiles.tile_cache_path(tmp_path, "aspect", z, x, y, 2, "avif")
assert cache.read_bytes() == AVIF
assert mapserve._bg["stats"]["telechargees"] == 1
# Fraîche : servie en cache seule sans amont ni rendu
data, state = tiles.cached_tile(tmp_path, "aspect", z, x, y, 2, "avif")
assert state == "fresh" and data == AVIF
def test_background_renders_locally_when_upstream_down(tmp_path, monkeypatch):
"""Amont éteint : la maintenance rend la tuile localement (autonomie)."""
import lidar_pipeline.mapserve as mapserve
from lidar_pipeline import tiles
_setup(tmp_path, monkeypatch)
_bg_reset(monkeypatch, mapserve)
monkeypatch.setattr(mapserve, "MAPS_URL", "http://amont:8975")
monkeypatch.setattr(mapserve, "_fetch_upstream", lambda rel: None)
z = 13
x, y = _tile_of_cell(1054, 6882, z)
mapserve._bg_enqueue([("aspect", z, x, y)])
assert mapserve._bg_process_one() is True
assert tiles.tile_cache_path(tmp_path, "aspect", z, x, y, 2, "avif").is_file()
assert mapserve._bg["stats"]["rendues"] == 1
def test_upstream_breaker_rearms_after_expiry(monkeypatch):
"""La suspension expirée, de nouveaux échecs la relancent (sinon chaque
tuile repaie le délai réseau d'un amont éteint, indéfiniment)."""
import lidar_pipeline.mapserve as mapserve
monkeypatch.setattr(mapserve, "_UPSTREAM", {"fails": 0, "until": 0.0})
for _ in range(3):
mapserve._upstream_mark(False)
assert mapserve._upstream_offline() is True
mapserve._UPSTREAM["until"] = 1.0 # suspension expirée
assert mapserve._upstream_offline() is False
mapserve._upstream_mark(False) # nouvel échec : resuspendu
assert mapserve._upstream_offline() is True
def test_empty_tile_skips_upstream_when_inventory_is_upstream(tmp_path, monkeypatch):
"""Inventaire venu de l'amont (LIDAR_SOURCE_URL) et aucune dalle sous la
tuile : l'amont n'en sait pas plus — pas de requête (20 s par tuile vide
quand il est éteint)."""
import lidar_pipeline.mapserve as mapserve
from lidar_pipeline import tiles
_setup(tmp_path, monkeypatch)
monkeypatch.setattr(mapserve, "MAPS_URL", "http://amont:8975")
monkeypatch.setattr(tiles, "REMOTE_SOURCE_URL", "http://amont:8973")
monkeypatch.setattr(tiles, "_contributing", lambda *a, **k: [])
calls = []
monkeypatch.setattr(mapserve, "_fetch_upstream", lambda rel: calls.append(rel))
resp = asyncio.run(mapserve.tile("aspect", 15, 1, "1.png"))
assert calls == []
assert resp.headers["X-Tile-Empty"] == "1"
def test_cache_only_serves_stale_tile_while_pending(tmp_path, monkeypatch):
"""Cache seule, tuile périmée (dalle régénérée) et amont muet : l'ancienne
tuile reste affichée en attendant la nouvelle — pas un trou transparent."""
import os
import lidar_pipeline.mapserve as mapserve
from lidar_pipeline import tiles
_setup(tmp_path, monkeypatch)
monkeypatch.setenv("LIDAR_TILE_CACHE_ONLY", "1")
monkeypatch.setattr(mapserve, "MAPS_URL", "")
z = 13
x, y = _tile_of_cell(1054, 6882, z)
cache = tiles.tile_cache_path(tmp_path, "aspect", z, x, y, 2, "avif")
cache.parent.mkdir(parents=True, exist_ok=True)
cache.write_bytes(b"ANCIENNE-TUILE")
os.utime(cache, (1_000_000, 1_000_000)) # plus vieille que la dalle
assert tiles.cached_tile(tmp_path, "aspect", z, x, y, 2, "avif")[1] == "pending"
resp = asyncio.run(mapserve.tile("aspect", z, x, f"{y}@2x.avif"))
assert resp.body == b"ANCIENNE-TUILE"
assert resp.headers["Cache-Control"] == "no-store" # redemandée plus tard
assert resp.headers["X-Tile-Pending"] == "1"
assert resp.headers["X-Tile-Empty"] == "0"
def test_background_tiles_before_sources(tmp_path, monkeypatch):
"""Tuiles d'abord (légères, visibles tout de suite), sources ensuite :
des milliers de sources à rapatrier ne retardent pas la pyramide."""
import lidar_pipeline.mapserve as mapserve
from lidar_pipeline import tiles
_setup(tmp_path, monkeypatch)
_bg_reset(monkeypatch, mapserve)
monkeypatch.setattr(mapserve, "MAPS_URL", "http://amont:8975")
monkeypatch.setattr(mapserve, "_fetch_upstream", lambda rel: b"TUILE")
fetched = []
class Src:
def ensure(self):
fetched.append(1)
return True
mapserve._bg["prefetch"].append(Src())
z = 13
x, y = _tile_of_cell(1054, 6882, z)
mapserve._bg_enqueue([("aspect", z, x, y)])
assert mapserve._bg_process_one() is True
assert fetched == [] and mapserve._bg["stats"]["telechargees"] == 1
assert mapserve._bg_process_one() is True
assert fetched == [1]