Offline-Karten Runde 5: Geraetetest-Feedback (Indikator, Flugmodus-Signal, Ent-Funklochen, Warnungs-Aktualitaet, Routen-Start-Check)

- Indikator links unter die Zoom-Regler (rechts verdeckte Legenden-Chips)
- Flugmodus bei offener App -> Position raw als Funkloch-Zone (offline-Event)
- Ent-Funklochen: Zonen-Liste im Offline-Modal mit X (removeDeadZone)
- Warnungs-Aktualitaet: _mergeStore Bbox-Replace (aufgehobene Giftkoeder/
  gefundene Hunde verschwinden; Fetch-Kreis deckt Bbox via sqrt2 ab;
  fresh=null merged nie) + 24h-Refresh im 50km-Umkreis beim Start
- Routen offline nutzbar halten: ensureRouteCorridors beim Start-Check
  (Stichproben-Verify, Re-Download aus preview_track, Region-Dedupe)
- Stub-Tests ins Repo: tests/js/ (r1/r3/r4/r5, alle gruen)
Bump v1231
This commit is contained in:
rene 2026-06-06 13:00:20 +02:00
parent 53bc27faa3
commit 6c313aca05
15 changed files with 606 additions and 52 deletions

View file

@ -278,9 +278,44 @@ window.OfflineIndicator = (() => {
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.
// Flugmodus/Netzverlust bei OFFENER App = klares Funkloch-Signal (René 2026-06-08):
// aktuelle Position direkt als Zone merken — raw in IndexedDB, denn der GL-Stack ist
// offline evtl. nicht ladbar. Dedupe 2 km wie MapOffline.markDeadZone; Cap 50.
function _markDeadZoneRaw(lat, lon) {
try {
const r = indexedDB.open('by-offline-tiles', 1);
r.onupgradeneeded = () => {
const d = r.result;
if (!d.objectStoreNames.contains('tiles')) d.createObjectStore('tiles');
if (!d.objectStoreNames.contains('meta')) d.createObjectStore('meta');
};
r.onsuccess = () => {
const db = r.result;
if (!db.objectStoreNames.contains('meta')) { db.close(); return; }
const store = db.transaction('meta', 'readwrite').objectStore('meta');
const rq = store.get('deadzones');
rq.onsuccess = () => {
let zones = rq.result || [];
const near = zones.some(z => {
const dLat = (z.lat - lat) * 111, dLon = (z.lon - lon) * 111 * Math.cos(lat * Math.PI / 180);
return Math.sqrt(dLat * dLat + dLon * dLon) < 2;
});
if (!near) {
zones.push({ lat, lon, ts: Date.now(), filled: false });
if (zones.length > 50) zones = zones.slice(-50);
store.put(zones, 'deadzones');
}
db.close();
};
rq.onerror = () => db.close();
};
} catch (e) {}
}
// Start-Check (auch bei Netz-Rückkehr): Funkloch-Zonen UND Routen-Korridore in
// Positionsnähe (50 km) füllen/verifizieren — das Gerät hält selbst aktuell, was
// offline nötig ist (René 2026-06-08: gespeicherte Routen müssen offline nutzbar
// bleiben, auch nach „Alles löschen"/Eviction). Ferne Gebiete kommen, wenn man dort ist.
let _autoFillTimer = null;
function _scheduleAutoFill(delayMs) {
if (!_offlineTilesMode()) return;
@ -288,12 +323,21 @@ window.OfflineIndicator = (() => {
_autoFillTimer = setTimeout(async () => {
if (!navigator.onLine) return;
try {
if (!(await _anyDeadZonesStored())) return; // nichts zu tun → GL-Stack nicht laden
const hasZones = await _anyDeadZonesStored();
let routes = [];
try { routes = (await API.routes.list()) || []; } catch {}
routes = routes.filter(r => (r.preview_track || []).length >= 2);
if (!hasZones && !routes.length) return; // nichts zu tun → GL-Stack nicht laden
const pos = await _lastKnownPos();
const o = pos ? { lat: pos.lat, lon: pos.lon } : {};
await UI.loadMapLibreUI();
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.`);
const n = await window.MapOffline?.autoFillDeadZones?.(o) || 0;
const k = routes.length ? (await window.MapOffline?.ensureRouteCorridors?.(routes, o) || 0) : 0;
if (n || k) {
const parts = [];
if (n) parts.push(`${n} Funkloch-${n === 1 ? 'Gebiet' : 'Gebiete'}`);
if (k) parts.push(`${k} Routen-${k === 1 ? 'Korridor' : 'Korridore'}`);
UI.toast?.info(`${parts.join(' + ')} automatisch offline gespeichert.`);
refresh();
}
} catch (e) {}
@ -471,6 +515,13 @@ window.OfflineIndicator = (() => {
// Funkloch-Zonen nachladen: verzögert beim Start + sobald die Verbindung zurückkommt.
_scheduleAutoFill(30_000);
window.addEventListener('online', () => _scheduleAutoFill(8_000));
// Flugmodus/Netzverlust bei offener App → Standort als Funkloch-Zone merken
// (wird beim nächsten Online-Sein automatisch geladen und künftig aktuell gehalten).
window.addEventListener('offline', async () => {
if (!_offlineTilesMode()) return;
const pos = await _lastKnownPos();
if (pos) _markDeadZoneRaw(pos.lat, pos.lon);
});
_checkStorageQuota(); // beim Init prüfen
setInterval(() => { _prefetchData(); refresh(); _checkStorageQuota(); }, 60_000);
}