Le bouton ⌖ recentre la carte sur la position GPS du téléphone. L'API Geolocation exige un contexte sécurisé : la webapp sert désormais du HTTP et le TLS est porté par le reverse proxy (Traefik, labels dans l'override local) ; le webapp est transparent au proxy (URLs relatives, X-Forwarded-For pour la restriction LIDAR_REGEN_CIDR). Ajoute le modèle d'override docker-compose.webapp.override.yml.example, un rebuild d'index à la demande et met à jour doc/déploiement ; les tuiles sont servies à la demande.
14 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 : rebuild local (tuiles servies à la demande)
▼
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, servies à la demande par les webapp.
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). - Clé SSH côté hôte : montée dans le conteneur pour le déploiement et le
git pulldistant (le rsync de tuiles, supprimé, n'en avait pas besoin). - 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 clé de l'hôte (~/.ssh, section 2). 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 à la demande depuis la machine de
traitement (section 4).
2. Accès SSH (déploiement)
- Le rsync ayant été retiré, le SSH ne sert plus à ramener les tuiles (elles
- sont servies à la demande, section 4). Il sert au déploiement et au support
- se connecter à la machine de traitement pour y inspecter
output/ou y déposer des tuiles manuellement.
ssh-keygen -t ed25519 # si pas encore de clé
ssh-copy-id lidar@<ip-machine> # compte sur la machine de traitement
ssh lidar@<ip-machine> exit # 1re connexion : enregistre known_hosts
La dernière commande évite le prompt « authenticity of host » en connexion non interactive (le conteneur ne peut pas répondre).
Le cache de tuiles du Pi se remplit à la demande depuis la machine de traitement ; ce compte SSH sert à l'inspecter ou à y déposer des tuiles manuellement si besoin.
Avec serve-webapp.sh et run.sh, la clé SSH de l'hôte (~/.ssh, clé +
known_hosts) est montée automatiquement en lecture seule dans le conteneur ;
avec docker compose, le montage équivalent est à décommenter dans
docker-compose.webapp.yml (voir l'option c ci-dessous). Le wrapper local
ssh (agent forwarding) simplifie ces connexions.
3. Configurer et lancer
Trois façons, au choix :
a. serve-webapp.sh (recommandé) — script de gestion de la stack
(arrière-plan, redémarrage automatique au reboot, sous-commandes). La
configuration vit dans webapp.env (copie du modèle, non versionné) :
cp webapp.env.example webapp.env
nano webapp.env # LIDAR_GENERATION_URL, jeton...
./serve-webapp.sh # démarre (build au premier lancement) et attend le serveur
Sous-commandes : stop, restart (relit webapp.env), status (conteneur
- état du rebuild),
sync(rebuild + vignettes),logs,update(git pull+ rebuild + redémarrage). Port hôte viaWEBAPP_PORTdanswebapp.envou l'environnement (WEBAPP_PORT=9000 ./serve-webapp.sh).
b. run.sh — conteneur au premier plan (Ctrl-C arrête tout) : pratique
pour tester, 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 \
./run.sh --serve-webapp # port 8973, ou --serve-webapp 9000
Les tuiles sont servies à la demande : une image absente du cache local est
téléchargée depuis la machine de traitement au premier affichage, le cache
se remplit ainsi progressivement. Le bouton ↻, la fin d'un run ou POST /api/sync déclenchent un rebuild de l'index et régénèrent les vignettes.
LIDAR_REGEN_CIDR restreint le lancement des générations (POST /api/generate : zones, sélection, complétion, régénération) aux clients
dont l'IP est dans un des réseaux indiqués — liste de CIDR séparés par
virgules, chaîne vide pour lever la restriction. Défaut :
127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 (boucle locale
- plages privées RFC1918 : LAN, hôte Docker via la passerelle 172.x ; les
IP publiques restent refusées). 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-Fordé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.
c. 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)- le cache local se remplit à la demande depuis
LIDAR_GENERATION_URL(pas deLIDAR_SYNC_CMD)
Décommenter aussi le montage de la clé SSH (déploiement) :
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, serve-webapp.sh (option a)
ou docker compose (option c) redémarrent la carte après un reboot du Pi
(--restart unless-stopped).
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).
5. Centrer la carte sur la position GPS (téléphone)
Le bouton ⌖ (barre d'outils de la carte, à côté du recadrage) recentre la carte sur la position GPS du téléphone et y dessine un marqueur vert.
Les navigateurs n'exposent l'API Geolocation qu'en contexte sécurisé
(HTTPS). Servie en http://<ip-pi>:8973, la carte ne peut donc pas obtenir la
position sur téléphone : le bouton affiche alors « connexion sécurisée (HTTPS)
requise ».
TLS terminé par le proxy (cas d'usage ici) — le certificat est porté par
Traefik : le webapp sert du HTTP, Traefik expose https://<domaine> et y
applique son certificat (valable, sans avertissement). Le téléphone se connecte
sur l'URL Traefik → contexte sécurisé → le bouton ⌖ fonctionne.
La config Traefik (routage HTTPS) vit dans le fichier override local
docker-compose.webapp.override.yml (non versionné, sur le Pi) — Compose le
merge par-dessus docker-compose.webapp.yml. Le modèle versionné
docker-compose.webapp.override.yml.example fournit les labels (provider
Docker) qui routent Host(\${LIDAR_WEBAPP_HOST}`)vers le service, enwebsecure` (443) + TLS avec le certificat porté par Traefik. Sur le Pi :
cp docker-compose.webapp.override.yml.example docker-compose.webapp.override.yml
# adapter le domaine : LIDAR_WEBAPP_HOST=carte.example.fr (dans un .env à
# côté du compose, l'environnement, ou en dur dans la règle Host())
# docker compose n'auto-charge PAS un override au nom custom (seulement
# docker-compose.override.yml) → passer -f explicitement :
docker compose -f docker-compose.webapp.yml \
-f docker-compose.webapp.override.yml up -d --build
Adapter si le proxy n'est pas en websecure ou que le provider n'est pas
Docker : changer entrypoints / ajouter
traefik.http.routers.lidar-webapp.tls.certresolver=…, ou déclarer le
service directement dans la config Traefik. Le webapp est transparent au
proxy (URLs relatives) et remonte l'IP réelle via X-Forwarded-For pour la
restriction LIDAR_REGEN_CIDR : l'IP du téléphone doit y figurer pour lancer
des générations.
Sans Traefik — servir la carte en HTTPS avec un certificat auto-signé :
./make-tls-cert.shcréetls/webapp.{crt,key}, à monter dans le conteneur avecLIDAR_SSL_CERTFILE/LIDAR_SSL_KEYFILE(le cheminserve-webapp.shle fait automatiquement). Le téléphone accepte alors l'avertissement « certificat non fiable » une fois.
Mise à jour
Avec serve-webapp.sh, une seule commande (git pull, rebuild de l'image,
redémarrage) :
./serve-webapp.sh update
Sinon, sur le Pi, après un git pull :
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'un rebuild :
curl http://<ip-pi>:8973/api/sync→running,phase(index) eterror(dernière erreur). Les logs du conteneur :docker logs lidar-webapp(oudocker compose -f docker-compose.webapp.yml logs -f webapp). Permission denied (publickey)en SSH : 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 dans aucun des réseaux deLIDAR_REGEN_CIDR(défaut : localhost + plages privées). 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, ou./serve-webapp.sh status) puis recharger la page ; le bouton ↻ de la carte enchaîne sync + vignettes automatiquement. - Port 8973 déjà pris :
WEBAPP_PORT=9000 ./serve-webapp.sh(ou la variable danswebapp.env),--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: les images produites sont servies à la demande depuis la machine de traitement, le Pi régénère vignettes/sous-tuiles/index.html, puis recharge la carte. Le bouton ↻ relance le même cycle à tout moment. - 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(rebuild de l'index), cache local à la demande, 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).serve-webapp.sh,webapp.env.example: gestion de la stack webapp sur le Pi (démarrage/arrêt/sync/logs/update), configuration locale danswebapp.env(ignoré par git).