From 43b1e8026f629ac81d4de245ddbdf2fbdbda2037 Mon Sep 17 00:00:00 2001 From: rene Date: Fri, 5 Jun 2026 17:16:16 +0200 Subject: [PATCH] =?UTF-8?q?Docs:=20Offline-Karten-Plan=20(GL/Vektor)=20?= =?UTF-8?q?=E2=80=94=20budget-getrieben=20+=20adaptives=20Funkloch-Lernen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- docs/OFFLINE_MAPS_PLAN.md | 72 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docs/OFFLINE_MAPS_PLAN.md diff --git a/docs/OFFLINE_MAPS_PLAN.md b/docs/OFFLINE_MAPS_PLAN.md new file mode 100644 index 0000000..d0acd84 --- /dev/null +++ b/docs/OFFLINE_MAPS_PLAN.md @@ -0,0 +1,72 @@ +# 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 ≈ 100–300 MB). Stadt-Tiles ~3,4× dicker als Land. +→ **Budget ≈ 16 MB** deckt in der Stadt ~10 km, auf dem **Land ~18–22 km** Radius ab (mehr Reichweite + genau dort wo die Funklöcher sind). Glyphs (~1–2 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 z0–14 (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`.