Offline-Karten Runde 4: Minimal-Speicher-Modell (Modell Rene)

Funkloch-Gedaechtnis = Quelle der Wahrheit, Kacheln = ableitbarer Cache:
- Ephemeres Vorausladen: Prefetch-Kacheln werden bei Aufzeichnungsende
  geloescht, wenn die Runde kein Funkloch hatte (nur neue Keys)
- Netz-Probe bei Aufzeichnung (~2min, 6s-Timeout): erkennt Funkloecher auch
  in bereits gespeicherten Gebieten (dort kein Remote-Miss als Signal)
- clear() behaelt Zonen (filled=false) -> naechster Online-Start laedt
  Funkloch-Gebiete automatisch neu, auch nach 'Alles loeschen'
- Start-Check mit Position: nur Zonen im 50-km-Umkreis, naechste zuerst,
  Coverage-Verify (faengt Eviction ab); Pfote Segment 5 = alle Zonen gefuellt
- Coverage-Layer zweifarbig: Funkloch orange, manuell blau + Modal-Legende
- Stub-Tests Runde 4 (Prune/Probe/clear/Naehe/Verify/Faerbung) bestanden
Bump v1230
This commit is contained in:
rene 2026-06-06 12:46:12 +02:00
parent 763108fa7c
commit 53bc27faa3
10 changed files with 221 additions and 74 deletions

View file

@ -25,9 +25,11 @@ window.OfflineIndicator = (() => {
function _offlineTilesMode() {
try { return !!(window.BY && BY.offlineTiles()); } catch (e) { return false; }
}
// Ist eine Offline-Region (Vektorkacheln) in IndexedDB gespeichert? (ohne MapOffline zu laden)
// WICHTIG: dasselbe Schema/Version wie map-offline.js anlegen — sonst legt ein versionsloses open()
// die DB leer an und MapOffline kann seine Stores nicht mehr erstellen.
// Offline-Ready (Pfote Segment 5) — ohne MapOffline/GL-Stack zu laden.
// Semantik (Modell René 2026-06-08): Gibt es bekannte FUNKLOCH-Zonen, zählt deren
// Füllstand (alle gefüllt = grün); ohne bekannte Zonen wie bisher: irgendein Gebiet da.
// WICHTIG: dasselbe Schema/Version wie map-offline.js anlegen — sonst legt ein versionsloses
// open() die DB leer an und MapOffline kann seine Stores nicht mehr erstellen.
function _offlineRegionStored() {
return new Promise(res => {
try {
@ -39,10 +41,18 @@ window.OfflineIndicator = (() => {
};
r.onsuccess = () => {
const db = r.result;
if (!db.objectStoreNames.contains('tiles')) { db.close(); return res(false); }
const cnt = db.transaction('tiles', 'readonly').objectStore('tiles').count();
cnt.onsuccess = () => { res(cnt.result > 0); db.close(); };
cnt.onerror = () => { res(false); db.close(); };
if (!db.objectStoreNames.contains('tiles') || !db.objectStoreNames.contains('meta')) {
db.close(); return res(false);
}
const mz = db.transaction('meta', 'readonly').objectStore('meta').get('deadzones');
mz.onsuccess = () => {
const zones = mz.result || [];
if (zones.length) { res(zones.every(z => z.filled)); db.close(); return; }
const cnt = db.transaction('tiles', 'readonly').objectStore('tiles').count();
cnt.onsuccess = () => { res(cnt.result > 0); db.close(); };
cnt.onerror = () => { res(false); db.close(); };
};
mz.onerror = () => { res(false); db.close(); };
};
r.onerror = () => res(false);
} catch (e) { res(false); }
@ -224,9 +234,10 @@ window.OfflineIndicator = (() => {
} catch (e) { console.warn('Offline-Region-Download fehlgeschlagen:', e); }
}
// Gibt es offene (ungefüllte) Funkloch-Zonen? — direkt aus IndexedDB, OHNE den
// GL-Stack zu laden (gleiches Schema/Version wie map-offline.js, s. Warnung oben).
function _openDeadZonesStored() {
// Gibt es überhaupt Funkloch-Zonen? — direkt aus IndexedDB, OHNE den GL-Stack zu
// laden. Auch GEFÜLLTE zählen: der Start-Check verifiziert deren Kacheln (nach
// „Alles löschen"/Eviction werden sie automatisch neu geladen, Modell René 2026-06-08).
function _anyDeadZonesStored() {
return new Promise(res => {
try {
const r = indexedDB.open('by-offline-tiles', 1);
@ -239,7 +250,7 @@ window.OfflineIndicator = (() => {
const db = r.result;
if (!db.objectStoreNames.contains('meta')) { db.close(); return res(false); }
const rq = db.transaction('meta', 'readonly').objectStore('meta').get('deadzones');
rq.onsuccess = () => { res((rq.result || []).some(z => !z.filled)); db.close(); };
rq.onsuccess = () => { res((rq.result || []).length > 0); db.close(); };
rq.onerror = () => { res(false); db.close(); };
};
r.onerror = () => res(false);
@ -247,8 +258,29 @@ window.OfflineIndicator = (() => {
});
}
// Funkloch-Zonen automatisch füllen, sobald Netz da ist — das Gerät lernt selbst,
// wo Offline-Karten nötig sind (dort wo Netz ist, braucht es keine).
// Letzte bekannte Position: GPS nur wenn Permission schon erteilt (kein Popup),
// sonst localStorage-Stand (gesetzt von wetter.js u.a.).
async function _lastKnownPos() {
try {
if (navigator.permissions && navigator.geolocation) {
const perm = await navigator.permissions.query({ name: 'geolocation' });
if (perm.state === 'granted') {
const pos = await new Promise(res =>
navigator.geolocation.getCurrentPosition(p => res(p), () => res(null), { timeout: 5000 }));
if (pos) return { lat: pos.coords.latitude, lon: pos.coords.longitude };
}
}
} catch {}
try {
const raw = localStorage.getItem(LS_LAST_POS);
if (raw) { const p = JSON.parse(raw); if (p?.lat != null) return { lat: p.lat, lon: p.lon }; }
} catch {}
return null;
}
// Funkloch-Zonen automatisch füllen/verifizieren, sobald Netz da ist — das Gerät lernt
// selbst, wo Offline-Karten nötig sind. Mit Position werden nur Zonen im Umkreis
// (50 km) geladen → Speicher bleibt minimal, ferne Zonen kommen, wenn man dort ist.
let _autoFillTimer = null;
function _scheduleAutoFill(delayMs) {
if (!_offlineTilesMode()) return;
@ -256,9 +288,10 @@ window.OfflineIndicator = (() => {
_autoFillTimer = setTimeout(async () => {
if (!navigator.onLine) return;
try {
if (!(await _openDeadZonesStored())) return; // nichts zu tun → GL-Stack nicht laden
if (!(await _anyDeadZonesStored())) return; // nichts zu tun → GL-Stack nicht laden
const pos = await _lastKnownPos();
await UI.loadMapLibreUI();
const n = await window.MapOffline?.autoFillDeadZones?.();
const n = await window.MapOffline?.autoFillDeadZones?.(pos ? { lat: pos.lat, lon: pos.lon } : {});
if (n) {
UI.toast?.info(`${n} Funkloch-${n === 1 ? 'Gebiet' : 'Gebiete'} automatisch offline gespeichert.`);
refresh();