425 lines
23 KiB
Markdown
425 lines
23 KiB
Markdown
# Carte à tuiles XYZ (image `lidar-maps`)
|
||
|
||
Carte « slippy » classique — même schéma de tuiles que Google Maps et
|
||
**OpenStreetMap** — servie par une image légère dédiée. Les rendus du pipeline
|
||
deviennent ainsi un **fond d'imagerie réutilisable** dans JOSM, iD, QGIS, uMap,
|
||
MapLibre ou OsmAnd, en plus de l'interface de consultation fournie.
|
||
|
||
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/
|
||
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) et
|
||
régénération forcée. La classification du sol (IGN, sol seul) et le raccord
|
||
des bords (bande de 100 m prise aux dalles voisines) sont imposés : aucun
|
||
réglage. 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). Pendant le run, chaque dalle terminée apparaît sur la carte : l'inventaire
|
||
est mis à jour après chaque dalle et la maintenance de pyramide traite ses
|
||
tuiles en priorité (surveillance toutes les 10 s).
|
||
|
||
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 |
|
||
|---|---|
|
||
| Projection | EPSG:3857, schéma XYZ OSM (origine nord-ouest, `y` vers le sud) |
|
||
| URL canonique | `/tiles/{couche}/{z}/{x}/{y}.png` — 256 px, PNG RGBA |
|
||
| Variante haute densité | `/tiles/{couche}/{z}/{x}/{y}@2x.webp` — 512 px (interface interne) |
|
||
| Zooms | 5 → 19 natif (0,2 m/px ≈ z19 en France) ; au-delà, sur-zoom côté client |
|
||
| Hors emprise | tuile entièrement transparente (superposable), en-tête `X-Tile-Empty: 1` |
|
||
| En attente (cache seule) | tuile transparente, en-têtes `X-Tile-Empty: 1` + `X-Tile-Pending: 1`, jamais mémorisée par le navigateur |
|
||
| CORS | `Access-Control-Allow-Origin: *` sur `/tiles/*` |
|
||
| Attribution | `LIDAR_ATTRIBUTION`, défaut « LiDAR HD © IGN — Licence Ouverte 2.0 » |
|
||
|
||
Découverte : `/tiles/{couche}.json` (TileJSON 3.0.0), `/tiles/wmts.xml`
|
||
(WMTS 1.0.0, grille `GoogleMapsCompatible`), `/tiles/josm.imagery.xml`
|
||
(toutes les couches d'un coup dans JOSM).
|
||
|
||
## Utiliser les tuiles ailleurs
|
||
|
||
- **JOSM** — *Imagery → Imagery preferences → + TMS*, coller
|
||
`http://<hôte>:8975/tiles/slope/{zoom}/{x}/{y}.png`. Pour tout ajouter d'un
|
||
coup : *Imagery preferences → Offline/Custom → Add imagery source XML* avec
|
||
`http://<hôte>:8975/tiles/josm.imagery.xml`.
|
||
- **iD** — *Fond de carte → Personnalisé*, coller
|
||
`http://<hôte>:8975/tiles/slope/{z}/{x}/{y}.png`.
|
||
- **QGIS** — *XYZ Tiles → Nouvelle connexion* (même URL, zoom max 19), ou
|
||
*WMS/WMTS → Nouveau* avec `http://<hôte>:8975/tiles/wmts.xml`.
|
||
- **uMap / MapLibre / Leaflet** — même gabarit XYZ, ou le TileJSON.
|
||
- **OsmAnd** — source de tuiles en ligne, gabarit XYZ, zoom max 19.
|
||
|
||
Le bouton **« Utiliser dans JOSM / QGIS »** de la carte affiche et copie ces
|
||
URL pour la couche choisie.
|
||
|
||
## Pyramide complète générée d'avance
|
||
|
||
Par défaut, **tous** les niveaux jusqu'au natif (19 en numérotation OSM
|
||
standard = 0,2 m/px, servi en `18@2x` par l'interface) sont écrits sur
|
||
disque en AVIF et générés d'avance par la maintenance de fond (sur une carte
|
||
alimentée par l'amont : téléchargés depuis `LIDAR_MAPS_URL`). Aucun niveau
|
||
n'est rendu à la volée : le rendu à la demande d'un Raspberry Pi (~170 ms par
|
||
tuile, 3 à la fois) donnait 1,5 à 6 s par écran aux zooms 17–19. L'interface
|
||
est plafonnée au zoom 19 (1 px écran = 1 px LiDAR) : jamais de tuile
|
||
agrandie. Ordre de grandeur mesuré : ~110 000 tuiles @2x pour 3 240 dalles,
|
||
dont 81 000 au niveau natif, ~5 Go.
|
||
|
||
Stockage réduit (disque compté) : `LIDAR_TILE_CACHE_MAX_Z` plus bas et/ou
|
||
`LIDAR_TILE_EVEN_LEVELS=1` (niveaux pairs seuls ; l'interface réduit alors
|
||
les tuiles du niveau supérieur aux zooms impairs, les autres niveaux sont
|
||
rendus à la volée avec un cache mémoire `LIDAR_TILE_MEMORY_CACHE_MB`).
|
||
|
||
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
|
||
|
||
Un clic sur la carte sélectionne la dalle LiDAR HD sous le curseur (cadre
|
||
jaune en pointillés), qu'elle soit générée ou non. La fiche s'affiche tout de
|
||
suite avec le nom, l'emprise Lambert 93, puis, si la dalle est rendue, la
|
||
résolution, la date de génération et le recalage vertical des passes
|
||
(faisceaux, décalages, correction des lignes). Les informations IGN arrivent
|
||
à part (`GET /api/map/ign?col&row`, catalogue STAC mis en cache dans
|
||
`output/ign_meta/`) : date et heure du scan LiDAR, capteurs, mission,
|
||
opérateur, date d'édition, procédé de classement, nombre de points et lien de
|
||
téléchargement du nuage `.copc.laz` sur la géoplateforme. Un catalogue lent
|
||
ou injoignable n'empêche jamais la sélection.
|
||
|
||
Quand le relief orienté est affiché, une rose des vents donne la couleur de
|
||
chaque orientation de pente (même formule CIELAB que le rendu) ; la clarté
|
||
porte le relief local (clair = bosse, sombre = creux).
|
||
|
||
## Affichage : relief et précision
|
||
|
||
La carte ne sert que les couches de `PANEL_VIZ` (`index.py`) : le **relief
|
||
orienté** (couche d'affichage principal, qui fusionne openness locale et
|
||
orientation des pentes) et la **précision** (`densite_sol` : densité des points
|
||
sol retenus pour le MNT). Les autres visualisations présentes sur disque ne
|
||
sont ni listées ni servies en tuiles.
|
||
|
||
Il n'y a plus de pile de couches. Le panneau propose trois modes, un clic
|
||
chacun, ou la touche **P** pour passer au suivant :
|
||
|
||
- **Relief** — le relief orienté seul ;
|
||
- **Précision** — la densité seule, en 16 gris (échelle log fixe : niveau k à
|
||
partir de 0,25 × 2^(k/2) pts/m², noir ≤ 0,35 ou aucun point, blanc ≥ 45) ;
|
||
se lit comme une carte de fiabilité géométrique ;
|
||
- **Les deux** — la précision en « produit » (`mix-blend-mode: multiply`) sur
|
||
le relief, opacité réglable (60 % par défaut) : les zones où le relief est
|
||
interpolé s'assombrissent sans masquer le relief.
|
||
|
||
La légende de la précision (16 paliers, info-bulle en pts/m² sur chaque
|
||
palier) s'affiche dès que la précision est visible. Les couches LiDAR vivent
|
||
dans un conteneur **isolé** (`isolation: isolate`) : le produit n'agit que sur
|
||
le relief, jamais sur le fond de carte.
|
||
|
||
Le lien de partage transporte la couche principale, le mode et l'opacité :
|
||
`#z/lat/lng&M=relief_oriente&P=both:60&B=1:85:1`. Les anciens liens de la pile
|
||
(`&L=…`) s'ouvrent sans erreur sur l'état courant.
|
||
|
||
### Figer la configuration
|
||
|
||
Le bouton **★ Définir par défaut** enregistre l'affichage courant — couche
|
||
principale, mode, opacité de la précision, fond de carte — dans
|
||
`output/.map-defaults.json`. Tout navigateur sans réglage local part alors de
|
||
cette configuration ; **↺ Réinitialiser** oublie l'état local et y revient.
|
||
|
||
```bash
|
||
curl http://localhost:8975/api/map/defaults # configuration servie
|
||
curl -X DELETE http://localhost:8975/api/map/defaults # retour au registre
|
||
```
|
||
|
||
Sans fichier enregistré, les défauts viennent du registre du pipeline
|
||
(`DEFAULT_VIZ`, `PRECISION_VIZ`, `DEFAULT_VIEW_MODE`,
|
||
`DEFAULT_PRECISION_OPACITY` dans `index.py`). Les valeurs reçues sont
|
||
filtrées : couche inconnue (ou la précision elle-même) refusée comme
|
||
principale, mode validé, opacités bornées à 0–1. Un fichier de l'ancienne pile
|
||
(`order`/`on`/`blend`) est ignoré, sauf le fond.
|
||
|
||
## Export PDF (planche d'impression terrain)
|
||
|
||
Le bouton **⎙** ouvre une carte de réglages (format A4/A3, paysage/portrait,
|
||
échelle 1:1 000 à 1:10 000, titre optionnel) et affiche un **cadre jaune en
|
||
pointillés** qui montre la zone qui sera imprimée. Le cadre est **posé sur le
|
||
terrain** : il se place au centre de la vue à l'ouverture (ou garde sa position
|
||
précédente si elle est encore visible), la carte zoome pour le montrer en
|
||
entier au-dessus des panneaux, puis on navigue librement sans qu'il bouge. On le
|
||
déplace en faisant glisser sa **poignée ✥** (souris ou doigt) ; au relâchement,
|
||
la géométrie Lambert 93 exacte est recalculée (`GET /api/export/frame`).
|
||
« ⌖ Centrer ici » le ramène au centre de la vue, « ⤢ Voir le cadre » zoome
|
||
dessus ; changer de format, d'orientation ou d'échelle recadre la vue. Les
|
||
réglages et la position du cadre sont mémorisés dans `localStorage` du
|
||
navigateur. **Exporter le PDF** télécharge la planche (`GET
|
||
/api/export/pdf`), nommée `relief_{x_km}_{y_km}_1-{échelle}.pdf` (centre
|
||
Lambert 93 en km, à trois décimales).
|
||
|
||
La planche (module `lidar_pipeline/export_pdf.py`) est composée directement
|
||
en Lambert 93 depuis les sources déjà rendues (pas de passage par les tuiles
|
||
XYZ), puis dessinée en vectoriel (texte, grille, légende) avec `reportlab` —
|
||
Pillow + pyproj + reportlab uniquement, **sans numpy** : elle tourne aussi
|
||
bien sur l'image complète que sur l'image légère du Pi seul. Contenu :
|
||
|
||
- la carte du **relief orienté**, recadrée à l'échelle demandée (300 dpi en
|
||
A4, 250 dpi en A3 — borne la mémoire du Pi, ~36 Mo en A3) ; hors emprise des
|
||
dalles disponibles, la zone reste blanche et hachurée ;
|
||
- un **quadrillage Lambert 93** (pas 100 m aux échelles 1:1 000/1:2 000, 500 m
|
||
au 1:5 000, 1 000 m au 1:10 000) gradué en marge, et les **coins WGS84** de
|
||
la zone imprimée aux quatre angles ;
|
||
- une **flèche du nord géographique** tenant compte de la convergence du
|
||
méridien (la carte est orientée sur le nord du quadrillage L93, pas le nord
|
||
géographique — l'écart est indiqué en degrés) ;
|
||
- une **échelle graphique** (barre alternée) et l'**échelle numérique** ;
|
||
- une **rose des orientations à 8 points** (N/NE/E/SE/S/SO/O/NO), même
|
||
formule CIELAB que la rose de la fiche de dalle ;
|
||
- le texte de légende du relief orienté, repris de `VIZ_LEGENDS` (source
|
||
unique avec l'interface et le TileJSON) ;
|
||
- un **encart qualité** : miniature de la densité de points sol (mailles
|
||
50 m, classes de couleur), chiffres clés (densité moyenne, maille la plus
|
||
faible, part de surface interpolée, période d'acquisition), zones **hachurées
|
||
en blanc** là où la qualité n'est pas renseignée, et une ligne « **Donnée
|
||
manquante (sans relief)** » listant les dalles de la zone qui n'ont pas
|
||
encore été générées ;
|
||
- un cartouche : titre (par défaut, liste des dalles couvertes), échelle,
|
||
format, dpi, centre L93, taille de la zone, date d'export et mention de
|
||
source IGN.
|
||
|
||
Un seul export PDF à la fois : un second appel pendant qu'un export tourne
|
||
reçoit `429` (réessayer). L'export n'est **jamais délégué** à
|
||
`LIDAR_GENERATION_URL` : contrairement à la génération de tuiles, la carte
|
||
légère seule (Pi sans worker) sait exporter par elle-même, à partir des
|
||
sources déjà rapatriées sur disque.
|
||
|
||
> **Licence** — LiDAR HD est diffusé sous **Licence Ouverte 2.0** :
|
||
> l'attribution IGN est obligatoire et doit rester visible chez le client.
|
||
> Avant d'utiliser ces rendus comme calque de **saisie** dans OpenStreetMap,
|
||
> vérifier la position de la communauté (OSM-FR) sur la source concernée.
|
||
|
||
## Comment une tuile est fabriquée
|
||
|
||
1. L'emprise de la tuile est convertie en Lambert 93 (échantillonnage 5×5 du
|
||
contour : les bords ne sont pas droits en L93).
|
||
2. Les dalles de 1 km qui l'intersectent sont retrouvées par leur nom
|
||
(`LHD_FXX_{col}_{row}` → X ∈ [col, col+1] km, Y ∈ [row−1, row] km).
|
||
3. Pour chacune, le **palier source** le plus grossier suffisant est choisi
|
||
parmi ceux que le pipeline produit déjà : vignette `index_thumbs`
|
||
(≈3,9 m/px), vignette intermédiaire `_mid` (1,56 m/px), quadrants
|
||
`index_subtiles` (2500 px, 4× moins à décoder que la dalle) puis la dalle.
|
||
Les trois dossiers sont scannés **indépendamment** : un cache partiel — une
|
||
machine légère ne rapatrie que quadrants et vignettes, jamais les dalles
|
||
entières — reste entièrement exploitable.
|
||
4. La fenêtre utile est découpée puis reprojetée par **transformation
|
||
projective** (`Image.transform(..., PERSPECTIVE)`), dalle par dalle : le
|
||
calage mesuré est inférieur au pixel.
|
||
5. Le résultat est encodé (PNG ou WebP) et écrit dans
|
||
`output/index_xyz/{couche}/{z}/{x}/{y}[@2x].{ext}`.
|
||
|
||
Aucune dépendance GDAL/PDAL : **Pillow + pyproj** uniquement.
|
||
|
||
### Cache et péremption
|
||
|
||
- Une tuile en cache est resservie tant qu'**aucune dalle contributrice n'est
|
||
plus récente qu'elle** : régénérer une dalle n'invalide que ses tuiles.
|
||
- Une tuile sans donnée est mémorisée par un marqueur `.empty` — jamais
|
||
recalculée.
|
||
- Les images sources décodées sont gardées dans un petit cache LRU : le
|
||
décodage AVIF domine le coût, les tuiles voisines le réutilisent.
|
||
- Côté navigateur, l'interface ajoute `?v=<stamp>` (mtime la plus récente) et
|
||
reçoit alors un cache immuable ; les clients OSM utilisent l'URL nue, servie
|
||
en revalidation courte.
|
||
|
||
### Mesures (dalles réelles 0,2 m, bloc 3×3 km, conteneur sans GPU)
|
||
|
||
| | premier rendu | depuis le cache |
|
||
|---|---|---|
|
||
| z10–z14 | 50–200 ms | 3–9 ms |
|
||
| z16–z18 (résolution native) | 46–130 ms | 3–9 ms |
|
||
|
||
Poids d'une tuile de pente à z17, mesuré sur la même tuile :
|
||
|
||
| encodage | poids | fidélité |
|
||
|---|---|---|
|
||
| PNG RGBA (défaut) | 210 Ko | sans perte |
|
||
| PNG palettisé (`LIDAR_TILE_PNG_PALETTE=1`) | 55 Ko | écart moyen 5,8 niveaux |
|
||
| WebP q78 (`…/{y}.webp`) | 38 Ko | avec perte, visuellement propre |
|
||
|
||
Le PNG canonique reste **sans perte** par défaut : ces rendus servent à
|
||
l'interprétation, pas à l'illustration. Pour un usage en fond d'imagerie où le
|
||
débit compte, deux leviers : demander l'URL `.webp` (les clients qui la
|
||
supportent : QGIS, iD, MapLibre, uMap) ou activer `LIDAR_TILE_PNG_PALETTE=1`.
|
||
|
||
Contrôles de calage effectués sur données réelles : un chemin OSM se superpose
|
||
exactement à la trace visible dans la couche de pente, et l'écart de couture
|
||
entre tuiles voisines reste **inférieur au bruit naturel du terrain** (écart
|
||
mesuré 35–53 niveaux contre une médiane de 49–53 entre deux colonnes voisines
|
||
prises au hasard dans la même tuile).
|
||
|
||
## Charge et petites machines
|
||
|
||
Le service est borné à chaque étage, rien n'est illimité :
|
||
|
||
| Étage | Défaut | Réglage |
|
||
|---|---|---|
|
||
| Rendus simultanés | 2 | `LIDAR_TILE_WORKERS` |
|
||
| Téléchargements de dalles simultanés | 2 | `LIDAR_TILE_FETCH_WORKERS` |
|
||
| Même tuile demandée en parallèle | 1 rendu, les autres attendent son résultat | — |
|
||
| Mémoire des sources décodées | 192 Mo | `LIDAR_TILE_SOURCE_CACHE_MB` |
|
||
|
||
Les requêtes en excès attendent le sémaphore **avant** tout décodage : elles ne
|
||
consomment ni CPU ni mémoire. Un navigateur en HTTP/1.1 n'ouvre de toute façon
|
||
que 6 connexions par origine, toutes couches confondues ; derrière un proxy HTTP/2 ce plafond disparaît et seuls ces réglages tiennent la charge.
|
||
|
||
Le pré-chauffage (`/api/map/warm`) est séquentiel : il ne peut pas saturer la
|
||
machine, seulement prendre du temps.
|
||
|
||
Sur un Raspberry Pi 2 Go, `LIDAR_TILE_WORKERS=1` et
|
||
`LIDAR_TILE_SOURCE_CACHE_MB=64` restent confortables.
|
||
|
||
## Cache seule + maintenance de fond (petit Raspberry Pi)
|
||
|
||
Sur une machine qui ne doit jamais calculer au fil de la navigation (un Pi qui
|
||
tient aussi d'autres services), deux variables inversent la charge :
|
||
|
||
```yaml
|
||
environment:
|
||
- LIDAR_TILE_CACHE_ONLY=1 # la navigation ne rend plus rien
|
||
- LIDAR_TILE_BACKGROUND=1 # une tâche de fond entretient la pyramide
|
||
```
|
||
|
||
- **`LIDAR_TILE_CACHE_ONLY=1`** — une tuile absente ou périmée est servie
|
||
transparente avec `X-Tile-Pending: 1` et `Cache-Control: no-store` (le
|
||
navigateur la redemande : dès que la maintenance l'a rendue, elle apparaît).
|
||
Plus aucun rendu local sur le chemin des requêtes ; avec `LIDAR_MAPS_URL`,
|
||
une tuile manquante (pas encore passée par la maintenance) y est rapatriée — un téléchargement de quelques dizaines de Ko, mis en
|
||
cache — puis servie : la navigation couvre tous les zooms sans jamais
|
||
calculer sur la petite machine.
|
||
- **`LIDAR_TILE_BACKGROUND=1`** — un sondeur rescane les dalles à intervalle
|
||
régulier (`LIDAR_TILE_BACKGROUND_INTERVAL`, 120 s) ; chaque dalle nouvelle ou
|
||
régénérée (le worker vient de produire, le cache à la demande vient de
|
||
rapatrier) met sa pyramide en file d'attente. Des rendeurs à basse priorité
|
||
(`os.nice`) la vident au rythme d'une tuile par
|
||
`LIDAR_TILE_BACKGROUND_PAUSE` seconde (1 s). Premier démarrage : TOUTES les
|
||
dalles sont inconnues, la pyramide se reconstruit entière — les tuiles déjà
|
||
fraîches ne coûtent qu'un `stat`. Les niveaux partent en file du plus petit
|
||
zoom au plus grand : la carte se remplit d'abord grossièrement.
|
||
|
||
| Variable | Défaut | Rôle |
|
||
|---|---|---|
|
||
| `LIDAR_TILE_BACKGROUND_MAX_Z` | natif (`18` en @2x, `19` en 256 px) | niveau maximal entretenu en fond, numérotation des URL |
|
||
| `LIDAR_TILE_BACKGROUND_SCALE` | `2` | tuiles entretenues : 2 = 512 px (celles de l'interface) |
|
||
| `LIDAR_TILE_BACKGROUND_FMT` | `webp` | format des tuiles entretenues (celui de l'interface) |
|
||
| `LIDAR_TILE_BACKGROUND_PAUSE` | `1.0` | pause (s) entre deux rendus — le levier de la discrétion |
|
||
| `LIDAR_TILE_BACKGROUND_INTERVAL` | `120` | secondes entre deux scans des dalles |
|
||
| `LIDAR_TILE_BACKGROUND_QUEUE_MAX` | `65536` | file d'attente bornée — chaque dalle mémorise la première tuile refusée faute de place et reprend de là aux scans suivants, jusqu'à ce que toute sa pyramide soit passée |
|
||
|
||
Pilotage : `GET /api/map/background` (état, compteurs, file), `POST
|
||
/api/map/background` (scan immédiat), `POST /api/map/warm` (pré-calcul manuel
|
||
exhaustif, tous zooms/formats, cf. ci-dessous). Enfin, plafonner le conteneur
|
||
lui-même (`mem_limit` + `memswap_limit` dans un override compose) garantit
|
||
qu'un rendu déréglé ne peut plus emporter la machine : le tueur OOM ne
|
||
toucherait que `lidar-maps`.
|
||
|
||
## Pré-chauffage
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8975/api/map/warm \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"layers": ["slope"], "z_min": 10, "z_max": 16}'
|
||
curl http://localhost:8975/api/map/warm # avancement / compte rendu
|
||
```
|
||
|
||
Sans `bounds`, l'emprise des dalles disponibles est utilisée. Utile après un
|
||
gros run pour que la première consultation soit instantanée.
|
||
|
||
## Deux machines
|
||
|
||
Deux amonts complémentaires, selon ce que la machine locale possède.
|
||
|
||
`LIDAR_SOURCE_URL` désigne la **webapp du pipeline** (port 8973) : l'inventaire
|
||
des dalles vient de son `/api/tiles` et chaque image source est rapatriée au
|
||
premier rendu qui en a besoin. Le conteneur carte démarre alors sans aucune
|
||
donnée locale et sert tout le catalogue distant :
|
||
|
||
```bash
|
||
docker run -d --name lidar-maps --network host \
|
||
-e LIDAR_SOURCE_URL=http://127.0.0.1:8973 \
|
||
-e LIDAR_OUTPUT_DIR=/data/output -e LIDAR_PORT=8975 \
|
||
-v "$PWD/output-maps:/data/output" --user "$(id -u):$(id -g)" lidar-maps
|
||
```
|
||
|
||
Mesuré sur 849 dalles réelles servies par un tunnel SSH : première tuile d'une
|
||
zone 0,9–3,2 s (téléchargement d'un quadrant de 1,8 Mo compris), puis **~1 ms**
|
||
depuis le cache local, qui ne garde que ce qui a été consulté.
|
||
|
||
`LIDAR_MAPS_URL` désigne un serveur de tuiles amont (machine de traitement) :
|
||
une tuile absente localement y est rapatriée (quelques dizaines de Ko) puis
|
||
mise en cache — au lieu de rapatrier les dalles entières. Un disjoncteur
|
||
suspend les tentatives 2 minutes après 3 échecs consécutifs : carte
|
||
consultable même worker éteint.
|
||
|
||
## Variables d'environnement
|
||
|
||
| Variable | Défaut | Rôle |
|
||
|---|---|---|
|
||
| `LIDAR_OUTPUT_DIR` | `/data/output` | dalles lues, cache `index_xyz/` écrit |
|
||
| `LIDAR_PORT` | `8975` | port d'écoute |
|
||
| `LIDAR_SOURCE_URL` | — | webapp du pipeline : inventaire + dalles rapatriées à la demande |
|
||
| `LIDAR_SOURCE_TOKEN` | — | jeton si la webapp amont exige `LIDAR_API_TOKEN` |
|
||
| `LIDAR_MAPS_URL` | — | serveur de tuiles amont (carte déportée) |
|
||
| `LIDAR_ATTRIBUTION` | LiDAR HD © IGN | mention servie (TileJSON, WMTS, JOSM, carte) |
|
||
| `LIDAR_TILE_PNG_PALETTE` | — | `1` : PNG palettisé (~4× plus léger, écart ~6 niveaux) |
|
||
| `LIDAR_TILE_SOURCE_CACHE_MB` | `192` | budget mémoire du cache d'images sources décodées |
|
||
| `LIDAR_TILE_WORKERS` | `2` | rendus de tuiles simultanés |
|
||
| `LIDAR_TILE_FETCH_WORKERS` | `2` | téléchargements de dalles simultanés (mode `LIDAR_SOURCE_URL`) |
|
||
| `LIDAR_SSL_CERTFILE` / `LIDAR_SSL_KEYFILE` | — | HTTPS direct (GPS sur téléphone) |
|
||
|
||
## Dépannage
|
||
|
||
- **Carte vide, `layers: 0`** (`curl /healthz`) : aucun rendu dans
|
||
`output/visualisations/`, ou dossier monté au mauvais endroit.
|
||
- **Tuiles transparentes partout** : zoom hors plage (5–19) ou zone sans
|
||
dalle ; l'en-tête `X-Tile-Empty` le confirme.
|
||
- **Première consultation lente** : normal, chaque tuile est rendue une fois —
|
||
pré-chauffer (ci-dessus).
|
||
- **JOSM refuse l'URL** : utiliser le gabarit `{zoom}/{x}/{y}` (JOSM) et non
|
||
`{z}/{x}/{y}` (Leaflet/QGIS).
|
||
- **Couche absente du menu** : elle n'existe pas sur disque pour ces dalles —
|
||
`/api/map/meta` liste ce qui est réellement disponible.
|
||
|
||
## Références
|
||
|
||
- `lidar_pipeline/tiles.py` — grille, reprojection, cache, pré-chauffage.
|
||
- `lidar_pipeline/mapserve.py` — routes tuiles/TileJSON/WMTS/JOSM et API carte.
|
||
- `lidar_pipeline/mapui.py` — interface (Leaflet `L.tileLayer`, LOD natif).
|
||
- `Dockerfile.maps`, `docker-compose.maps.yml`, `./run.sh --serve-maps`.
|