Garder localement les sources des dalles sur une carte alimentée par l'amont
Avec LIDAR_SOURCE_URL (Pi), la maintenance rapatrie et conserve les sources de chaque dalle dès qu'elle apparaît, sans attendre de visite : les niveaux rendus à la volée restent disponibles quand le conteneur de rendu est éteint. Seuls les paliers utiles sont pris (vignettes, quadrants 500 m ; la dalle entière, doublon de ses quadrants, ne l'est pas) ; une source manquée pendant que l'amont était éteint est reprise au scan suivant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@ -91,6 +91,13 @@ pairs : à un zoom impair, elle réduit les tuiles du niveau supérieur (nettes)
|
|||||||
Mesuré sur 8 dalles : environ 24 Mo de pyramide avant, 1,2 Mo après.
|
Mesuré sur 8 dalles : environ 24 Mo de pyramide avant, 1,2 Mo après.
|
||||||
`LIDAR_TILE_EVEN_LEVELS=0` rétablit le stockage de tous les niveaux.
|
`LIDAR_TILE_EVEN_LEVELS=0` rétablit le stockage de tous les niveaux.
|
||||||
|
|
||||||
|
Une carte alimentée par un serveur de dalles amont (`LIDAR_SOURCE_URL`, cas
|
||||||
|
du Pi) rapatrie et **garde localement** les sources de chaque dalle dès
|
||||||
|
qu'elle apparaît (vignettes et quadrants 500 m ; la dalle entière, doublon
|
||||||
|
de ses quadrants, n'est pas rapatriée), sans attendre de visite. Tous les
|
||||||
|
niveaux restent disponibles quand le conteneur de rendu est éteint ; une
|
||||||
|
source manquée pendant qu'il était éteint est reprise au scan suivant.
|
||||||
|
|
||||||
## Fiche de dalle et rose des vents
|
## Fiche de dalle et rose des vents
|
||||||
|
|
||||||
Un clic sur la carte sélectionne la dalle LiDAR HD sous le curseur (cadre
|
Un clic sur la carte sélectionne la dalle LiDAR HD sous le curseur (cadre
|
||||||
|
|||||||
@ -169,6 +169,7 @@ def _bg_conf():
|
|||||||
|
|
||||||
_bg = {"threads": [], "stop": threading.Event(), "queue": deque(),
|
_bg = {"threads": [], "stop": threading.Event(), "queue": deque(),
|
||||||
"queued": set(), "guard": threading.Lock(), "snapshot": None,
|
"queued": set(), "guard": threading.Lock(), "snapshot": None,
|
||||||
|
"prefetch": deque(),
|
||||||
"running": False,
|
"running": False,
|
||||||
"stats": {"rendues": 0, "a_jour": 0, "vides": 0, "scans": 0,
|
"stats": {"rendues": 0, "a_jour": 0, "vides": 0, "scans": 0,
|
||||||
"dernier_scan": None, "ajouts_dernier_scan": 0}}
|
"dernier_scan": None, "ajouts_dernier_scan": 0}}
|
||||||
@ -236,6 +237,22 @@ def background_scan(output_dir=None):
|
|||||||
continue
|
continue
|
||||||
changed.setdefault(layer, []).append((col, row))
|
changed.setdefault(layer, []).append((col, row))
|
||||||
_bg["snapshot"] = snapshot
|
_bg["snapshot"] = snapshot
|
||||||
|
# Carte alimentée par un serveur de dalles amont (Pi) : les sources des
|
||||||
|
# dalles nouvelles ou modifiées sont rapatriées et GARDÉES localement,
|
||||||
|
# sans attendre une visite — les niveaux rendus à la volée restent
|
||||||
|
# disponibles quand le conteneur de rendu est éteint.
|
||||||
|
# Toutes les dalles sont revérifiées (simples tests d'existence) : une
|
||||||
|
# source manquée pendant qu'il était éteint est reprise au scan suivant.
|
||||||
|
if tiles_mod.REMOTE_SOURCE_URL:
|
||||||
|
index = tiles_mod.source_index(output_dir)
|
||||||
|
pending = {str(s_.path) for s_ in _bg["prefetch"]}
|
||||||
|
for per_cell in index.values():
|
||||||
|
for tiers in per_cell.values():
|
||||||
|
for src in tiles_mod.sources_to_keep(tiers):
|
||||||
|
key = str(src.path)
|
||||||
|
if src.url is not None and key not in pending and not src.path.is_file():
|
||||||
|
_bg["prefetch"].append(src)
|
||||||
|
pending.add(key)
|
||||||
items = []
|
items = []
|
||||||
for layer, cells in changed.items():
|
for layer, cells in changed.items():
|
||||||
for col, row in cells:
|
for col, row in cells:
|
||||||
@ -261,7 +278,17 @@ def background_scan(output_dir=None):
|
|||||||
|
|
||||||
|
|
||||||
def _bg_process_one():
|
def _bg_process_one():
|
||||||
"""Traite une tuile de la file. Vrai si elle a dû être rendue."""
|
"""Traite une source à rapatrier, sinon une tuile de la file. Vrai si
|
||||||
|
un travail a été fait (rapatriement ou rendu)."""
|
||||||
|
try:
|
||||||
|
src = _bg["prefetch"].popleft()
|
||||||
|
except IndexError:
|
||||||
|
src = None
|
||||||
|
if src is not None:
|
||||||
|
if src.ensure():
|
||||||
|
_bg["stats"]["sources_locales"] = _bg["stats"].get("sources_locales", 0) + 1
|
||||||
|
return True
|
||||||
|
return False # amont éteint : repris au prochain scan
|
||||||
item = _bg_pop()
|
item = _bg_pop()
|
||||||
if item is None:
|
if item is None:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@ -973,3 +973,37 @@ def test_background_scan_only_queues_stored_levels(tmp_path, monkeypatch):
|
|||||||
zs = {z for (_l, z, _x, _y) in mapserve._bg["queue"]}
|
zs = {z for (_l, z, _x, _y) in mapserve._bg["queue"]}
|
||||||
assert zs and all(tiles.zoom_cached(z, 2) for z in zs)
|
assert zs and all(tiles.zoom_cached(z, 2) for z in zs)
|
||||||
assert zs <= {5, 7, 9, 11, 13, 15}
|
assert zs <= {5, 7, 9, 11, 13, 15}
|
||||||
|
|
||||||
|
|
||||||
|
def test_remote_fed_map_keeps_sources_locally(tmp_path, monkeypatch):
|
||||||
|
"""Carte alimentée par l'amont : les sources nécessaires sont rapatriées
|
||||||
|
et gardées sans attendre de visite ; une source manquée (amont éteint)
|
||||||
|
est reprise au scan suivant ; la dalle entière (doublon) n'est pas prise."""
|
||||||
|
import lidar_pipeline.mapserve as mapserve
|
||||||
|
from lidar_pipeline import tiles
|
||||||
|
S = tiles._Source
|
||||||
|
thumb = S(tmp_path / "t.webp", (0, 0, 1000, 1000), 3.9, url="http://amont/t")
|
||||||
|
quads = [S(tmp_path / f"q{i}.avif", (0, 0, 500, 500), 0.2, url=f"http://amont/q{i}") for i in range(4)]
|
||||||
|
full = S(tmp_path / "d.avif", (0, 0, 1000, 1000), 0.2, url="http://amont/d")
|
||||||
|
assert tiles.sources_to_keep([[thumb], quads, [full]]) == [thumb] + quads
|
||||||
|
monkeypatch.setattr(tiles, "REMOTE_SOURCE_URL", "http://amont")
|
||||||
|
monkeypatch.setattr(mapserve, "OUTPUT_DIR", tmp_path)
|
||||||
|
monkeypatch.setattr(tiles, "source_index", lambda *a, **k: {"relief_oriente": {(1, 2): [[thumb], quads, [full]]}})
|
||||||
|
for key, val in (("queue", __import__("collections").deque()), ("queued", set()),
|
||||||
|
("snapshot", None), ("prefetch", __import__("collections").deque())):
|
||||||
|
monkeypatch.setitem(mapserve._bg, key, val)
|
||||||
|
online = {"up": False}
|
||||||
|
def fake_fetch(url, dest):
|
||||||
|
if not online["up"]:
|
||||||
|
return False
|
||||||
|
dest.write_bytes(b"x"); return True
|
||||||
|
monkeypatch.setattr(tiles, "_fetch_source", fake_fetch)
|
||||||
|
mapserve.background_scan(tmp_path)
|
||||||
|
while mapserve._bg["prefetch"]:
|
||||||
|
mapserve._bg_process_one()
|
||||||
|
assert not any(s_.path.exists() for s_ in [thumb] + quads) # amont éteint
|
||||||
|
online["up"] = True
|
||||||
|
mapserve.background_scan(tmp_path) # dalle inchangée : reprise quand même
|
||||||
|
while mapserve._bg["prefetch"]:
|
||||||
|
mapserve._bg_process_one()
|
||||||
|
assert all(s_.path.exists() for s_ in [thumb] + quads) and not full.path.exists()
|
||||||
|
|||||||
@ -609,6 +609,22 @@ def clear_source_cache():
|
|||||||
_source_cache.clear()
|
_source_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
|
def sources_to_keep(tiers):
|
||||||
|
"""Paliers qu'une machine autonome doit garder pour rendre TOUS ses
|
||||||
|
niveaux sans l'amont : du plus grossier jusqu'au premier palier qui
|
||||||
|
atteint la résolution la plus fine (une dalle entière de même résolution
|
||||||
|
que ses quadrants serait un doublon)."""
|
||||||
|
if not tiers:
|
||||||
|
return []
|
||||||
|
finest = min(group[0].res for group in tiers)
|
||||||
|
keep = []
|
||||||
|
for group in tiers:
|
||||||
|
keep.extend(group)
|
||||||
|
if group[0].res <= finest:
|
||||||
|
break
|
||||||
|
return keep
|
||||||
|
|
||||||
|
|
||||||
def _pick_tier(tiers, target_res):
|
def _pick_tier(tiers, target_res):
|
||||||
"""Palier le plus grossier dont la résolution suffit à la tuile visée."""
|
"""Palier le plus grossier dont la résolution suffit à la tuile visée."""
|
||||||
for group in tiers:
|
for group in tiers:
|
||||||
|
|||||||
Reference in New Issue
Block a user