# Ü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:369–372`). 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 ~2–3 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).