# 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://: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//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://: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 **Generation** tab (**+ Area**: draw a rectangle → IGN download + run on the worker; **⤒ Complete**: already-downloaded but incomplete tiles) and the **↻ Generate this tile** button (Tile tab, after clicking a tile) 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 **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 && git pull && docker compose -f docker-compose.worker.yml up -d --build ``` ### Pi ```bash ssh "cd && 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.