banyaro/docs/OFFLINE_MAPS_PLAN.md
rene 27e7590eed Offline-Plan: Pfoten-Indikator (Welten-FAB) Segment 5 als Kopplung festgehalten
Segment 5 'Karten-Kacheln' misst aktuell OSM-Raster-Cache (offline-indicator.js,
CACHE_TILES) — GL nutzt das nicht → falsches 'Karte offline bereit'. Bei GL-Region-
Download umdefinieren (region.pmtiles + Glyphs prüfen, offline-fill-btn stößt Region-
Download an). An Offline-Strategie gekoppelt.
2026-06-05 17:55:33 +02:00

104 lines
7.1 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)
**Referenz-Radius = 5 km** (René, 2026-06-05: „5 km genügen"). Messungen:
| Gebiet | Fläche | Tiles | Größe |
|---|---|---|---|
| **München (48,1/11,5), dicht — 5 km** | 10×10 km | 82 | **6,4 MB** |
| **Bayerischer Wald, ländlich — 5 km** | 10×10 km | 99 | **2,6 MB** |
| (Kontext) München 10 km | 20×20 km | 252 | 15 MB |
| (Kontext) Bayerischer Wald 10 km | 20×20 km | 285 | 4,4 MB |
| (Kontext) Bayerischer Wald ~25 km | 50×50 km | 1.595 | 20 MB |
→ Vektor ist ~10× sparsamer als Raster (Raster 5 km ≈ 40120 MB). Stadt-Tiles ~2,5× dicker als Land.
→ 5 km ist NICHT 1/4 von 10 km (6,4 vs 15 MB) — die unteren Zoomstufen (Übersicht) sind immer dabei,
unabhängig von der bbox-Größe.
**Budget ≈ 7 MB** (5 km dichte Stadt + Glyphs). Budget-getrieben deckt das in der Stadt ~5 km, auf dem
**Land ~810 km** Radius ab (mehr Reichweite genau dort wo die Funklöcher sind). Glyphs (~12 MB) +
Style (winzig) → ~8 MB pro Gegend. Sehr sparsam → viele Gegenden problemlos (10 ≈ 80 MB).
→ 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=7` (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**, ~7 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).
- **Pfoten-Offline-Indikator (Welten-FAB) anpassen:** `offline-indicator.js` füllt 5 Pfoten-Segmente;
**Segment 5 „Karten-Kacheln"** prüft aktuell `CACHE_TILES` auf ≥ N **OSM-Raster**-Tiles (TILE_PREFETCH
z14/z13). Die GL-Karte nutzt dieses Raster NICHT → grünes Segment = falsches „Karte offline bereit".
→ Segment 5 umdefinieren: prüfen ob für die aktuelle Gegend eine **`region.pmtiles` + Glyphs** lokal
vorliegen (statt Raster-Tile-Count). `offline-fill-btn` („Offline-Inhalte laden") soll dann den
Region-Download anstoßen statt den Raster-Prefetch. (Bei Variante 1 „Raster-Fallback" bliebe Segment 5
wie es ist — Entscheidung hängt an der Offline-Strategie oben.)
## Offene Entscheidungen / Defaults
- Budget-Default **~7 MB** (Referenz 5 km Stadt; René 2026-06-05). Stadt ~5 km / Land ~810 km.
Optional Stufe „Groß ~16 MB" (Stadt ~10 km / Land ~1822 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`.
---
# Weitere Karten-To-Dos (nicht offline-spezifisch)
## Wetter-Chip: Niederschlag „nächste 3 Std" statt ganzer Tag
**Ist:** Der Karten-Chip unten zeigt die Regenwahrscheinlichkeit als **Tages-Maximum**
(`backend/weather.py:98``precip = daily['precipitation_probability_max'][0]`; angezeigt in
`pages/map.js:~2598` als `💧 {w.precip_prob}%`). Über den ganzen Tag gemittelt/maximiert = wenig
aussagekräftig für „soll ich JETZT raus".
**Soll:** Den **höchsten Wert der nächsten 3 Stunden** (ab aktueller Stunde) zeigen.
- Die stündlichen Daten werden in `weather.py` bereits geladen (`&hourly=precipitation_probability`,
Array `h_precip` ab Zeile ~116) — kein neuer API-Call nötig.
- Ändern: `precip_prob = max(h_precip[now_idx : now_idx+3])` (aktuellen Stundenindex bestimmen wie bei
der bestehenden `next_rain_time`-Logik). `next_rain_time`/Warnungen können bleiben.
- Optional Chip-Text klarstellen, dass sich der Wert auf die nächsten 3 h bezieht (z.B. `💧 {x}% (3h)`).