weather.py get_weather_for_location: precip = max(h_precip[now_h:now_h+3])
(Fallback Tages-Max). map.js Pill zeigt '💧 X% (3h)'. Gilt auch fürs
Welten-Banner (geteiltes precip_prob-Feld) — behebt das 'ganzer Tag'-Problem
überall. Gespeicherte Tagebuch-Snapshots unberührt (historisch).
11 KiB
Offline-Karten (GL/Vektor) — Feature-Plan
Status: KERN UMGESETZT + headless verifiziert (2026-06-05, v1213), flag-gated by_offline_tiles (Default AUS) bis Gerätetest.
Stand: 2026-06-05. Autor: René + Claude (Design).
Umsetzungsstand (2026-06-05)
✅ Fertig + headless bewiesen:
map-offline.js(window.MapOffline): Region-Download (downloadAround(lat,lon,radiusKm)) → Vektorkacheln z0–14 viapmtiles.getZxy(liefert bereits dekomprimierte MVT) + Glyphs in IndexedDB (by-offline-tiles).byt://-MapLibre-Protokoll (IndexedDB-first, remote-Fallback). ~15 MB / 5 km (dekomprimiert).map-gl-style.jsbuild({offline}):byt-Source stattpmtiles://. Flagby_offline_tiles(Default AUS).- ui.js/map.js laden map-offline + registrieren
byt.UI.loadMapLibreUIexportiert. - Welten-FAB Segment 5: prüft im GL-Modus gespeicherte Region (nicht mehr OSM-Raster); „Fehlende nachladen"
stößt
MapOffline.downloadAround(GPS, 5km)an. - Beweis: Download 97 Tiles (5 km München) → Netz AUS → 1903 Features gerendert, nicht geladene Gegend (Paris) leer; Glyphs nötig (sonst lässt MapLibre offline die ganze Kachel fallen).
🔲 Offen (Follow-ups):
- Gerätetest (iOS-PWA offline/IndexedDB) → dann Flag-Default auf Staging-AN (analog
by_map_gl). - Download-Button auf der Karte (
map-offline-btn) im GL-Modus aufdownloadAround(Karten-Center)umbiegen (bisher OSM-Raster-Prefetch). - Adaptives Lernen (rollendes Vorausladen beim Aufzeichnen + Funkloch-Gedächtnis).
- Bereichsauswahl / Routen-Korridor (inkl. „Route offline speichern" aus routes.js
_openDetail). - Glyph-Persistenz über App-Updates (aktuell SW-Cache, wird bei Update gepurged) → in IndexedDB ablegen + via
byt://f/servieren. - Alten OSM-Raster-Prefetch (
offline-indicator.js _prefetchTiles) entfernen, wenn Flag dauerhaft AN.
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ürtile.openstreetmap.org= altes Raster). offline-indicator.jsprefetcht 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 ≈ 40–120 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 ~8–10 km Radius ab (mehr Reichweite genau dort wo die Funklöcher sind). Glyphs (~1–2 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 alsregion.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 remotedach.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)
- 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.
- 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). - Manuelles Vorab-Laden („Offline-Inhalte laden"-Button) — zwei Modi:
- Aktuelle Gegend (Default, Gassi): budget-getrieben um die Position (~7 MB), ein Tipp.
- Bereich auswählen (mehrtägige Wanderung — Auto-5km reicht da nicht): Nutzer wählt ein größeres
Gebiet, das ganz heruntergeladen wird. Auswahl-Optionen:
- Karten-Ausschnitt: aktuellen Karten-Viewport (durch Zoomen/Verschieben gewählter bbox) als Download-Gebiet nehmen — simpel, kein Zeichnen nötig.
- Rechteck ziehen auf der Karte (präziser).
- Routen-Korridor: entlang einer Route ± Puffer (ideal für Touren, die einer Strecke folgen —
viel sparsamer als eine große bbox).
Verbindung zum Routen-Feature (WICHTIG, sonst hängt der Modus in der Luft): Einstieg primär
AUS der Route — im Routen-Detail (
routes.js_openDetail, Aktionsleiste neben GPX/Teilen/Navi) und in der Navigations-Ansicht ein Button „Route offline speichern". Nutzt den bereits geladenenroute.gps_track→ Korridor = alle Tiles im Puffer (z.B. 1–2 km) um den Track (nicht die ganze bbox). Zusätzlich im „Offline-Inhalte laden"-Dialog ein Modus „Aus meinen Routen wählen" (Liste der gespeicherten Routen → Korridor laden). Größen-Vorschau wie unten, vor dem Download.
- Größen-Vorschau VOR dem Download: die Tile-Byte-Längen aus der PMTiles-Directory aufsummieren (kein Tile-Download nötig) → „~45 MB" anzeigen, Nutzer bestätigt. Schützt vor versehentlichem Riesen-Download.
- Fortschritt + „X MB gespeichert" + Liste gespeicherter Gebiete (umbenennen/löschen/aktualisieren).
- Im Bereichs-Modus gilt das ~7-MB-Budget NICHT (Nutzer entscheidet bewusst), aber eine sinnvolle Obergrenze + der globale Speicher-Cap greifen.
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.jsablösen/abschalten (cacht ungenutztes Raster). - Pfoten-Offline-Indikator (Welten-FAB) anpassen:
offline-indicator.jsfüllt 5 Pfoten-Segmente; Segment 5 „Karten-Kacheln" prüft aktuellCACHE_TILESauf ≥ 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 eineregion.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 ~8–10 km. Optional Stufe „Groß ~16 MB" (Stadt ~10 km / Land ~18–22 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.
Weitere Karten-To-Dos (nicht offline-spezifisch)
Wetter-Chip: Niederschlag „nächste 3 Std" statt ganzer Tag — ✅ ERLEDIGT (2026-06-05, v1214)
Umgesetzt: weather.py get_weather_for_location → precip = max(h_precip[now_h:now_h+3]) (Fallback Tages-Max);
Pill map.js zeigt 💧 X% (3h). Gilt auch fürs Welten-Banner (geteiltes Feld). Hinweis: forecast_days=1 →
Slice am Tagesende kürzer (späte Nacht weniger Vorausschau); für volle Mitternachts-Abdeckung forecast_days=2.
(Original-Notiz:)
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.pybereits geladen (&hourly=precipitation_probability, Arrayh_precipab 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 bestehendennext_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)).