Skip to content
This repository has been archived by the owner on Nov 21, 2024. It is now read-only.

Multiple fixes #26

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
245 changes: 167 additions & 78 deletions L.TileLayer.PouchDBCached.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,60 @@
L.TileLayer.PouchDB = L.TileLayer.extend({
options: {
// 🍂namespace TileLayer
// 🍂section PouchDB tile caching options
// 🍂option useCache: Boolean = false
// Whether to use a PouchDB cache on this tile layer, or not
useCache : false,

// 🍂option saveToCache: Boolean = true
// When caching is enabled, whether to save new tiles to the cache or not
saveToCache : true,

// 🍂option useOnlyCache: Boolean = false
// When caching is enabled, whether to request new tiles from the network or not
useOnlyCache : false,

// 🍂option useCache: String = 'image/png'
// The image format to be used when saving the tile images in the cache
cacheFormat : 'image/png',

// 🍂option cacheMaxAge: Number = 24*3600*1000
// Maximum age of the cache, in seconds
cacheMaxAge : 96*3600*1000,

// an array of tiles that were not correctly recieved from the server
missedTiles : [],

// Size limit for the DB in MB, (assuming a 12 Ko weight for a single tile)
dbSizeLimit : 40 // in Mb
},
initialize: function (url, options) {
this._url = url;
options = L.setOptions(this, L.extend(this.options,options));
this.addInitHook();
},
addInitHook: function () {
// TODO: delete _seed_canvas if the tests with the original create tile method works fine
if (!this.options.useCache) {
this._db = null;
this._canvas = null;
return;
}

this._db = new PouchDB('offline-tiles');

L.TileLayer.addInitHook(function() {

if (!this.options.useCache) {
this._db = null;
this._canvas = null;
return;
}
this._canvas = document.createElement('canvas');

this._db = new PouchDB('offline-tiles');
this._canvas = document.createElement('canvas');
if (!(this._canvas.getContext && this._canvas.getContext('2d'))) {
// HTML5 canvas is needed to pack the tiles as base64 data. If
// the browser doesn't support canvas, the code will forcefully
// skip caching the tiles.
this._canvas = null;
}

if (!(this._canvas.getContext && this._canvas.getContext('2d'))) {
// HTML5 canvas is needed to pack the tiles as base64 data. If
// the browser doesn't support canvas, the code will forcefully
// skip caching the tiles.
this._canvas = null;
}
});

// 🍂namespace TileLayer
// 🍂section PouchDB tile caching options
// 🍂option useCache: Boolean = false
// Whether to use a PouchDB cache on this tile layer, or not
L.TileLayer.prototype.options.useCache = false;

// 🍂option saveToCache: Boolean = true
// When caching is enabled, whether to save new tiles to the cache or not
L.TileLayer.prototype.options.saveToCache = true;

// 🍂option useOnlyCache: Boolean = false
// When caching is enabled, whether to request new tiles from the network or not
L.TileLayer.prototype.options.useOnlyCache = false;

// 🍂option useCache: String = 'image/png'
// The image format to be used when saving the tile images in the cache
L.TileLayer.prototype.options.cacheFormat = 'image/png';

// 🍂option cacheMaxAge: Number = 24*3600*1000
// Maximum age of the cache, in seconds
L.TileLayer.prototype.options.cacheMaxAge = 24*3600*1000;


L.TileLayer.include({

L.TileLayer.PouchDB.include({
// Overwrites L.TileLayer.prototype.createTile
createTile: function(coords, done) {
var tile = document.createElement('img');
Expand All @@ -63,10 +74,11 @@ L.TileLayer.include({
var tileUrl = this.getTileUrl(coords);

if (this.options.useCache && this._canvas) {
this._db.get(tileUrl, {revs_info: true}, this._onCacheLookup(tile, tileUrl, done));
this._db.get(tileUrl, {revs_info: true}, this._onCacheLookup(tile, tileUrl, done).bind(this));
} else {
// Fall back to standard behaviour
tile.onload = L.bind(this._tileOnLoad, this, done, tile);
//tile.onerror = L.bind(this._tileOnError, this, done, tile);
}

tile.src = tileUrl;
Expand Down Expand Up @@ -109,7 +121,7 @@ L.TileLayer.include({
});
if (this.options.useOnlyCache) {
// Offline, not cached
// console.log('Tile not in cache', tileUrl);
//console.log('Tile not in cache', tileUrl);
tile.onload = L.Util.falseFn;
tile.src = L.Util.emptyImageUrl;
} else {
Expand All @@ -124,7 +136,7 @@ L.TileLayer.include({
tile.src = tileUrl;
}
}
}.bind(this);
};
},

// Returns an event handler (closure over DB key), which runs
Expand All @@ -146,20 +158,46 @@ L.TileLayer.include({
this.fire('tilecacheerror', { tile: tile, error: err });
return done();
}
var doc = {dataUrl: dataUrl, timestamp: Date.now()};

if (existingRevision) {
this._db.remove(tileUrl, existingRevision);
this._db.get(tileUrl, function(err, doc) {
if (err) { return console.log(err); }
this._db.remove(doc, function(err, response) {
if (err) { return console.log(err); }
// handle response
// console.log(response);
});
}.bind(this));
}
/// FIXME: There is a deprecation warning about parameters in the
/// this._db.put() call.
this._db.put(doc, tileUrl, doc.timestamp);

if (done) { done(); }
// The doc id will be included in the doc obj to meet PouchDB 6.x compatibility
var new_doc = {
_id: tileUrl,
dataUrl: dataUrl,
timestamp: Date.now()
};

this._db.get(tileUrl, function(err, doc) {
if (err) {
// we assume that the error means no older version exists
this._db.put(new_doc);
return console.log(err);
}
// if an old version exist we update doc
new_doc._rev = doc._rev; // we have to specify which revision we want to update
this._db.put(new_doc, function(err, response) {
if (err) { return console.log(err); }
// handle response
if (done) {
done();
return;
} // the seed next tile is binded here
});
}.bind(this));

if(done) { done(); }
},

// 🍂section PouchDB tile caching options
// 🍂method seed(bbox: LatLngBounds, minZoom: Number, maxZoom: Number): this
// 🍂section PouchDB tile caching options
// 🍂method seed(bbox: LatLngBounds, minZoom: Number, maxZoom: Number): this
// Starts seeding the cache given a bounding box and the minimum/maximum zoom levels
// Use with care! This can spawn thousands of requests and flood tileservers!
seed: function(bbox, minZoom, maxZoom) {
Expand All @@ -168,65 +206,104 @@ L.TileLayer.include({
if (!this._map) return;

var queue = [];
this.missedTiles = [];
var Total_TilesNumber = 0 ;

// FIXED: there was a wrong generation of y coordinates

for (var z = maxZoom; z>=minZoom; z--) { // we start from the maximum zoom so the maximum tiles number is calculated

for (var z = minZoom; z<=maxZoom; z++) {
var nePoint = this._map.project(bbox.getNorthEast(),z),
swPoint = this._map.project(bbox.getSouthWest(),z),
tileBounds = this._pxBoundsToTileRange(L.bounds(nePoint,swPoint));

var northEastPoint = this._map.project(bbox.getNorthEast(),z);
var southWestPoint = this._map.project(bbox.getSouthWest(),z);

// Calculate tile indexes as per L.TileLayer._update and
// L.TileLayer._addTilesFromCenterOut
var tileSize = this.getTileSize();
var tileBounds = L.bounds(
L.point(Math.floor(northEastPoint.x / tileSize.x), Math.floor(northEastPoint.y / tileSize.y)),
L.point(Math.floor(southWestPoint.x / tileSize.x), Math.floor(southWestPoint.y / tileSize.y)));
// Calculate tile indexes as per L.TileLayer._update
// we calculate the current global tile range for the given zoom level

this._currentGlobalTileRange = this._pxBoundsToTileRange(this._map.getPixelWorldBounds(z));

// TODO: estimate the time and size of the cache before seeding
var delta_x = tileBounds.max.x - tileBounds.min.x,
delta_y = tileBounds.max.y - tileBounds.min.y,
nbr_Tiles = Math.abs(delta_x)*Math.abs(delta_y);

Total_TilesNumber += Math.ceil(nbr_Tiles);
if( Total_TilesNumber > this.dbSizeLimit*1024*12 ){
alert('Number of tiles too high '+Total_TilesNumber+'!. please reduce zoom range or bounds.');
return;
}

for (var j = tileBounds.min.y; j <= tileBounds.max.y; j++) {
for (var i = tileBounds.min.x; i <= tileBounds.max.x; i++) {
point = new L.Point(i, j);
point.z = z;
queue.push(this._getTileUrl(point));
queue.push(this._getTileUrl(point,this._currentGlobalTileRange));
}
}

}

console.log(Total_TilesNumber +' tiles will be cached! ....');

var seedData = {
bbox: bbox,
minZoom: minZoom,
maxZoom: maxZoom,
queueLength: queue.length
}
};

this.fire('seedstart', seedData);

var tile = this._createTile();
tile._layer = this;

this._seedOneTile(tile, queue, seedData);

return this;
},

_createTile: function () {
return document.createElement('img');
var tile = document.createElement('img');

if (this.options.crossOrigin) {
tile.crossOrigin = 'Anonymous';
}
return tile;

},

// Modified L.TileLayer.getTileUrl, this will use the zoom given by the parameter coords
// Modified L.TileLayer.PouchDB.getTileUrl, this will use the zoom given by the parameter coords
// instead of the maps current zoomlevel.
_getTileUrl: function (coords) {
_getTileUrl: function (coords,currentGlobalTileRange) {
var zoom = coords.z;
if (this.options.zoomReverse) {
zoom = this.options.maxZoom - zoom;
}
zoom += this.options.zoomOffset;
return L.Util.template(this._url, L.extend({

var data = {
r: this.options.detectRetina && L.Browser.retina && this.options.maxZoom > 0 ? '@2x' : '',
s: this._getSubdomain(coords),
x: coords.x,
y: this.options.tms ? this._globalTileRange.max.y - coords.y : coords.y,
y: coords.y,
z: this.options.maxNativeZoom ? Math.min(zoom, this.options.maxNativeZoom) : zoom
}, this.options));
},
};
if (this._map && !this._map.options.crs.infinite) {
// from L.TileLayer tms = true but _globalTileRange must correspond to the current zoom
//var invertedY = this._globalTileRange.max.y - coords.y;
var invertedY = currentGlobalTileRange.max.y - coords.y;
if (this.options.tms) {
data['y'] = invertedY;
}
data['-y'] = invertedY; // either the user put tms = true or {-y} in the url
}

return L.Util.template(this._url, L.extend(data, this.options));
},
// Uses a defined tile to eat through one item in the queue and
// asynchronously recursively call itself when the tile has
// finished loading.
// finished loading. (that did not work properly especially on tiles error, the fix was to pass this as a call back for the save function)
_seedOneTile: function(tile, remaining, seedData) {
if (!remaining.length) {
this.fire('seedend', seedData);
Expand All @@ -244,20 +321,32 @@ L.TileLayer.include({

this._db.get(url, function(err, data) {
if (!data) {
/// FIXME: Do something on tile error!!
tile.onload = function(ev) {
this._saveTile(tile, url, null); //(ev)
// Save tile to db and bind seedOnTile function as a callback
this._saveTile(tile, url ,true, L.bind(this._seedOneTile, this, tile, remaining, seedData));

}.bind(this);

tile.onerror = function (ev) {
// push the url into missed tiles for further operations

this.missedTiles.push(url);

// in case of a tile load error and we have pushed it once go to the next tile
this._seedOneTile(tile, remaining, seedData);
}.bind(this);
tile.crossOrigin = 'Anonymous';
tile.src = url;

tile.src = url; // get the image

} else {

this._seedOneTile(tile, remaining, seedData);
}
}.bind(this));

}

});


L.tileLayer.pouchdb = function(url , options){
return new L.TileLayer.PouchDB(url,options);
};