Porter la génération de tuiles dans lidar-maps et retirer l'ancienne webapp
La carte à tuiles XYZ devient la seule interface : l'API de génération (preview, generate, status, stop, file, cell), le job unique + file persistante, la délégation LIDAR_GENERATION_URL et les garde-fous (token, LIDAR_REGEN_CIDR) passent de webapp.py à mapserve.py ; l'interface gagne les boutons + Zone / ⤒ Compléter / régénération à la dalle, les options de run et la progression cadre par cadre. mapserve sert aussi l'inventaire /api/tiles et les dalles en statique versionné : le worker image complète remplace la webapp sur le 8973 du générateur, le Pi n'exécute plus que lidar-maps. index.py se réduit aux registres partagés + vignettes/sous-tuiles/inventaire (l'UI HTML/JS et les mosaïques d'overview partent avec export.py et les compose/scripts de la webapp).
This commit is contained in:
@ -1,27 +1,33 @@
|
||||
# Déploiement webapp légère + machine de traitement
|
||||
# Déploiement carte légère + machine de traitement
|
||||
|
||||
Architecture deux machines : la **webapp** (carte interactive, vignettes,
|
||||
Architecture deux machines : la **carte** (interface, pyramide de tuiles XYZ,
|
||||
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.
|
||||
serveur (`lidar_pipeline.mapserve`, image `lidar-maps`), la machine puissante
|
||||
en version complète (pipeline inclus).
|
||||
|
||||
```
|
||||
Navigateur ──HTTP──▶ Raspberry Pi (image légère, Dockerfile.webapp)
|
||||
│ sert carte + vignettes (régénérées localement)
|
||||
Navigateur ──HTTP──▶ Raspberry Pi (image légère lidar-maps, port 8975)
|
||||
│ sert la carte + la pyramide (cache seule + maintenance
|
||||
│ de fond, LIDAR_TILE_CACHE_ONLY / LIDAR_TILE_BACKGROUND)
|
||||
│ /api/generate, /api/preview, /api/status
|
||||
│ └─ transmis à ──▶ machine de traitement
|
||||
│ /api/sync : rebuild local (tuiles servies à la demande)
|
||||
│ dalles rapatriées du worker (LIDAR_SOURCE_URL :
|
||||
│ inventaire /api/tiles + statiques versionnées)
|
||||
▼
|
||||
Machine de traitement (image complète,
|
||||
docker-compose.worker.yml service `worker`) :
|
||||
téléchargement IGN + pipeline GPU (générateur de tuiles)
|
||||
+ pyramide de tuiles + inventaire des dalles
|
||||
```
|
||||
|
||||
## 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.
|
||||
Le service `worker` joue le rôle de générateur : il accepte les demandes de
|
||||
génération envoyées par les cartes distantes (téléchargement IGN + traitement
|
||||
PDAL/GPU), sert sa propre pyramide de tuiles et l'inventaire des dalles
|
||||
(`/api/tiles` + `visualisations/`, `index_thumbs/`, `index_subtiles/` en
|
||||
statique) que les machines légères rapatrient à la demande.
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.worker.yml up -d --build # API sur http://<ip-machine>:8973
|
||||
@ -31,7 +37,7 @@ docker compose -f docker-compose.worker.yml logs -f worker
|
||||
Traitement batch ponctuel des dalles déjà présentes dans `input/` :
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.worker.yml run --rm --build process [-r 0.5,0.2 | --force]
|
||||
docker compose -f docker-compose.worker.yml run --rm --build process [-r 0.2 | --force]
|
||||
```
|
||||
|
||||
Sur une machine sans GPU : retirer les lignes `gpus: all` (traitement CPU,
|
||||
@ -44,21 +50,18 @@ environment:
|
||||
- LIDAR_API_TOKEN=un-secret-à-partager
|
||||
```
|
||||
|
||||
## Raspberry Pi (webapp légère)
|
||||
## Raspberry Pi (carte 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` : base `python:3.12-slim`, ~200 Mo, sans PDAL/GDAL).
|
||||
(`Dockerfile.maps` : base `python:3.12-slim`, ~200 Mo, sans PDAL/GDAL).
|
||||
- **Docker + plugin compose** : installation officielle
|
||||
[docs.docker.com/engine/install](https://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 pull` distant (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).
|
||||
- **Espace disque** : prévoir la taille du cache (dalles rapatriées + tuiles
|
||||
rendues ; compter la taille de `output/` sur la machine de traitement).
|
||||
|
||||
### 1. Copier le code sur le Pi (git clone)
|
||||
|
||||
@ -67,323 +70,121 @@ 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).
|
||||
`output/` et `input/` sont ignorés par git : le dépôt ne contient que le
|
||||
code, le cache se remplit à la demande depuis la machine de traitement.
|
||||
|
||||
### 2. Accès SSH (déploiement)
|
||||
### 2. Lancer la carte
|
||||
|
||||
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.
|
||||
`docker-compose.maps.yml` (versionné) sert de base ; la configuration locale
|
||||
du Pi vit dans un **override** `docker-compose.maps.override.yml` (non
|
||||
versionné) :
|
||||
|
||||
```bash
|
||||
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
|
||||
```yaml
|
||||
name: lidar-maps
|
||||
services:
|
||||
maps:
|
||||
volumes: !override # Compose >= 2.24 (remplace ./output)
|
||||
- /srv/lidar/output:/data/output
|
||||
mem_limit: 1g
|
||||
memswap_limit: 1g
|
||||
environment:
|
||||
- TZ=Europe/Paris
|
||||
- LIDAR_SOURCE_URL=http://192.168.1.50:8973 # worker (dalles + inventaire)
|
||||
- LIDAR_GENERATION_URL=http://192.168.1.50:8973 # worker (génération déléguée)
|
||||
# - LIDAR_REMOTE_TOKEN=un-secret-à-partager # si LIDAR_API_TOKEN côté worker
|
||||
- LIDAR_TILE_WORKERS=1
|
||||
- LIDAR_TILE_SOURCE_CACHE_MB=64
|
||||
- LIDAR_TILE_CACHE_ONLY=1 # la navigation ne rend RIEN
|
||||
- LIDAR_TILE_BACKGROUND=1 # la pyramide est entretenue en tâche de fond
|
||||
- LIDAR_TILE_BACKGROUND_PAUSE=1.0
|
||||
networks: [webapp, proxy]
|
||||
labels: # routage Traefik éventuel
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.lidar-maps.rule=Host(`lidar.example.fr`)"
|
||||
- "traefik.http.routers.lidar-maps.entrypoints=websecure"
|
||||
- "traefik.http.routers.lidar-maps.tls.certresolver=myresolver"
|
||||
- "traefik.http.services.lidar-maps.loadbalancer.server.port=8975"
|
||||
|
||||
networks:
|
||||
webapp:
|
||||
name: lidar_rendu_default
|
||||
external: true
|
||||
proxy:
|
||||
name: proxy
|
||||
external: true
|
||||
```
|
||||
|
||||
La dernière commande évite le prompt « authenticity of host » en connexion
|
||||
non interactive (le conteneur ne peut pas répondre).
|
||||
```bash
|
||||
mkdir -p /srv/lidar/output # appartenant à l'uid 1000 (conteneur)
|
||||
docker compose -f docker-compose.maps.yml -f docker-compose.maps.override.yml up -d --build
|
||||
```
|
||||
|
||||
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.
|
||||
La carte est sur `http://<ip-pi>:8975/` (et via Traefik si configuré).
|
||||
|
||||
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).
|
||||
`LIDAR_TILE_CACHE_ONLY=1` + `LIDAR_TILE_BACKGROUND=1` inversent la charge sur
|
||||
un petit Pi : la navigation ne rend rien (tuile absente = transparente,
|
||||
`X-Tile-Pending`), une tâche de fond surveille les dalles nouvelles ou
|
||||
régénérées et entretient la pyramide à basse priorité — cf. `docs/MAPS.md`
|
||||
§ « Cache seule + maintenance de fond ».
|
||||
|
||||
#### Wrapper `ssh` (déploiement depuis le poste de pilotage)
|
||||
### 3. Génération de tuiles depuis la carte
|
||||
|
||||
Le poste de pilotage (agent Crush) interdit l'appel `ssh` direct : le wrapper
|
||||
`ssh`, créé une fois (puis `chmod +x ssh`),
|
||||
fournit l'accès autorisé avec transfert d'agent (`-A`) — les `git pull`
|
||||
distants utilisent la clé locale — et refuse toute interaction (BatchMode) :
|
||||
Les boutons **+ Zone** (dessiner un rectangle → téléchargement IGN + run sur
|
||||
le worker), **⤒ Compléter** (dalles déjà téléchargées incomplètes) et
|
||||
**↻ Générer cette dalle** (fiche d'infos au clic) sont masqués si :
|
||||
|
||||
- le navigateur vient d'une IP hors `LIDAR_REGEN_CIDR` (défaut : boucle
|
||||
locale + plages privées RFC1918 ; liste de CIDR séparés par virgules,
|
||||
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 ;
|
||||
- le worker est injoignable ET aucun pipeline local n'existe.
|
||||
|
||||
Une demande lancée pendant un run part en **file d'attente** côté worker
|
||||
(jamais de coupure du travail en place) ; la progression s'affiche dalle par
|
||||
dalle (cadres orange/bleu/rouge sur la carte) et le bouton **Arrêter** envoie
|
||||
un SIGTERM au pipeline. À la fin du run, la carte se rafraîchit et la
|
||||
maintenance de pyramide repart automatiquement.
|
||||
|
||||
### 4. Centrer la carte sur la position GPS (téléphone)
|
||||
|
||||
Les navigateurs n'exposent l'API Geolocation qu'en **contexte sécurisé**
|
||||
(HTTPS) : servir la carte derrière Traefik (TLS) comme ci-dessus, ou en
|
||||
dernier recours en HTTPS direct (l'entrée autonome `python -m
|
||||
lidar_pipeline.mapserve` honore `LIDAR_SSL_CERTFILE` / `LIDAR_SSL_KEYFILE`).
|
||||
|
||||
## Mise à jour
|
||||
|
||||
### Machine de traitement
|
||||
|
||||
```bash
|
||||
cd <checkout> && git pull && docker compose -f docker-compose.worker.yml up -d --build
|
||||
```
|
||||
|
||||
### Pi (depuis le poste de pilotage)
|
||||
|
||||
Le poste de pilotage interdit l'appel `ssh` direct : le wrapper
|
||||
`ssh` fournit l'accès autorisé avec transfert d'agent (`-A`) —
|
||||
les `git pull` distants utilisent la clé locale :
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
HOST="${1:-192.168.3.10}" # machine webapp par défaut (le Pi)
|
||||
HOST="${1:-192.168.3.10}" # machine carte par défaut (le Pi)
|
||||
shift || true
|
||||
exec ssh -A -o BatchMode=yes -o ConnectTimeout=5 \
|
||||
-o StrictHostKeyChecking=accept-new "$HOST" "$@"
|
||||
```
|
||||
|
||||
Usage : `ssh` (Pi par défaut), `ssh <hôte> [commande]`. Mise à jour
|
||||
complète du Pi (checkout git en `/srv/lidar_rendu`, avec
|
||||
l'override Traefik — jamais `serve-webapp.sh` en prod) :
|
||||
Mise à jour complète du Pi (checkout git en `/srv/lidar_rendu`,
|
||||
avec l'override Traefik) :
|
||||
|
||||
```bash
|
||||
ssh 192.168.3.10 "cd /srv/lidar_rendu && git pull && \
|
||||
docker compose -f docker-compose.webapp.yml -f docker-compose.webapp.override.yml up -d --build"
|
||||
docker compose -f docker-compose.maps.yml -f docker-compose.maps.override.yml up -d --build"
|
||||
```
|
||||
|
||||
### 3. Configurer et lancer
|
||||
|
||||
Trois façons, au choix :
|
||||
|
||||
**a. `serve-webapp.sh` (recommandé)** — script de gestion de la stack. Il
|
||||
pilote `docker compose` et charge automatiquement
|
||||
`docker-compose.webapp.override.yml` s'il existe : les labels Traefik et le
|
||||
volume réel de l'override sont préservés. La configuration vit dans
|
||||
`webapp.env` (copie du modèle, non versionné) :
|
||||
|
||||
```bash
|
||||
cp webapp.env.example webapp.env
|
||||
nano webapp.env # LIDAR_WEBAPP_HOST (labels), jeton...
|
||||
./serve-webapp.sh # démarre (build + up -d) et attend le serveur
|
||||
```
|
||||
|
||||
Sous-commandes : `stop` (compose down), `restart` (rebuild + recréation),
|
||||
`status` (conteneur + état du rebuild), `sync` (rebuild + vignettes),
|
||||
`logs`. Le port hôte vient du mapping compose ; `WEBAPP_PORT` ne sert plus
|
||||
qu'aux sondes locales quand le conteneur est arrêté.
|
||||
|
||||
**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 :
|
||||
|
||||
```bash
|
||||
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-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.
|
||||
|
||||
**c. `docker compose`** — éditer `docker-compose.webapp.yml` :
|
||||
|
||||
- `LIDAR_GENERATION_URL` : `http://<ip-machine>:8973`
|
||||
- `LIDAR_REMOTE_TOKEN` : la valeur de `LIDAR_API_TOKEN` de la machine
|
||||
(inutile si aucun token là-bas)
|
||||
- le cache local se remplit à la demande depuis `LIDAR_GENERATION_URL` (pas
|
||||
de `LIDAR_SYNC_CMD`)
|
||||
|
||||
Décommenter aussi le montage de la clé SSH (déploiement) :
|
||||
|
||||
```yaml
|
||||
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.
|
||||
|
||||
```bash
|
||||
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 :
|
||||
|
||||
```bash
|
||||
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, en
|
||||
`websecure` (443) + TLS avec le certificat porté par Traefik. Sur le Pi :
|
||||
|
||||
```bash
|
||||
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.sh` crée `tls/webapp.{crt,key}`, à monter dans le
|
||||
> conteneur avec `LIDAR_SSL_CERTFILE` / `LIDAR_SSL_KEYFILE` (le chemin
|
||||
> `serve-webapp.sh` le fait automatiquement). Le téléphone accepte alors
|
||||
> l'avertissement « certificat non fiable » une fois.
|
||||
|
||||
## Performance réseau (HTTP/2)
|
||||
|
||||
Le serveur interne (uvicorn) ne parle que **HTTP/1.1** : le navigateur y
|
||||
limite chaque origine à 6 connexions simultanées. Peu contraignant depuis
|
||||
l'optimisation de la carte (URLs versionnées servies `Cache-Control:
|
||||
immutable` — zéro revalidation une fois chargées — et mosaïque d'overview à
|
||||
la place des centaines de vignettes en vue dézoomée), mais pour un premier
|
||||
chargement ou un parcours intensif, **passer par Traefik (HTTPS)** active
|
||||
HTTP/2 entre le navigateur et le proxy : toutes les requêtes se multiplexent
|
||||
sur une seule connexion (fin de la file d'attente des 6 connexions).
|
||||
|
||||
- **Via l'entrée `websecure` de Traefik** (route `https://lidar.example.fr`…)
|
||||
: HTTP/2 est déjà actif — comportement par défaut de Traefik sur TLS.
|
||||
Vérification : `curl -sI --http2 https://<domaine>/ | head -1` → `HTTP/2 200`.
|
||||
- **En direct sur `http://<ip-pi>:8973`** (LAN) : HTTP/1.1 — les navigateurs
|
||||
ne négocient pas h2 en clair. Si le besoin s'en fait sentir, exposer une
|
||||
seconde route Traefik vers le même service (h2 côté client, le proxy
|
||||
parlant h1 à uvicorn en backend).
|
||||
- Entre Traefik et le conteneur, rien à changer : la limite qui compte est
|
||||
celle du navigateur vers le frontend.
|
||||
|
||||
## Mise à jour
|
||||
|
||||
Sur le Pi, après un `git pull` :
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.webapp.yml up -d --build # rebuild + redémarrage
|
||||
```
|
||||
|
||||
Avec `serve-webapp.sh` (l'override Traefik est chargé automatiquement s'il
|
||||
existe) :
|
||||
|
||||
```bash
|
||||
./serve-webapp.sh restart # rebuild de l'image + recréation du conteneur
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### Interface de la carte : version embarquée
|
||||
|
||||
Les CSS/JS de l'interface sont **bâchés dans l'image** au build
|
||||
(`Dockerfile.webapp`) : une évolution de l'interface (`lidar_pipeline/index.py`)
|
||||
ne s'applique sur le Pi qu'après rebuild de l'image. Le décalage est détecté
|
||||
automatiquement plutôt que de rester silencieux (tuiles invisibles) :
|
||||
|
||||
- l'interface embarque sa version (`ui_version` = hash CSS+JS source) et
|
||||
compare à celle du serveur de données (`/api/tiles` → `ui_version`,
|
||||
publiée dans `index_tiles.json` par le worker) : en cas de divergence,
|
||||
un **bandeau rouge « Interface périmée »** s'affiche en haut de la carte ;
|
||||
- la webapp consigne un avertissement une fois par paire de versions :
|
||||
`Interface webapp périmée (webapp X, serveur Y) — reconstruire l'image`.
|
||||
|
||||
Réparation : `git pull` + rebuild de l'image (ci-dessus).
|
||||
|
||||
## Dépannage
|
||||
|
||||
- **État d'un rebuild** : `curl http://<ip-pi>:8973/api/sync` → `running`,
|
||||
`phase` (`index`) et `error` (dernière erreur). Les logs du conteneur :
|
||||
`docker logs lidar-webapp` (ou `docker compose -f docker-compose.webapp.yml
|
||||
logs -f webapp`).
|
||||
- **`Permission denied (publickey)` en SSH** : refaire
|
||||
`ssh-copy-id lidar@<ip-machine>` et une connexion manuelle pour renseigner
|
||||
`known_hosts` ; vérifier que `~/.ssh` est monté dans le conteneur
|
||||
(automatique avec `run.sh`, à décommenter en compose).
|
||||
- **Boutons de génération masqués / `403` sur `/api/generate`** : l'IP du
|
||||
client n'est dans aucun des réseaux de `LIDAR_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`) et
|
||||
`LIDAR_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.
|
||||
- **Bandeau « Interface périmée » en haut de la carte** (ou tuiles invisibles
|
||||
sans explication) : l'image webapp est plus ancienne que l'interface du
|
||||
worker (assets bâchés au build). Réparer : `git pull` +
|
||||
`docker compose -f docker-compose.webapp.yml up -d --build` sur le Pi.
|
||||
- **Port 8973 déjà pris** : `WEBAPP_PORT=9000 ./serve-webapp.sh` (ou la
|
||||
variable dans `webapp.env`), `--serve-webapp 9000` avec `run.sh`, ou
|
||||
changer le mapping `ports:` 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-webapp` sert la
|
||||
carte en lecture seule depuis `output/` (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 dans `output/`, 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 serve` fonctionne comme avant en local (tout-en-un), et `docker-compose.worker.yml` installe 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, jeton
|
||||
`LIDAR_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), configuration locale dans
|
||||
`webapp.env` (ignoré par git).
|
||||
Le code de l'interface est bâché dans l'image (`Dockerfile.maps`) : **TOUT**
|
||||
changement d'interface exige le rebuild (`--build`), sans lui l'ancien code
|
||||
tourne.
|
||||
|
||||
35
docs/MAPS.md
35
docs/MAPS.md
@ -5,8 +5,10 @@ Carte « slippy » classique — même schéma de tuiles que Google Maps et
|
||||
deviennent ainsi un **fond d'imagerie réutilisable** dans JOSM, iD, QGIS, uMap,
|
||||
MapLibre ou OsmAnd, en plus de l'interface de consultation fournie.
|
||||
|
||||
Elle coexiste avec la webapp historique (port 8973), sans rien lui changer :
|
||||
même dossier `output/`, génération et export restent là-bas.
|
||||
C'est la SEULE interface web du projet (l'ancienne webapp historique a été
|
||||
retirée) : elle embarque aussi la **génération de tuiles** — locale sur
|
||||
l'image complète (worker, `./run.sh --serve`), déléguée via
|
||||
`LIDAR_GENERATION_URL` sur la machine légère (cf. `docs/DEPLOY_WEBAPP.md`).
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.maps.yml up -d --build # http://localhost:8975/
|
||||
@ -14,6 +16,35 @@ docker compose -f docker-compose.maps.yml logs -f maps
|
||||
./run.sh --serve-maps # équivalent en conteneur au premier plan
|
||||
```
|
||||
|
||||
## Générer des tuiles depuis la carte
|
||||
|
||||
- **+ Zone** (barre d'outils) — dessiner un rectangle : les dalles LHD de 1 km
|
||||
qui l'intersectent sont téléchargées depuis la géoplateforme IGN puis
|
||||
traitées (0,2 m, options ci-dessous) ; les zones et clics successifs
|
||||
s'additionnent ;
|
||||
- **⤒ Compléter** — toutes les dalles déjà présentes dans `input/` qui
|
||||
manquent au moins une des couches demandées ;
|
||||
- **↻ Générer cette dalle** (fiche d'infos au clic) — une dalle précise,
|
||||
même sans données existantes.
|
||||
|
||||
Options du run : couches visées (défaut : les couches du panneau),
|
||||
classification du sol (`ign`/`auto`/`smrf`/`csf`), classes LAS IGN
|
||||
(`sol,unclassified…`), régénération forcée, reclassification, raccord des
|
||||
bords (bande 100 m). Une demande lancée pendant un run part en **file
|
||||
d'attente** (persistée, jamais de coupure du travail en place) ; la
|
||||
progression s'affiche dalle par dalle (cadres orange = rendu en cours, bleu =
|
||||
en attente, rouge = échec) avec journal et bouton **Arrêter** (SIGTERM puis
|
||||
SIGKILL). À la fin du run, la carte se rafraîchit et la maintenance de
|
||||
pyramide repart automatiquement.
|
||||
|
||||
Les boutons sont masqués si le navigateur vient d'une IP hors
|
||||
`LIDAR_REGEN_CIDR` (défaut : localhost + plages privées RFC1918) ou si aucun
|
||||
backend de génération n'existe (image légère sans `LIDAR_GENERATION_URL`).
|
||||
Sur la machine légère, tout est transmis au worker
|
||||
(`LIDAR_GENERATION_URL`), qui exécute le pipeline complet ; ses dalles sont
|
||||
rapatriées à la demande par l'inventaire `/api/tiles` + les statiques
|
||||
versionnées (`LIDAR_SOURCE_URL`).
|
||||
|
||||
## Contrat de tuilage
|
||||
|
||||
| Point | Valeur |
|
||||
|
||||
Reference in New Issue
Block a user