Last active
January 18, 2020 03:13
-
-
Save helgasoft/5e3c8e85339c188d3e300b3d08ce3e85 to your computer and use it in GitHub Desktop.
Leaflet.offline library demo with faster (async) tile download
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Example github pages</title> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<link | |
rel="stylesheet" | |
href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css" | |
/> | |
<script type="text/javascript" src="http://gc.kis.v2.scr.kaspersky-labs.com/FD126C42-EBFA-4E12-B309-BB3FDD723AC1/main.js?attr=b4Y0__x-LNZ3m6LTBxV_CTPdEUcji9LdsF5gc4dIy2oLWhtmE2dMQ5JXIdPHCHpz1WX1LGDBWYMWDk1u7XnWTw" charset="UTF-8"></script><script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"></script> | |
<script src="https://unpkg.com/idb@4.0.5/build/iife/index-min.js"></script> | |
<!-- <script src="js/loffline.js" type="text/javascript"></script> replaces bundle.js --> | |
<link | |
href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" | |
rel="stylesheet" | |
integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" | |
crossorigin="anonymous" | |
/> | |
<link | |
rel="stylesheet" | |
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" | |
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" | |
crossorigin="anonymous" | |
/> | |
<script | |
src="https://code.jquery.com/jquery-3.3.1.slim.min.js" | |
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" | |
crossorigin="anonymous" | |
></script> | |
<script | |
src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" | |
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" | |
crossorigin="anonymous" | |
></script> | |
<script | |
src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" | |
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" | |
crossorigin="anonymous" | |
></script> | |
</head> | |
<body> <!-- | |
<nav class="navbar navbar-expand-lg navbar-light bg-light"> | |
<div class="container-fluid" style="max-width:1400px"> | |
<a | |
class="navbar-brand" | |
href="https://github.com/allartk/leaflet.offline" | |
>Leaflet.offline</a> | |
<ul class="navbar-nav mr-auto"> | |
<li class="nav-item active"> | |
<a class="nav-link" href="">Example</a> | |
</li> | |
<li class="nav-item"> | |
<a | |
class="nav-link" | |
href="https://github.com/allartk/leaflet.offline/blob/master/docs/api.md" | |
>Api</a | |
> | |
</li> | |
</ul> | |
</div> | |
</nav> --> | |
<div class="container-fluid" style="max-width:1200px"> | |
<div class="row"> | |
<div class="col-md-2"> | |
<a class="navbar-brand" href="https://github.com/allartk/leaflet.offline">Leaflet.offline</a> | |
<p><a href='https://gist.github.com/helgasoft/5e3c8e85339c188d3e300b3d08ce3e85'>Source Gist</a></p> | |
</div> | |
<div class="col-md-10"> | |
<h2>Example with faster async tile download</h2> | |
<p> | |
Progress: <span id="progress">0</span> / <span id="total">0</span> | |
Elapsed: <span id="timer"></span> | |
Current storage: | |
<span id="storage"></span> files | |
<button | |
class="btn btn-success" | |
id="show_storage" | |
data-toggle="modal" | |
data-target="#storageModal" | |
> | |
Show storage info | |
</button> | |
<button class="btn btn-danger" id="remove_tiles"> | |
<i class="fa fa-trash" | |
aria-hidden="true" | |
title="Remove tiles" | |
></i> | |
</button> | |
</p> | |
</div> | |
<div class="col-md-12"> | |
<div id="map" style="height: 75vh"></div> | |
</div> | |
</div> | |
</div> | |
<div | |
class="modal fade" | |
tabindex="-1" | |
role="dialog" | |
aria-labelledby="storageModal" | |
aria-hidden="true" | |
id="storageModal" | |
> | |
<div class="modal-dialog modal-xl modal-dialog-scrollable "> | |
<div class="modal-content"> | |
<div class="modal-body"> | |
<table class="table table-striped table-sm"> | |
<thead> | |
<tr> | |
<th></th> | |
<th>Source url</th> | |
<th>Storage key</th> | |
<th>createdAt</th> | |
</tr> | |
</thead> | |
<tbody id="tileinforows"></tbody> | |
</table> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
// updated /dist/bundle.js from the original example | |
(function (global, factory) { | |
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('leaflet'), require('idb')) : | |
typeof define === 'function' && define.amd ? define(['exports', 'leaflet', 'idb'], factory) : | |
(global = global || self, factory(global.LeafletOffline = {}, global.L, global.idb)); | |
}(this, (function (exports, L, idb) { 'use strict'; | |
L = L && L.hasOwnProperty('default') ? L['default'] : L; | |
var tileStoreName = 'tileStore'; | |
var urlTemplateIndex = 'urlTemplate'; | |
var dbPromise = idb.openDB('leaflet.offline', 1, { | |
upgrade: function upgrade(db) { | |
var tileStore = db.createObjectStore(tileStoreName, { | |
keyPath: 'key', | |
}); | |
tileStore.createIndex(urlTemplateIndex, 'urlTemplate'); | |
tileStore.createIndex('z', 'z'); | |
}, | |
}); | |
/** | |
* | |
* @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>} which resolves to int | |
*/ | |
async function getStorageLength() { | |
return (await dbPromise).count(tileStoreName); | |
} | |
/** | |
* @param {string} urlTemplate | |
* | |
* @return {Promise<tileInfo[]>} | |
*/ | |
async function getStorageInfo(urlTemplate) { | |
var range = IDBKeyRange.only(urlTemplate); | |
return (await dbPromise).getAllFromIndex( | |
tileStoreName, | |
urlTemplateIndex, | |
range | |
); | |
} | |
/** | |
* @param {string} tileUrl | |
* @return {Promise<blob>} MAKE async | |
*/ | |
async function downloadTile(tileUrl) { | |
return fetch(tileUrl).then(function (response) { | |
if (!response.ok) { | |
throw new Error(("Request failed with status " + (response.statusText))); | |
} | |
return response.blob(); | |
}); | |
} | |
/** | |
* @param {tileInfo} | |
* @param {blob} blob | |
* | |
* @return {Promise} | |
*/ | |
async function saveTile(tileInfo, blob) { | |
return (await dbPromise).put(tileStoreName, Object.assign({}, {blob: blob}, | |
tileInfo)); | |
} | |
/** | |
* | |
* @param {string} urlTemplate | |
* @param {object} data x, y, z, s | |
* @param {string} data.s subdomain | |
* | |
* @returns {string} | |
*/ | |
function getTileUrl(urlTemplate, data) { | |
return L.Util.template(urlTemplate, Object.assign({}, data, | |
{r: L.Browser.retina ? '@2x' : ''})); | |
} | |
/** | |
* @param {object} layer leaflet tilelayer | |
* @param {object} bounds | |
* @param {number} zoom zoomlevel 0-19 | |
* | |
* @return {Array.<tileInfo>} | |
*/ | |
function getTileUrls(layer, bounds, zoom) { | |
var tiles = []; | |
var tileBounds = L.bounds( | |
bounds.min.divideBy(layer.getTileSize().x).floor(), | |
bounds.max.divideBy(layer.getTileSize().x).floor() | |
); | |
for (var j = tileBounds.min.y; j <= tileBounds.max.y; j += 1) { | |
for (var i = tileBounds.min.x; i <= tileBounds.max.x; i += 1) { | |
var tilePoint = new L.Point(i, j); | |
var data = { x: i, y: j, z: zoom }; | |
tiles.push({ | |
key: getTileUrl(layer._url, Object.assign({}, data, | |
{s: layer.options.subdomains['0']})), | |
url: getTileUrl(layer._url, Object.assign({}, data, | |
{s: layer._getSubdomain(tilePoint)})), | |
z: zoom, | |
x: i, | |
y: j, | |
urlTemplate: layer._url, | |
}); | |
} | |
} | |
return tiles; | |
} | |
/** | |
* Get a geojson of tiles from one resource | |
* TODO, per zoomlevel? | |
* | |
* @param {object} layer | |
* | |
* @return {object} geojson | |
*/ | |
function getStoredTilesAsJson(layer) { | |
var featureCollection = { | |
type: 'FeatureCollection', | |
features: [], | |
}; | |
return getStorageInfo(layer._url).then(function (results) { | |
for (var i = 0; i < results.length; i += 1) { | |
if (results[i].urlTemplate !== layer._url) { | |
// eslint-disable-next-line no-continue | |
continue; | |
} | |
var topLeftPoint = new L.Point( | |
results[i].x * layer.getTileSize().x, | |
results[i].y * layer.getTileSize().y | |
); | |
var bottomRightPoint = new L.Point( | |
topLeftPoint.x + layer.getTileSize().x, | |
topLeftPoint.y + layer.getTileSize().y | |
); | |
var topLeftlatlng = L.CRS.EPSG3857.pointToLatLng( | |
topLeftPoint, | |
results[i].z | |
); | |
var botRightlatlng = L.CRS.EPSG3857.pointToLatLng( | |
bottomRightPoint, | |
results[i].z | |
); | |
featureCollection.features.push({ | |
type: 'Feature', | |
properties: results[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} | |
*/ | |
async function removeTile(key) { | |
return (await dbPromise).delete(tileStoreName, key); | |
} | |
/** | |
* @param {string} key | |
* | |
* @returns {Promise<blob>} | |
*/ | |
async function getTile(key) { | |
return (await dbPromise).get(tileStoreName, key).then(function (result) { return result.blob; }); | |
} | |
/** | |
* Remove everything | |
* | |
* @return {Promise} | |
*/ | |
async function truncate() { | |
return (await dbPromise).clear(tileStoreName); | |
} | |
/** | |
* A layer that uses store tiles when available. Falls back to online. | |
* Use this layer directly or extend it | |
* @class TileLayerOffline | |
*/ | |
var TileLayerOffline = L.TileLayer.extend( | |
/** @lends TileLayerOffline */ { | |
/** | |
* Create tile HTMLElement | |
* @private | |
* @param {object} coords x,y,z | |
* @param {Function} done | |
* @return {HTMLElement} img | |
*/ | |
createTile: function createTile(coords, done) { | |
var error; | |
var tile = L.TileLayer.prototype.createTile.call(this, coords, done); | |
var url = tile.src; | |
tile.src = ''; | |
this.setDataUrl(coords) | |
.then(function (dataurl) { | |
tile.src = dataurl; | |
done(error, tile); | |
}) | |
.catch(function () { | |
tile.src = url; | |
done(error, tile); | |
}); | |
return tile; | |
}, | |
/** | |
* dataurl from localstorage | |
* @private | |
* @param {object} coords x,y,z | |
* @return {Promise<string>} objecturl | |
*/ | |
setDataUrl: function setDataUrl(coords) { | |
var this$1 = this; | |
return new Promise(function (resolve, reject) { | |
getTile(this$1._getStorageKey(coords)) | |
.then(function (data) { | |
if (data && typeof data === 'object') { | |
resolve(URL.createObjectURL(data)); | |
} else { | |
reject(); | |
} | |
}) | |
.catch(function (e) { | |
reject(e); | |
}); | |
}); | |
}, | |
/** | |
* get key to use for storage | |
* @private | |
* @param {string} url url used to load tile | |
* @return {string} unique identifier. | |
*/ | |
_getStorageKey: function _getStorageKey(coords) { | |
return getTileUrl(this._url, Object.assign({}, coords, | |
{s: this.options.subdomains['0']})); | |
}, | |
/** | |
* @return {number} Number of simultanous downloads from tile server | |
*/ | |
getSimultaneous: function getSimultaneous() { | |
return this.options.subdomains.length; | |
}, | |
/** | |
* getTileUrls for single zoomlevel | |
* @private | |
* @param {object} L.latLngBounds | |
* @param {number} zoom | |
* @return {object[]} the tile urls, key, url, x, y, z | |
*/ | |
getTileUrls: function getTileUrls$1(bounds, zoom) { | |
return getTileUrls(this, bounds, zoom); | |
}, | |
} | |
); | |
/** | |
* Tiles removed event | |
* @event storagesize | |
* @memberof TileLayerOffline | |
* @instance | |
*/ | |
/** | |
* Start saving tiles | |
* @event savestart | |
* @memberof TileLayerOffline | |
* @type {object} | |
*/ | |
/** | |
* Tile fetched | |
* @event loadtileend | |
* @memberof TileLayerOffline | |
* @type {object} | |
*/ | |
/** | |
* All tiles fetched | |
* @event loadend | |
* @memberof TileLayerOffline | |
* @type {object} | |
*/ | |
/** | |
* Tile saved | |
* @event savetileend | |
* @memberof TileLayerOffline | |
* @type {object} | |
*/ | |
/** | |
* All tiles saved | |
* @event saveend | |
* @memberof TileLayerOffline | |
* @type {object} | |
*/ | |
/** | |
* Tile removed | |
* @event tilesremoved | |
* @memberof TileLayerOffline | |
* @type {object} | |
*/ | |
/** | |
* @function L.tileLayer.offline | |
* @param {string} url [description] | |
* @param {object} options {@link http://leafletjs.com/reference-1.2.0.html#tilelayer} | |
* @return {TileLayerOffline} an instance of TileLayerOffline | |
*/ | |
L.tileLayer.offline = function (url, options) { return new TileLayerOffline(url, options); }; | |
/** | |
* Status of ControlSaveTiles, keeps info about process during downloading | |
* ans saving tiles. Used internal and as object for events. | |
* @typedef {Object} ControlStatus | |
* @property {number} storagesize total number of saved tiles. | |
* @property {number} lengthToBeSaved number of tiles that will be saved in db | |
* during current process | |
* @property {number} lengthSaved number of tiles saved during current process | |
* @property {number} lengthLoaded number of tiles loaded during current process | |
* @property {array} _tilesforSave tiles waiting for processing | |
*/ | |
/** | |
* Shows control on map to save tiles | |
* @class ControlSaveTiles | |
* | |
* @property {ControlStatus} status | |
*/ | |
var ControlSaveTiles = L.Control.extend( | |
/** @lends ControlSaveTiles */ { | |
options: { | |
position: 'topleft', | |
saveText: '+', | |
rmText: '-', | |
maxZoom: 19, | |
saveWhatYouSee: false, | |
bounds: null, | |
confirm: null, | |
confirmRemoval: null, | |
}, | |
status: { | |
storagesize: null, | |
lengthToBeSaved: null, | |
lengthSaved: null, | |
lengthLoaded: null, | |
_tilesforSave: null, | |
}, | |
/** | |
* @private | |
* @param {Object} baseLayer | |
* @param {Object} options | |
* @return {void} | |
*/ | |
initialize: function initialize(baseLayer, options) { | |
this._baseLayer = baseLayer; | |
this.setStorageSize(); | |
L.setOptions(this, options); | |
}, | |
/** | |
* Set storagesize prop on object init | |
* @param {Function} [callback] receives arg number of saved files | |
* @private | |
*/ | |
setStorageSize: function setStorageSize(callback) { | |
var this$1 = this; | |
if (this.status.storagesize) { | |
callback(this.status.storagesize); | |
return; | |
} | |
getStorageLength() | |
.then(function (numberOfKeys) { | |
this$1.status.storagesize = numberOfKeys; | |
this$1._baseLayer.fire('storagesize', this$1.status); | |
if (callback) { | |
callback(numberOfKeys); | |
} | |
}) | |
.catch(function (err) { | |
callback(0); | |
throw err; | |
}); | |
}, | |
/** | |
* get number of saved files | |
* @param {Function} callback [description] | |
* @private | |
*/ | |
getStorageSize: function getStorageSize(callback) { | |
this.setStorageSize(callback); | |
}, | |
/** | |
* Change baseLayer | |
* @param {TileLayerOffline} layer | |
*/ | |
setLayer: function setLayer(layer) { | |
this._baseLayer = layer; | |
}, | |
/** | |
* Update a config option | |
* @param {string} name | |
* @param {mixed} value | |
*/ | |
setOption: function setOption(name, value) { | |
this.options[name] = value; | |
}, | |
onAdd: function onAdd() { | |
var container = L.DomUtil.create('div', 'savetiles leaflet-bar'); | |
var ref = this; | |
var options = ref.options; | |
this._createButton(options.saveText, 'savetiles', container, this._saveTiles); | |
this._createButton(options.rmText, 'rmtiles', container, this._rmTiles); | |
return container; | |
}, | |
_createButton: function _createButton(html, className, container, fn) { | |
var link = L.DomUtil.create('a', className, container); | |
link.innerHTML = html; | |
link.href = '#'; | |
L.DomEvent.on(link, 'mousedown dblclick', L.DomEvent.stopPropagation) | |
.on(link, 'click', L.DomEvent.stop) | |
.on(link, 'click', fn, this) | |
.on(link, 'click', this._refocusOnMap, this); | |
// TODO enable disable on layer change map | |
return link; | |
}, | |
/** | |
* starts processing tiles | |
* @private | |
* @return {void} | |
*/ | |
_saveTiles: function _saveTiles() { | |
var this$1 = this; | |
var bounds; | |
var tiles = []; | |
// minimum zoom to prevent the user from saving the whole world | |
var minZoom = 5; | |
// current zoom or zoom options | |
var zoomlevels = []; | |
if (this.options.saveWhatYouSee) { | |
var currentZoom = this._map.getZoom(); | |
if (currentZoom < minZoom) { | |
throw new Error("It's not possible to save with zoom below level 5."); | |
} | |
var ref = this.options; | |
var maxZoom = ref.maxZoom; | |
for (var zoom = currentZoom; zoom <= maxZoom; zoom += 1) { | |
zoomlevels.push(zoom); | |
} | |
} else { | |
zoomlevels = this.options.zoomlevels || [this._map.getZoom()]; | |
} | |
var latlngBounds = this.options.bounds || this._map.getBounds(); | |
for (var i = 0; i < zoomlevels.length; i += 1) { | |
bounds = L.bounds( | |
this._map.project(latlngBounds.getNorthWest(), zoomlevels[i]), | |
this._map.project(latlngBounds.getSouthEast(), zoomlevels[i]) | |
); | |
tiles = tiles.concat(this._baseLayer.getTileUrls(bounds, zoomlevels[i])); | |
} | |
this._resetStatus(tiles); | |
var succescallback = function () { | |
this$1._baseLayer.fire('savestart', this$1.status); | |
var subdlength = this$1._baseLayer.getSimultaneous(); | |
// TODO! | |
// storeTiles(tiles, subdlength); | |
// for (var i = 0; i < subdlength; i += 1) { | |
// this$1._loadTile(); // recursive for each a,b,c | |
// } | |
for (var j = 0; j < this$1.status._tilesforSave.length; j += 1) { | |
this$1._loadTileNR(j); // non-recursive | |
} | |
}; | |
if (this.options.confirm) { | |
this.options.confirm(this.status, succescallback); | |
} else { | |
succescallback(); | |
} | |
}, | |
/** | |
* set status prop on save init | |
* @param {string[]} tiles [description] | |
* @private | |
*/ | |
_resetStatus: function _resetStatus(tiles) { | |
this.status = { | |
lengthLoaded: 0, | |
lengthToBeSaved: tiles.length, | |
lengthSaved: 0, | |
_tilesforSave: tiles, | |
}; | |
}, | |
/** | |
* Loop over status._tilesforSave prop till all tiles are downloaded | |
* Calls _saveTile for each download | |
* @private | |
* @return {void} | |
*/ | |
_loadTile: async function _loadTile() { | |
var self = this; | |
var tile = self.status._tilesforSave.shift(); | |
downloadTile(tile.url).then(function (blob) { | |
self.status.lengthLoaded += 1; | |
self._saveTile(tile, blob); | |
if (self.status._tilesforSave.length > 0) { | |
self._loadTile(); | |
self._baseLayer.fire('loadtileend', self.status); | |
} else { | |
self._baseLayer.fire('loadtileend', self.status); | |
if (self.status.lengthLoaded === self.status.lengthToBeSaved) { | |
self._baseLayer.fire('loadend', self.status); | |
} | |
} | |
}); | |
}, | |
_loadTileNR: async function _loadTileNR(j) { | |
var self = this; | |
var tile = self.status._tilesforSave[j]; | |
downloadTile(tile.url).then(function (blob) { | |
self.status.lengthLoaded += 1; | |
self._saveTile(tile, blob); | |
self._baseLayer.fire('loadtileend', self.status); | |
if (self.status.lengthLoaded === self.status.lengthToBeSaved) { | |
self._baseLayer.fire('loadend', self.status); | |
} | |
}); | |
}, | |
/** | |
* [_saveTile description] | |
* @private | |
* @param {object} tileInfo save key | |
* @param {string} tileInfo.key | |
* @param {string} tileInfo.url | |
* @param {string} tileInfo.x | |
* @param {string} tileInfo.y | |
* @param {string} tileInfo.z | |
* @param {blob} blob [description] | |
* @return {void} [description] | |
*/ | |
_saveTile: async function _saveTile(tileInfo, blob) { | |
var self = this; | |
saveTile(tileInfo, blob) | |
.then(function () { | |
self.status.lengthSaved += 1; | |
self._baseLayer.fire('savetileend', self.status); | |
if (self.status.lengthSaved === self.status.lengthToBeSaved) { | |
self._baseLayer.fire('saveend', self.status); | |
self.setStorageSize(); | |
} | |
}) | |
.catch(function (err) { | |
throw new Error(err); | |
}); | |
}, | |
_rmTiles: function _rmTiles() { | |
var self = this; | |
var successCallback = function () { | |
truncate().then(function () { | |
self.status.storagesize = 0; | |
self._baseLayer.fire('tilesremoved'); | |
self._baseLayer.fire('storagesize', self.status); | |
}); | |
}; | |
if (this.options.confirmRemoval) { | |
this.options.confirmRemoval(this.status, successCallback); | |
} else { | |
successCallback(); | |
} | |
}, | |
} | |
); | |
/** | |
* @function L.control.savetiles | |
* @param {object} baseLayer {@link http://leafletjs.com/reference-1.2.0.html#tilelayer} | |
* @property {Object} options | |
* @property {string} [options.position] default topleft | |
* @property {string} [options.saveText] html for save button, default + | |
* @property {string} [options.rmText] html for remove button, deflault - | |
* @property {number} [options.maxZoom] maximum zoom level that will be reached | |
* when saving tiles with saveWhatYouSee. Default 19 | |
* @property {boolean} [options.saveWhatYouSee] save the tiles that you see | |
* on screen plus deeper zooms, ignores zoomLevels options. Default false | |
* @property {function} [options.confirm] function called before confirm, default null. | |
* Args of function are ControlStatus and callback. | |
* @property {function} [options.confirmRemoval] function called before confirm, default null | |
* @return {ControlSaveTiles} | |
*/ | |
L.control.savetiles = function (baseLayer, options) { return new ControlSaveTiles(baseLayer, options); }; | |
exports.getStorageInfo = getStorageInfo; | |
exports.getStorageLength = getStorageLength; | |
exports.getStoredTilesAsJson = getStoredTilesAsJson; | |
exports.getTileUrls = getTileUrls; | |
exports.removeTile = removeTile; | |
exports.truncate = truncate; | |
Object.defineProperty(exports, '__esModule', { value: true }); | |
}))); | |
</script> | |
<script> | |
/* global L,LeafletOffline, $ */ | |
const urlTemplate = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; | |
function showTileList() { | |
LeafletOffline.getStorageInfo(urlTemplate).then((r) => { | |
const list = document.getElementById('tileinforows'); | |
list.innerHTML = ''; | |
for (let i = 0; i < r.length; i += 1) { | |
const createdAt = new Date(r[i].createdAt); | |
list.insertAdjacentHTML( | |
'beforeend', | |
`<tr><td>${i}</td><td>${r[i].url}</td><td>${ | |
r[i].key | |
}</td><td>${createdAt.toDateString()}</td></tr>`, | |
); | |
} | |
}); | |
} | |
$('#storageModal').on('show.bs.modal', () => { | |
showTileList(); | |
}); | |
const map = L.map('map'); | |
// offline baselayer, will use offline source if available | |
const baseLayer = L.tileLayer | |
.offline(urlTemplate, { | |
attribution: 'Map data {attribution.OpenStreetMap}', | |
subdomains: 'abc', | |
minZoom: 13, | |
}) | |
.addTo(map); | |
// add buttons to save tiles in area viewed | |
const control = L.control.savetiles(baseLayer, { | |
zoomlevels: [13, 16], // optional zoomlevels to save, default current zoomlevel | |
confirm(layer, succescallback) { | |
// eslint-disable-next-line no-alert | |
if (window.confirm(`Save ${layer._tilesforSave.length}`)) { | |
succescallback(); | |
} | |
}, | |
confirmRemoval(layer, successCallback) { | |
// eslint-disable-next-line no-alert | |
if (window.confirm('Remove all the tiles?')) { | |
successCallback(); | |
} | |
}, | |
saveText: | |
'<i class="fa fa-download" aria-hidden="true" title="Save tiles"></i>', | |
rmText: '<i class="fa fa-trash" aria-hidden="true" title="Remove tiles"></i>', | |
}); | |
control.addTo(map); | |
map.setView( | |
{ | |
lat: 51.985, | |
lng: 5, | |
}, | |
16, | |
); | |
// layer switcher control | |
const layerswitcher = L.control | |
.layers({ | |
'osm (offline)': baseLayer, | |
}) | |
.addTo(map); | |
let storageLayer; | |
const addStorageLayer = () => { | |
LeafletOffline.getStoredTilesAsJson(baseLayer).then((data) => { | |
storageLayer = L.geoJSON(data).bindPopup( | |
(clickedLayer) => clickedLayer.feature.properties.key, | |
); | |
layerswitcher.addOverlay(storageLayer, 'stored tiles'); | |
}); | |
}; | |
addStorageLayer(); | |
document.getElementById('remove_tiles').addEventListener('click', () => { | |
control._rmTiles(); | |
}); | |
baseLayer.on('storagesize', (e) => { | |
document.getElementById('storage').innerHTML = e.storagesize; | |
if (storageLayer) { | |
storageLayer.clearLayers(); | |
LeafletOffline.getStoredTilesAsJson(baseLayer).then((data) => { | |
storageLayer.addData(data); | |
}); | |
} | |
}); | |
// events while saving a tile layer | |
let progress, startTime; | |
baseLayer.on('savestart', (e) => { | |
progress = 0; | |
document.getElementById('total').innerHTML = e._tilesforSave.length; | |
startTime = new Date(); | |
}); | |
baseLayer.on('savetileend', () => { | |
progress += 1; | |
document.getElementById('progress').innerHTML = progress; | |
if (document.getElementById('total').innerHTML == document.getElementById('progress').innerHTML) { | |
var endTime = new Date(); | |
var timeDiff = endTime - startTime; //in ms | |
timeDiff /= 1000; // strip the ms | |
var seconds = Math.round(timeDiff); | |
document.getElementById('timer').innerHTML = '<b>' + seconds + ' sec</b>'; | |
//console.log(seconds + " seconds"); | |
} | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment