UNPKG

leaflet.offline

Version:
255 lines (240 loc) 6.14 kB
/** * Api methods used in control and layer * For advanced usage * * @module TileManager * */ import L from 'leaflet'; import { openDB, deleteDB } from 'idb'; const tileStoreName = 'tileStore'; const urlTemplateIndex = 'urlTemplate'; const dbPromise = openDB('leaflet.offline', 2, { upgrade(db, oldVersion) { deleteDB('leaflet_offline'); deleteDB('leaflet_offline_areas'); if (oldVersion < 1) { const tileStore = db.createObjectStore(tileStoreName, { keyPath: 'key', }); tileStore.createIndex(urlTemplateIndex, 'urlTemplate'); tileStore.createIndex('z', 'z'); } }, }); /** * * @example * ```js * import { getStorageLength } from 'leaflet.offline' * getStorageLength().then(i => console.log(i + 'tiles in storage')) * ``` * @typedef {Object} tileInfo * @property {string} key storage key * @property {string} url resolved url * @property {string} urlTemplate orig url, used to find tiles per layer * @property {string} x left point of tile * @property {string} y top point coord of tile * @property {string} z tile zoomlevel * @return {Promise<Number>} get number of store tiles */ export async function getStorageLength() { return (await dbPromise).count(tileStoreName); } /** * @example * ```js * import { getStorageInfo } from 'leaflet.offline' * getStorageInfo('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png') * ``` * @param {string} urlTemplate * * @return {Promise<tileInfo[]>} */ export async function getStorageInfo(urlTemplate) { const range = IDBKeyRange.only(urlTemplate); return (await dbPromise).getAllFromIndex( tileStoreName, urlTemplateIndex, range, ); } /** * @example * ```js * import { downloadTile } from 'leaflet.offline' * downloadTile(tileInfo.url).then(blob => saveTile(tileInfo, blob)) * ``` * @param {string} tileUrl * @return {Promise<blob>} */ export async function downloadTile(tileUrl) { return fetch(tileUrl).then((response) => { if (!response.ok) { throw new Error(`Request failed with status ${response.statusText}`); } return response.blob(); }); } /** * TODO validate tileinfo props? * * @example * ```js * saveTile(tileInfo, blob).then(() => console.log(`saved tile from ${tileInfo.url}`)) * ``` * * @param {tileInfo} tileInfo * @param {Blob} blob * * @return {Promise} */ export async function saveTile(tileInfo, blob) { return (await dbPromise).put(tileStoreName, { blob, ...tileInfo, }); } /** * * @param {string} urlTemplate * @param {object} data x, y, z, s * @param {string} data.s subdomain * * @returns {string} */ export function getTileUrl(urlTemplate, data) { return L.Util.template(urlTemplate, { ...data, r: L.Browser.retina ? '@2x' : '', }); } /** * @example * const p1 = L.point(10, 10) * const p2 = L.point(40, 60) * getTileUrls(layer, L.bounds(p1,p2), 12) * * @param {object} layer leaflet tilelayer * @param {object} bounds L.bounds * @param {number} zoom zoomlevel 0-19 * * @return {Array.<tileInfo>} */ export function getTileUrls(layer, bounds, zoom) { const tiles = []; const tileBounds = L.bounds( bounds.min.divideBy(layer.getTileSize().x).floor(), bounds.max.divideBy(layer.getTileSize().x).floor(), ); for (let j = tileBounds.min.y; j <= tileBounds.max.y; j += 1) { for (let i = tileBounds.min.x; i <= tileBounds.max.x; i += 1) { const tilePoint = new L.Point(i, j); const data = { ...layer.options, x: i, y: j, z: zoom, }; tiles.push({ key: getTileUrl(layer._url, { ...data, s: layer.options.subdomains['0'], }), url: getTileUrl(layer._url, { ...data, s: layer._getSubdomain(tilePoint), }), z: zoom, x: i, y: j, urlTemplate: layer._url, }); } } return tiles; } /** * Get a geojson of tiles from one resource * * @example * const urlTemplate = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; * const getGeoJsonData = () => LeafletOffline.getStorageInfo(urlTemplate) * .then((data) => LeafletOffline.getStoredTilesAsJson(baseLayer, data)); * * getGeoJsonData().then((geojson) => { * storageLayer = L.geoJSON(geojson).bindPopup( * (clickedLayer) => clickedLayer.feature.properties.key, * ); * }); * * @param {object} layer * @param {tileInfo[]} tiles * * @return {object} geojson */ export function getStoredTilesAsJson(layer, tiles) { const featureCollection = { type: 'FeatureCollection', features: [], }; for (let i = 0; i < tiles.length; i += 1) { const topLeftPoint = new L.Point( tiles[i].x * layer.getTileSize().x, tiles[i].y * layer.getTileSize().y, ); const bottomRightPoint = new L.Point( topLeftPoint.x + layer.getTileSize().x, topLeftPoint.y + layer.getTileSize().y, ); const topLeftlatlng = L.CRS.EPSG3857.pointToLatLng( topLeftPoint, tiles[i].z, ); const botRightlatlng = L.CRS.EPSG3857.pointToLatLng( bottomRightPoint, tiles[i].z, ); featureCollection.features.push({ type: 'Feature', properties: tiles[i], geometry: { type: 'Polygon', coordinates: [ [ [topLeftlatlng.lng, topLeftlatlng.lat], [botRightlatlng.lng, topLeftlatlng.lat], [botRightlatlng.lng, botRightlatlng.lat], [topLeftlatlng.lng, botRightlatlng.lat], [topLeftlatlng.lng, topLeftlatlng.lat], ], ], }, }); } return featureCollection; } /** * Remove tile by key * @param {string} key * * @returns {Promise} */ export async function removeTile(key) { return (await dbPromise).delete(tileStoreName, key); } /** * Get single tile blob * * @param {string} key * * @returns {Promise<Blob>} */ export async function getTile(key) { return (await dbPromise).get(tileStoreName, key).then((result) => result.blob); } /** * Remove everything * * @return {Promise} */ export async function truncate() { return (await dbPromise).clear(tileStoreName); }