MoBis_MRU / public /tilecache.js
SamDNX's picture
Deploy MoBis: live planner API + web app + dataset
3accc99
Raw
History Blame Contribute Delete
3.16 kB
"use strict";
/*
* Offline map tiles via IndexedDB.
*
* A custom Leaflet tile layer that serves map tiles from IndexedDB when present
* β€” so areas you've viewed once work fully offline afterwards. This works in the
* Android WebView too, where a service worker can't run from a file:// page (the
* service worker handles the same job for the hosted PWA).
*
* It degrades safely: on any IndexedDB/network/CORS error it falls back to the
* plain tile URL, so the map can never be worse than an ordinary tile layer.
* Tiles are only stored as you pan/zoom while online (never bulk pre-fetched),
* in line with OpenStreetMap's tile usage policy.
*/
(function () {
const DB_NAME = "mobis-tiles", STORE = "tiles";
let dbp = null;
function db() {
if (dbp) return dbp;
dbp = new Promise((resolve, reject) => {
let req;
try { req = indexedDB.open(DB_NAME, 1); } catch (e) { return reject(e); }
req.onupgradeneeded = () => req.result.createObjectStore(STORE);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
}).catch(() => null);
return dbp;
}
async function idbGet(key) {
const d = await db();
if (!d) return null;
return new Promise((res) => {
try {
const r = d.transaction(STORE, "readonly").objectStore(STORE).get(key);
r.onsuccess = () => res(r.result || null);
r.onerror = () => res(null);
} catch (e) { res(null); }
});
}
async function idbPut(key, blob) {
const d = await db();
if (!d) return;
try { d.transaction(STORE, "readwrite").objectStore(STORE).put(blob, key); } catch (e) {}
}
if (!window.L || !L.TileLayer) return;
const CachedTileLayer = L.TileLayer.extend({
createTile(coords, done) {
const tile = document.createElement("img");
tile.setAttribute("role", "presentation");
tile.alt = "";
const url = this.getTileUrl(coords);
let settled = false;
const finish = () => { if (!settled) { settled = true; done(null, tile); } };
tile.addEventListener("load", finish);
tile.addEventListener("error", finish);
const useBlob = (blob) => {
const obj = URL.createObjectURL(blob);
tile.addEventListener("load", () => { try { URL.revokeObjectURL(obj); } catch (e) {} }, { once: true });
tile.src = obj;
};
idbGet(url).then((blob) => {
if (blob) { useBlob(blob); return; }
if (!navigator.onLine) { finish(); return; } // offline + uncached β†’ blank
fetch(url, { mode: "cors" })
.then((r) => (r && r.ok) ? r.blob() : Promise.reject())
.then((b) => { idbPut(url, b); useBlob(b); })
.catch(() => { tile.src = url; }); // CORS/network hiccup β†’ direct
}).catch(() => { tile.src = url; });
return tile;
},
});
// Drop-in factory: returns the caching layer, or a plain tile layer if the
// browser lacks IndexedDB.
window.makeTileLayer = function (template, opts) {
if (!("indexedDB" in window)) return L.tileLayer(template, opts);
return new CachedTileLayer(template, opts);
};
})();