banyaro/docs/OFFLINE_MAPS_PLAN.md
rene 43b1e8026f Docs: Offline-Karten-Plan (GL/Vektor) — budget-getrieben + adaptives Funkloch-Lernen
Festgehalten als Feature-Plan: Region-Extract per pmtiles (budget-getrieben statt
Radius, ~16 MB Default, gemessen 15 MB für 10km München / ~18-22km Land), Client
IndexedDB-Blob + MapLibre lokal/remote, adaptives Lernen (rollendes Vorausladen
beim Aufzeichnen + Funkloch-Gedächtnis aus echten Fetch-Fehlern, alles lokal),
manuelles Vorab-Laden, Budget+LRU. Umsetzung nach Produktions-Rollout.
2026-06-05 17:16:16 +02:00

72 lines
4.9 KiB
Markdown
Raw 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.

# Offline-Karten (GL/Vektor) — Feature-Plan
**Status:** geplant (Umsetzung nach Tile-Build/Produktions-Rollout der GL-Karten).
**Stand:** 2026-06-05. Autor: René + Claude (Design).
## Ziel
GL-Vektorkarten offline-tauglich machen — Kernszenario **Gassi/Wandern im Funkloch**.
Selbst-zielend (cacht wo nötig, nicht überall), speichersparsam, ohne Nutzeraufwand.
## Problem (warum GL aktuell NICHT offline geht)
- PMTiles lädt per **HTTP-Range (206 Partial Content)**. Die **Cache-API kann 206 nicht speichern**
(`cache.put()` wirft) → Basemap-Kacheln landen nie im Offline-Cache.
- SW hat **keine Regel für `/tiles`** (nur für `tile.openstreetmap.org` = altes Raster).
- `offline-indicator.js` prefetcht weiterhin **OSM-Raster** (a.tile.openstreetmap.org), das die GL-Karte
gar nicht nutzt → doppelter Regress: Raster gecacht das niemand zeigt, GL-Karte offline trotzdem leer.
- Folge offline heute: App + Daten da, aber GL-Karte = Routenlinie/Marker auf **leerem Hintergrund**.
## Gemessene Speicher-Fakten (an echter dach.pmtiles, maxzoom=14 + Overzoom bis ~16)
| Gebiet | Fläche | Tiles | Größe |
|---|---|---|---|
| München (48,1/11,5), dicht | 20×20 km (10 km Radius) | 252 | **15 MB** |
| Bayerischer Wald, ländlich | 20×20 km | 285 | **4,4 MB** |
| Bayerischer Wald, ländlich | 50×50 km (~25 km Radius) | 1.595 | **20 MB** |
→ Vektor ist ~10× sparsamer als Raster (Raster 10 km ≈ 100300 MB). Stadt-Tiles ~3,4× dicker als Land.
**Budget ≈ 16 MB** deckt in der Stadt ~10 km, auf dem **Land ~1822 km** Radius ab (mehr Reichweite
genau dort wo die Funklöcher sind). Glyphs (~12 MB) + Style (winzig) obendrauf → ~17 MB pro Gegend.
→ Messmethode (reproduzierbar): `docker run --rm -v /tmp/pmt:/out protomaps/go-pmtiles:latest extract
https://staging.banyaro.app/tiles/dach.pmtiles /out/x.pmtiles --bbox=W,S,E,N` → Dateigröße ablesen.
## Architektur
### Region-Extract (budget-getrieben, NICHT fester Radius)
- PMTiles-**Directory enthält pro Tile die Byte-Länge** → Server kann die Größe einer Region
**aufsummieren OHNE die Tiles zu laden**.
- Endpoint `GET /tiles/region?lat=&lon=&budget=16` (MB): wächst die bbox um die Position, bis die
summierte Tile-Länge ≈ Budget erreicht (Stadt → kleiner Radius, Land → großer Radius), extrahiert dann
genau diese Region als `region.pmtiles` (**ein 200er**, ~16 MB). `pmtiles extract` (go-pmtiles) oder
python-pmtiles im Container.
- Client lädt die Datei einmal → **IndexedDB** (Blob; 200er, anders als die 206-Ranges cachebar).
- MapLibre liest **offline** aus dem lokalen Blob via `pmtiles://` (pmtiles.js kann aus ArrayBuffer lesen);
**online** weiter remote `dach.pmtiles` (immer aktuell, ganz DACH+Anrainer). Source je nach Verbindung wählen.
- Glyphs (`/fonts/*.pbf`, Open Sans Regular+Semibold) mit cachen (200er, cachebar).
### Adaptive Strategie (der eigentliche Clou — lernt von selbst)
1. **Rollendes Vorausladen beim Aufzeichnen:** Solange GPS aktiv UND Empfang da, fortlaufend Tiles um die
**aktuelle Position** cachen. Deckt den echten Weg + die Anfahrt automatisch ab — auch beim ersten Mal,
bevor man ins Funkloch läuft.
2. **Funkloch-Gedächtnis:** Wo echte Requests **scheitern** (Timeout/Fehler während aktivem GPS — NICHT
`navigator.onLine`, das lügt bei Captive-Portal/Schwachempfang), den Bereich als „Offline nötig"
markieren → priorisiert behalten, beim nächsten Online-Durchgang großzügiger nachladen.
Caveat: im Funkloch selbst kann nicht geladen werden → greift ab dem 2. Besuch (Gassi = repetitiv → ok).
3. **Manuelles Vorab-Laden:** Button „Diese Gegend offline speichern" für geplante neue Touren (Lernen
deckt nur Bekanntes ab). Fortschritt + „X MB gespeichert" + Löschen/Aktualisieren.
### Drumherum
- **Budget-Cap + LRU:** Gesamtspeicher gedeckelt; selten besuchte Funkloch-Caches fallen raus.
- **Privatsphäre:** „Wo verliere ich Netz" = Aufenthaltsorte → **komplett lokal (IndexedDB), nie hochgeladen.**
- **Aktualität:** Offline-Region beim nächsten Online-Sein neu ziehbar (Basemap-Updates / pmtiles-Refresh).
- **Aufräumen:** Den alten OSM-Raster-Prefetch in `offline-indicator.js` ablösen/abschalten (cacht ungenutztes Raster).
## Offene Entscheidungen / Defaults
- Budget-Default **16 MB**, optional Stufe „Groß 40 MB" (Stadt ~16 km / Land ~30+ km) für Wandertage.
- Zoom z014 (Overzoom liefert Straßenebene gratis).
- Detektionssignal = echte Fetch-Timeouts bei aktivem GPS (nicht `navigator.onLine`).
- Speicher = IndexedDB (Blobs); MapLibre-Source-Umschaltung online/offline.
## Abhängigkeiten
- GL-Tiles in **Produktion** (dach.pmtiles + fonts auf Prod-Volume) — Voraussetzung.
- pmtiles-Directory-Byte-Summierung (Server) + pmtiles.js Blob-Source (Client).
- WebGL-Kontext-Disziplin beachten (siehe Skill/Memory: jede GL-Karte beim Schließen `remove()`).
Siehe `docs/TILE_SERVER_HANDOVER.md` (Tile-Pipeline) + Memory `project_tile_server_maintenance`.