tilfluktsrom/pwa/src/cache/map-cache-manager.ts

112 lines
2.8 KiB
TypeScript
Raw Normal View History

/**
* Map tile cache manager.
* Seeds tiles by programmatically panning a Leaflet map across a grid.
* Tiles are cached by the service worker (CacheFirst strategy on OSM URLs).
*
* Ported from MapCacheManager.kt same 3×3 grid × 4 zoom levels approach.
*/
import type L from 'leaflet';
const CACHE_RADIUS_DEGREES = 0.15; // ~15km
const CACHE_ZOOM_LEVELS = [10, 12, 14, 16];
const GRID_SIZE = 3;
const PAN_DELAY_MS = 400; // Slightly longer than Android (300ms) for web
const STORAGE_KEY = 'mapCache';
interface CacheMeta {
lat: number;
lon: number;
radius: number;
complete: boolean;
}
function getCacheMeta(): CacheMeta | null {
try {
const raw = localStorage.getItem(STORAGE_KEY);
return raw ? JSON.parse(raw) : null;
} catch {
return null;
}
}
function saveCacheMeta(meta: CacheMeta): void {
localStorage.setItem(STORAGE_KEY, JSON.stringify(meta));
}
/** Check if we have a tile cache covering the given location. */
export function hasCacheForLocation(lat: number, lon: number): boolean {
const meta = getCacheMeta();
if (!meta?.complete) return false;
const margin = meta.radius * 0.3;
return (
Math.abs(lat - meta.lat) < meta.radius - margin &&
Math.abs(lon - meta.lon) < meta.radius - margin
);
}
/**
* Seed the tile cache by panning the map across the surrounding area.
* The service worker's CacheFirst strategy on OSM tile URLs handles storage.
*
* Reports progress via callback (0 to 1).
*/
export async function cacheMapArea(
map: L.Map,
lat: number,
lon: number,
onProgress?: (progress: number) => void,
): Promise<boolean> {
try {
const totalSteps = CACHE_ZOOM_LEVELS.length * GRID_SIZE * GRID_SIZE;
let step = 0;
const originalZoom = map.getZoom();
const originalCenter = map.getCenter();
for (const zoom of CACHE_ZOOM_LEVELS) {
map.setZoom(zoom);
for (let row = 0; row < GRID_SIZE; row++) {
for (let col = 0; col < GRID_SIZE; col++) {
const panLat =
lat -
CACHE_RADIUS_DEGREES +
(2 * CACHE_RADIUS_DEGREES * row) / (GRID_SIZE - 1);
const panLon =
lon -
CACHE_RADIUS_DEGREES +
(2 * CACHE_RADIUS_DEGREES * col) / (GRID_SIZE - 1);
map.setView([panLat, panLon], zoom, { animate: false });
map.invalidateSize();
step++;
onProgress?.(step / totalSteps);
await sleep(PAN_DELAY_MS);
}
}
}
// Restore original view
map.setView(originalCenter, originalZoom, { animate: false });
saveCacheMeta({
lat,
lon,
radius: CACHE_RADIUS_DEGREES,
complete: true,
});
return true;
} catch {
return false;
}
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}