banyaro/docs/TILE_SERVER_HANDOVER.md

189 lines
9.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Übergabe: Selbst-gehosteter Tile-Server (Karte + Touren auf eigenen Vektortiles)
> **An den Kollegen, der das hier im `banyaro`-Repo weiterbaut.**
> Geschrieben aus dem `banyaro-ios`-Kontext heraus, nachdem der App-Store-Resubmit
> (Build 1.0(5)) raus war. Dies ist der **Vorbereitungs- + Staging-Test-Plan**. Bitte
> erst „Kontext & Entscheidung" lesen, dann den Staging-Spike ausführen, dann die
> offenen Punkte mit René klären, bevor irgendwas nach Produktion geht.
---
## 1. Worum geht's
Wir wollen **Karten selbst hosten** (analog Pocket Earth), statt vom öffentlichen
OSM-Raster-Server zu ziehen. Damit bauen wir **Karte UND Touren** auf eigenen
Vektortiles auf — Web (PWA) und nativ (iOS).
**Warum überhaupt:**
- **Lizenz/Policy:** Die PWA nutzt aktuell `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`
(`backend/static/js/ui.js``Map.OSM_URL`, `offline-indicator.js`). Die
[OSMF-Tile-Usage-Policy](https://operations.osmfoundation.org/policies/tiles/)
verbietet heavy/kommerzielle Nutzung — auf Dauer kein tragfähiges Fundament.
- **Offline:** iOS soll Regionen offline vorhalten (Pocket-Earth-Modell). Mit eigenen
Tiles ist das sauber machbar, mit dem öffentlichen Raster-Server nicht.
- **Kontrolle & Konsistenz:** Eine Tile-/Style-Quelle für Web + iOS, kein
Drittanbieter-Limit, Retina/Vektor-Styling, eigenes Karten-Design möglich.
- **Companion-Prinzip:** Substanz lebt auf banyaro.app — die Tile-Infrastruktur gehört
genau hierher (DS/Docker/NPM/Staging sind alle in diesem Repo). Die iOS-App ist nur
das native Fenster.
**Bewusst NICHT Teil des laufenden App-Store-Resubmits** — das ist eigener Scope nach
der Freigabe (andere Risikoklasse: Infra/Ops, Heim-Uplink-Verfügbarkeit; kein offener
Apple-Punkt). Siehe iOS-Memory `project_app_review_build3` / `project_build4_karte`.
---
## 2. Ist-Zustand (im Repo verifiziert)
- **Karte Web:** Leaflet + `leaflet.markercluster`, Raster-Basemap vom öffentlichen
OSM-Server. Helper in `backend/static/js/ui.js` (`Map.OSM_URL`, `L.tileLayer(...)`),
weitere Vorkommen in `ui.js:1006` und `offline-indicator.js:193`.
- **Karte iOS:** aktuell Apple **MapKit** (im `banyaro-ios`-Repo). Soll später auf
MapLibre Native + Offline-Regionen umgestellt werden — **separater iOS-Workstream**,
dieser Tile-Server ist die Voraussetzung dafür.
- **Deployment:** ein Docker-Service `banyaro` (FastAPI :8000 → DS :3010) hinter
**NPM (Nginx Proxy Manager)**. Static-Files via FastAPI-`StaticFiles`-Mounts
(`/css`, `/js`, `/icons`, `/img` in `backend/main.py:369372`). Volume `./data:/data`.
- **Staging:** eigener Container `banyaro-staging`, `docker-compose.staging.yml`,
Pfad `/volume1/docker/banyaro-staging`, URL **https://staging.banyaro.app**,
Deploy via `make staging` (pusht `develop`).
- **DS-Zugang:** Host `ds` (10.47.11.10, SSH-Port 4711), `sudo docker`.
- **Tile-Pipeline:** existiert noch **nicht** (keine pbf/mbtiles/pmtiles im Repo).
Es gibt konzeptionell validierte OSM-POI-Pipeline-Notizen (iOS-Memory
`project_build4_pois`) — separat von den Basemap-Tiles, aber gleiche pbf-Quelle.
---
## 3. Empfohlene Architektur: **planetiler → PMTiles → MapLibre**
Für einen **Heim-Server (DiskStation)** mit **Offline-Anspruch** ist das die klar beste
Wahl — leichter als ein klassischer Raster-Renderer und ohne laufenden Render-Prozess:
```
OSM-Extract (.osm.pbf, Geofabrik)
│ planetiler (OpenMapTiles-Schema, einmalig + bei Updates)
region.pmtiles ← EIN Single-File-Tile-Archiv (Vektor)
│ per HTTP Range-Requests ausgeliefert (nginx/FastAPI StaticFiles → 206)
MapLibre ── Web: MapLibre GL JS + pmtiles-Protokoll (ersetzt Leaflet-Raster)
└─ iOS: MapLibre Native, Offline = .pmtiles lokal auf dem Gerät
+ Style-JSON + Glyphs (Fonts) + Sprite (statisch gehostet)
+ Touren = GeoJSON-Linien-Layer obendrauf (Basemap-unabhängig)
```
**Warum PMTiles (und nicht ein Raster-Tile-Server):**
- **Kein Server-Prozess, kein PostGIS, kein renderd/Mapnik.** Eine Datei, ausgeliefert
per Range-Request — die DS muss zur Laufzeit **nichts rendern**. Ideal für schwache
Hardware + Heim-Uplink.
- **Offline trivial:** dieselbe `.pmtiles` lokal auf dem iPhone → MapLibre liest direkt.
Das ist das Pocket-Earth-Modell.
- **Vektor:** Retina-scharf, eigenes Styling, Labels drehbar, klein.
**Verworfen — `openstreetmap-tile-server` / renderd+Mapnik (Raster):** schwerer
PostGIS-Import, CPU-Rendering pro Request, große Storage, schlechte Offline-Story,
kein Vektor-Styling. Für unseren Fall strikt unterlegen.
**Stack-Komponenten:**
| Teil | Tool | Hinweis |
|------|------|---------|
| Tile-Generierung | [planetiler](https://github.com/onthegomap/planetiler) (Docker) | OpenMapTiles-Schema, Output direkt `.pmtiles` |
| Auslieferung | nginx/NPM **oder** FastAPI `StaticFiles` | beide können Range-Requests (206); für Spike reicht StaticFiles |
| Web-Client | `maplibre-gl` + `pmtiles` (JS) | ersetzt Leaflet schrittweise (Feature-Flag) |
| iOS-Client | MapLibre Native (eigener Workstream) | Offline-Region = lokale `.pmtiles` |
| Style | OpenMapTiles-kompatibel (z. B. Positron/OSM-Bright) + Glyphs + Sprite | selbst hosten für volle Unabhängigkeit |
---
## 4. Staging-Spike (konkret, zum Loslegen)
Ziel: eine kleine Region als `.pmtiles` erzeugen, auf **staging** ausliefern, mit
MapLibre rendern — und Größe/Zeit/Performance/Range-Requests messen. **Erst klein**
(Bayern), nicht gleich DACH.
### 4.1 Extract + Tiles erzeugen (lokal/Build-Maschine, NICHT auf der DS)
```bash
mkdir -p tiles/build && cd tiles/build
# planetiler lädt den Geofabrik-Extract selbst und schreibt direkt PMTiles:
docker run --rm -v "$PWD:/data" ghcr.io/onthegomap/planetiler:latest \
--download --area=bayern --output=/data/bayern.pmtiles
# Ergebnis: bayern.pmtiles (grobe Schätzung: paar hundert MB, wenige Minuten)
```
> DACH gibt es bei Geofabrik nicht als ein Extract → später `germany` + `austria` +
> `switzerland` per `osmium merge` zusammenführen, dann planetiler darauf. Für den
> Spike reicht `bayern`.
### 4.2 Auf Staging ausliefern (einfachster Weg: FastAPI StaticFiles vom data-Volume)
Die große Datei NICHT ins Image bauen — ins `./data`-Volume legen und mounten.
```python
# backend/main.py — nahe der bestehenden StaticFiles-Mounts (Z. 369ff)
import os
_TILES_DIR = os.getenv("TILES_DIR", "/data/tiles")
if os.path.isdir(_TILES_DIR):
app.mount("/tiles", StaticFiles(directory=_TILES_DIR), name="tiles")
```
- `bayern.pmtiles` nach `/volume1/docker/banyaro-staging/data/tiles/` kopieren
(scp zur DS), dann `make staging`.
- Starlette `FileResponse` beherrscht Range-Requests → MapLibre/pmtiles bekommt 206.
- **Verifizieren:** `curl -I -H "Range: bytes=0-1023" https://staging.banyaro.app/tiles/bayern.pmtiles`
muss **HTTP/1.1 206 Partial Content** + `Accept-Ranges: bytes` liefern. Prüfen, dass
NPM die Range-Header nicht verschluckt.
### 4.3 MapLibre-Testseite (Feature-Flag, Leaflet bleibt Fallback)
- `maplibre-gl` + `pmtiles` einbinden, Protokoll registrieren:
```js
const p = new pmtiles.Protocol();
maplibregl.addProtocol('pmtiles', p.tile);
// source: { type:'vector', url:'pmtiles://https://staging.banyaro.app/tiles/bayern.pmtiles' }
```
- Style-JSON (OpenMapTiles-Schema) + Glyphs + Sprite hosten (z. B. unter `/tiles/style/`).
Fertige freie Styles: OSM-Bright / Positron (maputnik-kompatibel). Glyphs z. B. aus
`openmaptiles/fonts`. Für den Spike darf der Style minimal sein.
- Touren-Polyline als GeoJSON-`line`-Layer auf die Map legen (ist basemap-unabhängig —
sobald MapLibre läuft, ist die Tour nur noch ein Layer).
### 4.4 Messen & festhalten
- Dateigröße + Generierungszeit (Bayern, dann hochrechnen auf DACH).
- Render-Performance über den Heim-Uplink (erste Zoomstufen, Labels).
- DS-Storage-Budget (`/volume1/...` frei?).
- Range-Requests durch NPM ok? CORS nötig (falls Tiles auf anderer Subdomain als App)?
---
## 5. Offene Entscheidungen (mit René klären)
1. **Region-Scope:** DACH am Stück vs. Deutschland-only vs. **herunterladbare
Regionen** (für iOS-Offline granular). DACH-PMTiles grob ~23 GB (verifizieren).
2. **Hosting-Pfad:** Pfad (`/tiles` an der App) vs. eigene Subdomain
(`tiles.banyaro.app`) hinter NPM. Subdomain = sauberer cachebar, aber CORS-Setup.
Für Skalierung ggf. nginx direkt statt FastAPI StaticFiles (kein App-CPU).
3. **Style:** welcher Basis-Style (Positron/OSM-Bright/eigenes Hunde-Theme), Glyphs/Sprite
selbst hosten.
4. **Update-Kadenz:** wie oft aus frischem OSM-Extract neu generieren (monatlich?).
planetiler-Rerun + Datei tauschen (atomar). Cron/Make-Target dafür.
5. **iOS-Workstream:** MapLibre Native + Offline-Region-Download ersetzt MapKit —
eigener Build-4-Task im `banyaro-ios`-Repo, **nach** dieser Infra.
6. **Attribution (Pflicht):** „© OpenStreetMap contributors" (ODbL) muss in Web **und**
iOS sichtbar sein. Bei eigenem Style ggf. zusätzliche Datenquellen-Hinweise.
---
## 6. Vorgeschlagene Reihenfolge
1. Staging-Spike (Abschnitt 4) mit **Bayern** — Proof of Concept, Zahlen sammeln.
2. Entscheidungen aus Abschnitt 5 mit René.
3. DACH-PMTiles generieren (osmium merge → planetiler), Update-Make-Target.
4. PWA: MapLibre hinter Feature-Flag produktiv, Leaflet-Pfad rausnehmen wenn stabil.
5. iOS (separat): MapLibre Native + Offline-Regionen, MapKit ablösen.
---
## 7. Querverweise
- iOS-Memory: `project_build4_karte` (Entscheidung OSM/MapLibre/Offline),
`project_build4_pois` (POI-Pipeline aus pbf — gleiche Datenquelle),
`project_osm_contribution` (OSM-Beitragskreislauf, gehört in die PWA),
`project_companion` (Companion-Prinzip), `project_app_review_build3` (warum nicht im Resubmit).
- NPM/IPv6/Reverse-Proxy-Stolpersteine auf der DS: Skill `synology-troubleshooting`.
- Deploy: `make staging` (→ staging.banyaro.app), `make deploy` (→ Produktion, deployt
den **Arbeitsbaum**). `make bump` NUR bei Frontend-Asset-Änderungen (SW-Cache).