@yaga/indexed-db-tile-cache
Version:
Spatial tile cache that saves its data into the IndexedDB of your browser
238 lines • 11.4 kB
JavaScript
;
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var tile_utils_1 = require("@yaga/tile-utils");
var buffer_1 = require("buffer");
var events_1 = require("events");
var request = require("request");
var consts_1 = require("./consts");
/**
* Class for a spatial-tile-cache that stores its data in the browsers IndexedDB
*/
var IndexedDbTileCache = /** @class */ (function (_super) {
__extends(IndexedDbTileCache, _super);
function IndexedDbTileCache(options) {
if (options === void 0) { options = {}; }
var _this = _super.call(this) || this;
_this.options = options;
_this.options.databaseName = _this.options.databaseName || consts_1.DEFAULT_DATABASE_NAME;
_this.options.databaseVersion = _this.options.databaseVersion || consts_1.DEFAULT_DATABASE_VERSION;
_this.options.objectStoreName = _this.options.objectStoreName || consts_1.DEFAULT_OBJECT_STORE_NAME;
_this.options.tileUrl = _this.options.tileUrl || consts_1.DEFAULT_TILE_URL;
_this.options.tileUrlSubDomains = _this.options.tileUrlSubDomains || consts_1.DEFAULT_TILE_URL_SUB_DOMAINS;
_this.options.crawlDelay = _this.options.crawlDelay || consts_1.DEFAULT_CRAWL_DELAY;
_this.options.maxAge = _this.options.maxAge || consts_1.DEFAULT_MAX_AGE;
// Create the store if it does not exists...
var dbRequest = indexedDB.open(_this.options.databaseName, _this.options.databaseVersion);
dbRequest.addEventListener("upgradeneeded", function (dbEvent) {
/**
* Fired event from IndexedDB to give the possibility to enhance something on the store
* @event IndexedDbTileCache#upgradeneeded
*/
_this.emit("upgradeneeded", dbEvent);
var database = dbEvent.target.result;
database.createObjectStore(_this.options.objectStoreName, { keyPath: "url" });
});
dbRequest.addEventListener("error", function (dbEvent) {
/**
* Piping the error event
* @event IndexedDbTileCache#upgradeneeded
*/
_this.emit("error", dbEvent.target.error);
});
return _this;
}
/**
* Get the internal tile entry from the database with all its additional meta information.
*
* If the tile is marked as outdated by the `IIndexedDbTileCacheOptions.maxAge` property, it tries to download it
* again. On any error it will provide the cached version.
*
* If you pass `true` as parameter for the `downloadIfUnavaiable` argument, it tries to dowenload a tile if it is
* not stored already.
*/
IndexedDbTileCache.prototype.getTileEntry = function (tileCoordinates, downloadIfUnavaiable) {
var _this = this;
var dbRequest = indexedDB.open(this.options.databaseName, this.options.databaseVersion);
return new Promise(function (resolve, reject) {
dbRequest.addEventListener("success", function (dbEvent) {
var database = dbEvent.target.result;
var tx = database.transaction([_this.options.objectStoreName])
.objectStore(_this.options.objectStoreName).get(_this.createInternalTileUrl(tileCoordinates));
tx.addEventListener("success", function (event) {
if (!event.target.result) {
if (downloadIfUnavaiable) {
return _this.downloadTile(tileCoordinates).then(resolve, reject);
}
return reject(new Error("Unable to find entry"));
}
var tileEntry = event.target.result;
// Make a buffer from UInt8Array to get additional methods
if (!(tileEntry.data instanceof buffer_1.Buffer)) {
tileEntry.data = new buffer_1.Buffer(tileEntry.data);
}
if (tileEntry.timestamp < Date.now() - _this.options.maxAge) {
return _this.downloadTile(tileCoordinates).catch(function () {
// Not available so keep cached version...
return resolve(tileEntry);
}).then(resolve);
}
resolve(tileEntry);
});
tx.addEventListener("error", function (event) {
_this.emit("error", dbEvent.target.error);
reject(event.target.error);
});
});
dbRequest.addEventListener("error", function (dbEvent) {
_this.emit("error", dbEvent.target.error);
reject(dbEvent.target.error);
});
});
};
/**
* Creates an internal tile url from the url template from IIndexedDbTileCacheOptions
*
* It keeps the sub-domain placeholder to provide unique database entries while seeding from multiple sub-domains.
*/
IndexedDbTileCache.prototype.createInternalTileUrl = function (tileCoordinates) {
return this.options.tileUrl
.split(/{x}/).join(tileCoordinates.x.toString())
.split(/{y}/).join(tileCoordinates.y.toString())
.split(/{z}/).join(tileCoordinates.z.toString());
};
/**
* Creates a real tile url from the url template from IIndexedDbTileCacheOptions
*/
IndexedDbTileCache.prototype.createTileUrl = function (tileCoordinates) {
var randomSubDomain = this.options
.tileUrlSubDomains[Math.floor(Math.random() * this.options.tileUrlSubDomains.length)];
return this.createInternalTileUrl(tileCoordinates)
.split(/{s}/).join(randomSubDomain);
};
/**
* Receive a tile as an Uint8Array / Buffer
*/
IndexedDbTileCache.prototype.getTileAsBuffer = function (tileCoordinates) {
return this.getTileEntry(tileCoordinates, true).then(function (tileEntry) {
return Promise.resolve(tileEntry.data);
});
};
/**
* Receives a tile as its base64 encoded data url.
*/
IndexedDbTileCache.prototype.getTileAsDataUrl = function (tileCoordinates) {
return this.getTileEntry(tileCoordinates, true).then(function (tileEntry) {
return Promise.resolve("data:" + tileEntry.contentType + ";base64," + tileEntry.data.toString("base64"));
});
};
/**
* Download a specific tile by its coordinates and store it within the indexed-db
*/
IndexedDbTileCache.prototype.downloadTile = function (tileCoordinates) {
var _this = this;
var buffers = [];
return new Promise(function (resolve, reject) {
var contentType = "";
request.get(_this.createTileUrl(tileCoordinates))
.on("data", function (chunk) {
buffers.push(chunk);
})
.on("response", function (response) {
contentType = response.headers["content-type"];
})
.on("error", reject)
.on("end", function () {
var dbRequest = indexedDB.open(_this.options.databaseName, _this.options.databaseVersion);
var tileCacheEntry = {
contentType: contentType,
data: buffer_1.Buffer.concat(buffers),
timestamp: Date.now(),
url: _this.createInternalTileUrl(tileCoordinates),
};
dbRequest.addEventListener("success", function (dbEvent) {
var database = dbEvent.target.result;
var tx = database.transaction([_this.options.objectStoreName], "readwrite")
.objectStore(_this.options.objectStoreName).put(tileCacheEntry);
tx.addEventListener("success", function () {
resolve(tileCacheEntry);
});
tx.addEventListener("error", function (event) {
_this.emit("error", event.target.error);
reject(event.target.error);
});
});
dbRequest.addEventListener("error", function (dbEvent) {
_this.emit("error", dbEvent.target.error);
reject(dbEvent.target.error);
});
});
});
};
/**
* Seeds an area of tiles by the given bounding box, the maximal z value and the optional minimal z value.
*
* The returned number in the promise is equal to the duration of the operation in milliseconds.
*/
IndexedDbTileCache.prototype.seedBBox = function (bbox, maxZ, minZ) {
var _this = this;
if (minZ === void 0) { minZ = 0; }
var start = Date.now();
var list = tile_utils_1.getListOfTilesInBBox(bbox, maxZ, minZ);
var total = list.length;
return new Promise(function (resolve, reject) {
var fn = function () {
/**
* @event IndexedDbTileCache#seed-progess
* @type IIndexedDbTileCacheSeedProgress
*/
_this.emit("seed-progress", { total: total, remains: list.length });
var val = list.shift();
if (val) {
_this.downloadTile(val).then(function () {
setTimeout(fn, _this.options.crawlDelay);
}, reject);
return;
}
resolve(Date.now() - start);
};
fn();
});
};
/**
* Purge the whole store
*/
IndexedDbTileCache.prototype.purgeStore = function () {
var _this = this;
var dbRequest = indexedDB.open(this.options.databaseName, this.options.databaseVersion);
return new Promise(function (resolve, reject) {
dbRequest.addEventListener("success", function (dbEvent) {
var database = dbEvent.target.result;
var tx = database.transaction([_this.options.objectStoreName], "readwrite")
.objectStore(_this.options.objectStoreName).clear();
tx.addEventListener("success", function () {
resolve(true);
});
tx.addEventListener("error", function (event) {
_this.emit("error", dbEvent.target.error);
reject(event.target.error);
});
});
dbRequest.addEventListener("error", function (dbEvent) {
_this.emit("error", dbEvent.target.error);
});
});
};
return IndexedDbTileCache;
}(events_1.EventEmitter));
exports.IndexedDbTileCache = IndexedDbTileCache;
//# sourceMappingURL=tile-cache.js.map