Split webapp for Raspberry Pi deployment, remote generation API and sync

La webapp (carte + vignettes) et la génération de tuiles se déploient sur
deux machines : image légère Dockerfile.webapp (FastAPI + Pillow AVIF
natif + pyproj) sur Raspberry Pi, pipeline complet sur la machine de
traitement. LIDAR_GENERATION_URL délègue /api/generate, /api/preview et
/api/status ; /api/sync ramène les tuiles par rsync puis régénère
vignettes et index localement. Token partagé optionnel
(LIDAR_API_TOKEN/LIDAR_REMOTE_TOKEN). Retire du dépôt les journaux
internes (.swival, audit-findings) et les données (data/, notebooks/).
Doc : docs/DEPLOY_WEBAPP.md.
This commit is contained in:
Antoine Jacquin
2026-09-02 19:44:39 +02:00
parent ed3e90ea89
commit 422f58d772
24 changed files with 2390 additions and 329 deletions

View File

@ -54,6 +54,14 @@ def tile_filename(col, row):
return f"LHD_FXX_{col:04d}_{row:04d}_PTS_LAMB93_IGN69.copc.laz"
def _report_download(output_dir, tile, state, detail=None):
"""Émet un événement de téléchargement pour la file de génération (best-effort)."""
if output_dir is None:
return
from .progress import report_event
report_event(output_dir, tile, "download", state, detail=detail)
def _bbox_wgs84(col, row):
"""Bbox WGS84 de la dalle (col,row) pour la requête STAC (peut être élargie)."""
try:
@ -117,46 +125,76 @@ def download_file(url, dest_path, timeout=120, chunk=1024 * 1024):
return done
def fetch_tiles(input_dir, specs, output_dir=None):
"""Télécharge les dalles IGN spécifiées, sauf celles déjà présentes/générées.
def fetch_tiles(input_dir, specs, output_dir=None, only_viz=None, resolutions=(0.5,), force=False):
"""Télécharge les dalles IGN spécifiées, sauf celles inutiles au traitement.
Args:
input_dir: dossier des fichiers LAZ (écriture autorisée requise).
specs: liste de tuples (col, row).
output_dir: dossier de sortie (optionnel) — permet d'ignorer les
tuiles dont les visualisations existent déjà.
tuiles déjà complètes.
only_viz: noms d'étapes des visualisations demandées (ex: ['aspect']).
Si fourni, une tuile n'est ignorée que si elle possède déjà
toutes ces visualisations aux résolutions demandées —
sinon son LAZ est (re)téléchargé pour compléter les
visualisations manquantes.
resolutions: résolutions attendues (m/px) pour considérer une tuile
complète quand only_viz est fourni.
force: True pour télécharger même les tuiles complètes (régénération :
leur LAZ est requis pour retraiter avec --force).
Returns:
Liste des chemins téléchargés.
"""
input_dir = Path(input_dir)
complete = None
if output_dir is not None and only_viz and not force:
from .index import cells_with_all_viz, step_to_keyword
keys = [step_to_keyword(v) for v in only_viz]
complete = cells_with_all_viz(Path(output_dir) / "visualisations",
keys, resolutions)
downloaded = []
for col, row in specs:
name = tile_filename(col, row)
tile = name[:-len(".copc.laz")] if name.endswith(".copc.laz") else name
dest = input_dir / name
if dest.exists():
logger.info(f" {name} : déjà présent dans input/ — aucun téléchargement")
_report_download(output_dir, tile, "skip", "déjà dans input/")
continue
if output_dir is not None:
vis_dir = Path(output_dir) / "visualisations"
if list(vis_dir.glob(f"LHD_FXX_{col:04d}_{row:04d}_PTS*")):
logger.info(f" {name} : visualisations déjà générées — ignorée")
continue
if output_dir is not None and not force:
if complete is not None:
if (col, row) in complete:
logger.info(f" {name} : visualisations déjà complètes — ignorée")
_report_download(output_dir, tile, "skip", "visualisations déjà complètes")
continue
else:
vis_dir = Path(output_dir) / "visualisations"
if list(vis_dir.glob(f"LHD_FXX_{col:04d}_{row:04d}_PTS*")):
logger.info(f" {name} : visualisations déjà générées — ignorée")
_report_download(output_dir, tile, "skip", "visualisations déjà générées")
continue
logger.info(f" {name} : recherche dans le catalogue IGN...")
_report_download(output_dir, tile, "start", "catalogue IGN")
try:
url = find_tile_url(col, row)
except Exception as e:
logger.warning(f" ✗ {name} : erreur catalogue ({e})")
_report_download(output_dir, tile, "fail", f"erreur catalogue : {e}")
continue
if not url:
logger.warning(f" ✗ {name} : introuvable dans le catalogue IGN (zone non publiée ?)")
_report_download(output_dir, tile, "fail", "introuvable dans le catalogue IGN")
continue
logger.info(f" {name} : téléchargement depuis la géoplateforme...")
_report_download(output_dir, tile, "start", "géoplateforme")
try:
download_file(url, dest)
logger.info(f" ✓ {name} téléchargée")
_report_download(output_dir, tile, "ok")
downloaded.append(dest)
except Exception as e:
dest.unlink(missing_ok=True)
logger.warning(f" ✗ {name} : échec du téléchargement ({e})")
_report_download(output_dir, tile, "fail", f"échec du téléchargement : {e}")
return downloaded