index.py rewritten as a continuous Leaflet map: rotated L93 tiles, stackable visualization layers (per-layer opacity, drag-reorder persisted in localStorage), tile info panel, live rebuild after each tile during a run. Leaflet is vendored in assets/vendor/ so the map works fully offline; georeferencing falls back rasterio -> pyproj -> affine so the lightweight webapp (no GDAL) is supported. docker-compose.worker.yml adds the tile generator service (full image + GPU) that remote webapps call via LIDAR_GENERATION_URL, plus a one-shot process profile. webapp.py gains LIDAR_AUTO_SYNC_SECONDS periodic cache refresh and LIDAR_REGEN_CIDR restricting generation to the local network. run.sh --serve-webapp now mounts ~/.ssh read-only so the rsync sync works. docs/DEPLOY_WEBAPP.md completed for Raspberry Pi deployment: prerequisites, git clone install, SSH key setup, first sync, update procedure and troubleshooting.
11 KiB
Déploiement webapp légère + machine de traitement
Architecture deux machines : la webapp (carte interactive, vignettes,
API) tourne sur un Raspberry Pi ; la génération de tuiles (téléchargement
IGN, PDAL, GPU) tourne sur une machine puissante. Les deux exécutent le même
code (lidar_pipeline.webapp), dans deux images Docker différentes.
Navigateur ──HTTP──▶ Raspberry Pi (image légère, Dockerfile.webapp)
│ sert carte + vignettes (régénérées localement)
│ /api/generate, /api/preview, /api/status
│ └─ transmis à ──▶ machine de traitement
│ /api/sync : rsync output/ ──◀── machine puissante
▼
Machine de traitement (image complète,
docker-compose.worker.yml service `worker`) :
téléchargement IGN + pipeline GPU (générateur de tuiles)
Machine de traitement (générateur de tuiles)
Le service worker joue le rôle de générateur : il accepte les demandes
de génération envoyées par les webapp distantes (téléchargement IGN +
traitement PDAL/GPU) et expose les tuiles produites pour le rsync.
docker compose -f docker-compose.worker.yml up -d --build # API sur http://<ip-machine>:8973
docker compose -f docker-compose.worker.yml logs -f worker
Traitement batch ponctuel des dalles déjà présentes dans input/ :
docker compose -f docker-compose.worker.yml run --rm --build process [-r 0.5,0.2 | --force]
Sur une machine sans GPU : retirer les lignes gpus: all (traitement CPU,
plus lent). Optionnel mais recommandé si le réseau n'est pas de confiance :
protéger l'API avec un jeton partagé — décommenter dans
docker-compose.worker.yml :
environment:
- LIDAR_API_TOKEN=un-secret-à-partager
Raspberry Pi (webapp légère)
0. Prérequis sur le Pi
- Architecture : image construite nativement sur la machine (ARM64 ou
x86_64, vérifier avec
uname -m). Le build se fait sur le Pi lui-même (Dockerfile.webapp: basepython:3.12-slim, ~200 Mo, sans PDAL/GDAL). - Docker + plugin compose : installation officielle
docs.docker.com/engine/install
(tester avec
docker compose version). - rsync/ssh côté client : inutile sur l'hôte — ils sont dans l'image — mais la clé SSH de l'hôte est montée dans le conteneur.
- Espace disque : prévoir la taille du cache de tuiles (compter la
taille de
output/sur la machine de traitement, ~quelques dizaines de Mo par dalle et par résolution).
1. Copier le code sur le Pi (git clone)
git clone ssh://git@git.example.fr:2222/code_public/lidar_rendu.git lidar
cd lidar
Le clone SSH utilise la même clé que le rsync (~/.ssh, voir ci-dessous) ;
si le serveur git n'est pas encore connu, faire une première connexion pour
accepter son empreinte. output/ et input/ sont ignorés par git : le
dépôt ne contient que le code, le cache de tuiles se remplit ensuite par
rsync (premier chargement, section 4).
2. Accès SSH pour le rsync (la Pi tire les tuiles traitées)
ssh-keygen -t ed25519 # si pas encore de clé
ssh-copy-id lidar@<ip-machine> # compte lecture sur output/
ssh lidar@<ip-machine> exit # 1re connexion : enregistre known_hosts
La dernière commande évite le prompt « authenticity of host » pendant le rsync (le conteneur ne peut pas répondre interactivement).
Sur la machine puissante, le dossier output/ doit être lisible par ce
compte (ex. /srv/lidar/output si vous préférez un chemin dédié — adaptez
LIDAR_SYNC_CMD).
Avec run.sh, ~/.ssh (clé + known_hosts) est monté automatiquement en
lecture seule dans le conteneur ; avec docker compose, le montage
équivalent est à décommenter dans docker-compose.webapp.yml (voir
l'option b ci-dessous).
3. Configurer et lancer
Deux façons, au choix :
a. run.sh (le plus simple) — conteneur webapp seul, cache local des
tuiles ; les variables se passent depuis l'environnement hôte, ~/.ssh
est monté automatiquement :
LIDAR_GENERATION_URL=http://192.168.1.50:8973 \
LIDAR_REMOTE_TOKEN=un-secret-à-partager \
LIDAR_SYNC_CMD="rsync -a --delete --exclude=*.tif --exclude=.generation* --exclude=index_thumbs --exclude=index_subtiles lidar@192.168.1.50:/srv/lidar/output/ /data/output/" \
LIDAR_AUTO_SYNC_SECONDS=600 \
./run.sh --serve-webapp # port 8973, ou --serve-webapp 9000
LIDAR_AUTO_SYNC_SECONDS entretient le cache tout seul : toutes les N
secondes, rsync ramène les nouvelles tuiles et les vignettes manquantes
sont régénérées (les mtimes évitent tout recalcul inutile). Sans cette
variable, le cache se rafraîchit à la demande : bouton ↻, fin d'un run,
ou POST /api/sync.
LIDAR_REGEN_CIDR restreint le lancement des générations (POST /api/generate : zones, complétion, régénération) aux clients dont l'IP
est dans le réseau indiqué — défaut 192.168.1.0/24, chaîne vide pour
lever la restriction. L'IP est celle de la connexion (conservée par le
DNAT Docker pour les clients du LAN) ; derrière un reverse proxy local
(dans le réseau autorisé), X-Forwarded-For désigne le client réel.
Les autres clients consultent la carte normalement : les boutons de
génération sont masqués et l'API répond 403.
b. docker compose — éditer docker-compose.webapp.yml :
LIDAR_GENERATION_URL:http://<ip-machine>:8973LIDAR_REMOTE_TOKEN: la valeur deLIDAR_API_TOKENde la machine (inutile si aucun token là-bas)LIDAR_SYNC_CMD: la commande rsync qui copieoutput/distant vers/data/output/local, ex :
- LIDAR_SYNC_CMD=rsync -a --delete --exclude=*.tif --exclude=.generation* --exclude=index_thumbs --exclude=index_subtiles lidar@192.168.1.50:/srv/lidar/output/ /data/output/
Décommenter aussi le montage de la clé SSH (rsync) :
volumes:
- ./output:/data/output
- ~/.ssh:/home/lidar/.ssh:ro
Les vignettes (index_thumbs/, index_subtiles/) ne se synchronisent pas :
elles sont régénérées sur place par le Pi (/api/sync → build_index),
c'est le seul travail lourd qu'il fait. Exclure *.tif évite de copier des
intermédiaires éventuels ; les sidecars output/DTM/*_method.txt servent au
panneau d'infos et sont synchronisés.
mkdir -p output
docker compose -f docker-compose.webapp.yml up -d --build
La carte est sur http://<ip-pi>:8973/.
run.sh lance le conteneur au premier plan (Ctrl-C arrête tout) : pratique
pour tester. Pour une installation permanente, préférer docker compose
(restart: unless-stopped : la carte revient après un reboot du Pi).
4. Premier chargement
Si aucune tuile n'a encore été synchronisée, déclencher une première fois :
curl -X POST http://<ip-pi>:8973/api/sync
(puis attendre la fin : curl http://<ip-pi>:8973/api/sync → "running": false.) Le bouton ↻ de la carte fait la même chose (sync + vignettes).
Mise à jour
Sur le Pi, après un git pull (ou rsync du code) :
docker compose -f docker-compose.webapp.yml up -d --build # rebuild + redémarrage
Avec run.sh --serve-webapp, l'image est reconstruite automatiquement à
chaque lancement ; il suffit de relancer la commande. Le cache de tuiles
(output/) n'est pas affecté par les mises à jour d'image.
Dépannage
- État d'une sync :
curl http://<ip-pi>:8973/api/sync→running,phase(syncpuisindex) eterror(dernière erreur, ex. fin de journal rsync en cas d'échec). Les logs du conteneur :docker logs lidar-webapp(oudocker compose -f docker-compose.webapp.yml logs -f webapp). Permission denied (publickey)pendant la sync : refairessh-copy-id lidar@<ip-machine>et une connexion manuelle pour renseignerknown_hosts; vérifier que~/.sshest monté dans le conteneur (automatique avecrun.sh, à décommenter en compose).- Boutons de génération masqués /
403sur/api/generate: l'IP du client n'est pas dansLIDAR_REGEN_CIDR(défaut192.168.1.0/24). Adapter la variable ou la vider pour lever la restriction. - « Machine de traitement injoignable » : vérifier le service
worker(docker compose -f docker-compose.worker.yml logs -f worker) etLIDAR_GENERATION_URL(IP + port 8973). La consultation de la carte reste possible avec le cache local. - Carte vide après sync : attendre la fin du rebuild (
GET /api/sync→"running": false) puis recharger la page ; le bouton ↻ de la carte enchaîne sync + vignettes automatiquement. - Port 8973 déjà pris :
--serve-webapp 9000avecrun.sh, ou changer le mappingports:en compose (le conteneur écoute toujours sur 8973).
Fonctionnement
- Dessiner une zone (bouton « + Zone ») sur la carte du Pi envoie la demande à la machine de traitement, qui télécharge les dalles IGN puis les traite. La file de génération (progression tuile par tuile) est lue depuis la machine distante en direct.
- À la fin du run, le navigateur appelle
/api/sync: rsync ramène les images, le Pi régénère vignettes/sous-tuiles/index.html, puis recharge la carte. Le bouton ↻ relance le même cycle à tout moment. AvecLIDAR_AUTO_SYNC_SECONDS, le conteneur webapp seul maintient aussi son cache périodiquement (utile quand personne ne consulte la carte). - Sans machine de traitement configurée,
./run.sh --serve-webappsert la carte en lecture seule depuisoutput/(cache figé, aucun traitement possible depuis l'interface). - Autonomie : une fois les tuiles chargées dans le cache local, la
webapp fonctionne seule — Leaflet est vendorisé dans
assets/vendor/(aucun CDN), tuiles/vignettes/infos sont lues dansoutput/, et un backend injoignable masque simplement les boutons de génération sans affecter la consultation. - Machine de traitement seule (sans Pi) : rien ne change,
--serve/docker compose up -d --build servefonctionne comme avant en local (tout-en-un), etdocker-compose.worker.ymlinstalle le générateur seul. - Si la machine de traitement est éteinte, la carte du Pi reste consultable (données synchronisées) ; seuls les nouveaux traitements sont indisponibles (message « machine de traitement injoignable »).
Références
lidar_pipeline/webapp.py: proxy distant (LIDAR_GENERATION_URL),/api/sync, cache local périodique (LIDAR_AUTO_SYNC_SECONDS), jetonLIDAR_API_TOKEN/LIDAR_REMOTE_TOKEN, restriction des générations au réseau local (LIDAR_REGEN_CIDR).lidar_pipeline/index.py: vignettes/index sans GDAL (pyproj ou repli affine), recharge après sync.docker-compose.worker.yml: générateur de tuiles (machine puissante).Dockerfile.webapp,docker-compose.webapp.yml: webapp légère ARM64 (FastAPI + Pillow AVIF natif + pyproj, ~200 Mo).