180 lines
7.1 KiB
Markdown
180 lines
7.1 KiB
Markdown
# Two-machine deployment: lightweight map + processing worker
|
||
|
||
Two-machine architecture: the **map** (interface, XYZ tile pyramid, API) runs
|
||
on a Raspberry Pi; **tile generation** (IGN downloads, PDAL, GPU) runs on a
|
||
powerful machine. Both run the same server (`lidar_pipeline.mapserve`, image
|
||
`lidar-maps`), the powerful machine in its full version (pipeline included).
|
||
|
||
```
|
||
Browser ──HTTP──▶ Raspberry Pi (lightweight lidar-maps image, port 8975)
|
||
│ serves the map + the pyramid (cache-only + background
|
||
│ maintenance, LIDAR_TILE_CACHE_ONLY / LIDAR_TILE_BACKGROUND)
|
||
│ /api/generate, /api/preview, /api/status
|
||
│ └─ forwarded to ──▶ processing worker
|
||
│ tiles fetched back from the worker (LIDAR_SOURCE_URL:
|
||
│ /api/tiles inventory + versioned static assets)
|
||
▼
|
||
Processing worker (full image,
|
||
docker-compose.worker.yml service `worker`):
|
||
IGN download + GPU pipeline (tile generator)
|
||
+ tile pyramid + tile inventory
|
||
```
|
||
|
||
See also `docs/MAPS.md` for the map/tile-pyramid internals referenced below.
|
||
|
||
## Processing worker (tile generator)
|
||
|
||
The `worker` service acts as the generator: it accepts generation requests
|
||
sent by remote maps (IGN download + PDAL/GPU processing), serves its own tile
|
||
pyramid and tile inventory (`/api/tiles` + static `visualisations/`,
|
||
`index_thumbs/`, `index_subtiles/`) that lightweight machines fetch on demand.
|
||
|
||
```bash
|
||
docker compose -f docker-compose.worker.yml up -d --build # API at http://<worker-ip>:8973
|
||
docker compose -f docker-compose.worker.yml logs -f worker
|
||
```
|
||
|
||
One-off batch processing of tiles already present in `input/`:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.worker.yml run --rm --build process [-r 0.2 | --force]
|
||
```
|
||
|
||
On a machine without a GPU: remove the `gpus: all` lines (CPU processing,
|
||
slower). Optional but recommended if the network isn't trusted: protect the
|
||
API with a shared token — uncomment in `docker-compose.worker.yml`:
|
||
|
||
```yaml
|
||
environment:
|
||
- LIDAR_API_TOKEN=a-secret-to-share
|
||
```
|
||
|
||
## Raspberry Pi (lightweight map)
|
||
|
||
### 0. Prerequisites on the Pi
|
||
|
||
- **Architecture**: the image is built natively on the machine (ARM64 or
|
||
x86_64, check with `uname -m`). The build happens on the Pi itself
|
||
(`Dockerfile.maps`: `python:3.12-slim` base, ~200 MB, no PDAL/GDAL).
|
||
- **Docker + compose plugin**: official install
|
||
[docs.docker.com/engine/install](https://docs.docker.com/engine/install/)
|
||
(verify with `docker compose version`).
|
||
- **Disk space**: plan for the cache size (fetched tiles + rendered tiles;
|
||
use the size of `output/` on the processing machine as a reference).
|
||
|
||
### 1. Copy the code onto the Pi (git clone)
|
||
|
||
```bash
|
||
git clone https://github.com/<you>/lidar_rendu.git lidar
|
||
cd lidar
|
||
```
|
||
|
||
`output/` and `input/` are git-ignored: the repository only holds the code,
|
||
the cache fills on demand from the processing machine.
|
||
|
||
### 2. Start the map
|
||
|
||
`docker-compose.maps.yml` (versioned) serves as the base; the Pi's local
|
||
configuration lives in an **override** file `docker-compose.maps.override.yml`
|
||
(not versioned). Start from the provided template:
|
||
|
||
```bash
|
||
cp docker-compose.maps.override.yml.example docker-compose.maps.override.yml
|
||
```
|
||
|
||
It looks like this (adapt addresses, cache folder and Traefik routing):
|
||
|
||
```yaml
|
||
name: lidar-maps
|
||
services:
|
||
maps:
|
||
volumes: !override # Compose >= 2.24 (replaces ./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 (tiles + inventory)
|
||
- LIDAR_GENERATION_URL=http://192.168.1.50:8973 # worker (delegated generation)
|
||
# - LIDAR_REMOTE_TOKEN=a-secret-to-share # if LIDAR_API_TOKEN is set on the worker
|
||
- LIDAR_TILE_WORKERS=1
|
||
- LIDAR_TILE_SOURCE_CACHE_MB=64
|
||
- LIDAR_TILE_CACHE_ONLY=1 # browsing renders NOTHING
|
||
- LIDAR_TILE_BACKGROUND=1 # the pyramid is maintained as a background task
|
||
- LIDAR_TILE_BACKGROUND_PAUSE=1.0
|
||
networks: [webapp, proxy]
|
||
labels: # optional Traefik routing
|
||
- "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
|
||
```
|
||
|
||
```bash
|
||
mkdir -p /srv/lidar/output # owned by uid 1000 (container)
|
||
docker compose -f docker-compose.maps.yml -f docker-compose.maps.override.yml up -d --build
|
||
```
|
||
|
||
The map is available at `http://<pi-host>:8975/` (and via Traefik if
|
||
configured).
|
||
|
||
`LIDAR_TILE_CACHE_ONLY=1` + `LIDAR_TILE_BACKGROUND=1` flip the load pattern on
|
||
a small Pi: browsing renders nothing (a missing tile is served transparent,
|
||
with `X-Tile-Pending`), and a background task watches for new or regenerated
|
||
tiles and maintains the pyramid at low priority — see `docs/MAPS.md`, section
|
||
"Cache-only + background maintenance".
|
||
|
||
### 3. Generating tiles from the map
|
||
|
||
The **+ Zone** ("Add area": draw a rectangle → IGN download + run on the
|
||
worker), **⤒ Compléter** ("Complete": already-downloaded but incomplete
|
||
tiles) and **↻ Générer cette dalle** ("Generate this tile": info panel on
|
||
click) buttons are hidden if:
|
||
|
||
- the browser's IP is outside `LIDAR_REGEN_CIDR` (default: loopback + private
|
||
RFC1918 ranges; a comma-separated list of CIDRs, or an empty string to lift
|
||
the restriction). The IP is the one seen on the connection (preserved by
|
||
Docker's DNAT for LAN clients); behind a local reverse proxy (itself inside
|
||
the allowed network), `X-Forwarded-For` designates the real client;
|
||
- the worker is unreachable AND no local pipeline exists.
|
||
|
||
A request submitted while a run is already in progress is placed in a
|
||
**queue** on the worker side (an in-progress job is never interrupted);
|
||
progress is displayed tile by tile (orange/blue/red frames on the map) and the
|
||
**Arrêter** ("Stop") button sends a SIGTERM to the pipeline. At the end of the
|
||
run, the map refreshes and pyramid maintenance resumes automatically.
|
||
|
||
### 4. Centering the map on the GPS position (phone)
|
||
|
||
Browsers only expose the Geolocation API in a **secure context** (HTTPS):
|
||
serve the map behind Traefik (TLS) as above, or, as a last resort, directly
|
||
over HTTPS (the standalone entry point `python -m lidar_pipeline.mapserve`
|
||
honors `LIDAR_SSL_CERTFILE` / `LIDAR_SSL_KEYFILE`).
|
||
|
||
## Updating
|
||
|
||
### Processing machine
|
||
|
||
```bash
|
||
cd <checkout> && git pull && docker compose -f docker-compose.worker.yml up -d --build
|
||
```
|
||
|
||
### Pi
|
||
|
||
```bash
|
||
ssh <pi-host> "cd <checkout> && git pull && docker compose -f docker-compose.maps.yml -f docker-compose.maps.override.yml up -d --build"
|
||
```
|
||
|
||
The interface code is baked into the image (`Dockerfile.maps`): **ANY**
|
||
interface change requires a rebuild (`--build`) — without it, the old code
|
||
keeps running.
|