diff --git a/package-lock.json b/package-lock.json index f482dcb..694a295 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3176,6 +3176,11 @@ } } }, + "idb": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/idb/-/idb-4.0.5.tgz", + "integrity": "sha512-P+Fk9HT2h1DhXoE1YNK183SY+CRh2GHNh28de94sGwhe0bUA75JJeVJWt3SenE5p0BXK7maflIq29dl6UZHrFw==" + }, "ieee754": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", diff --git a/package.json b/package.json index 710bc10..7fe3569 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ ] }, "dependencies": { + "idb": "^4.0.5", "js-clipper": "^1.0.1", "leaflet": "^1.2.0" }, @@ -48,6 +49,7 @@ "babel-core": "^6.26.0", "babel-eslint": "^8.0.1", "babel-loader": "^7.1.2", + "babel-polyfill": "^6.26.0", "babel-preset-env": "^1.6.0", "babel-preset-stage-0": "^6.24.1", "css-loader": "^0.28.7", @@ -60,6 +62,7 @@ "lint-staged": "^4.2.3", "postcss-loader": "^2.0.7", "prettier": "^1.7.4", + "uglifyjs-webpack-plugin": "1.3.0", "webpack": "^3.6.0" } } diff --git a/src/OverPassLayer.js b/src/OverPassLayer.js index c217a6e..59d5bb4 100644 --- a/src/OverPassLayer.js +++ b/src/OverPassLayer.js @@ -1,5 +1,6 @@ import L from 'leaflet'; import ClipperLib from 'js-clipper'; +import { openDB } from 'idb'; import './OverPassLayer.css'; import './MinZoomIndicator'; @@ -14,6 +15,8 @@ const OverPassLayer = L.FeatureGroup.extend({ timeout: 30 * 1000, // Milliseconds retryOnTimeout: false, noInitialRequest: false, + cacheEnabled: false, + cacheTTL: 1800, // Seconds beforeRequest() {}, @@ -73,6 +76,23 @@ const OverPassLayer = L.FeatureGroup.extend({ this._ids = {}; this._loadedBounds = options.loadedBounds || []; this._requestInProgress = false; + + if (this.options.cacheEnabled) { + this._initCacheDb(); + } + }, + + async _initCacheDb() { + this._cacheDb = await openDB('leaflet-overpass-layer', 1, { + upgrade(db) { + db.createObjectStore('cache', { autoIncrement: true }); + } + }); + + const layerAddedToMap = !!this._map; + if (layerAddedToMap) { + this._loadCachedItems(); + } }, _getPoiPopupHTML(tags, id) { @@ -328,7 +348,39 @@ const OverPassLayer = L.FeatureGroup.extend({ _onRequestLoad(xhr, bounds) { if (xhr.status >= 200 && xhr.status < 400) { - this.options.onSuccess.call(this, JSON.parse(xhr.response)); + let result = JSON.parse(xhr.response); + this.options.onSuccess.call(this, result); + + const cacheDbInitialized = typeof this._cacheDb !== 'undefined'; + if (this.options.cacheEnabled && cacheDbInitialized) { + let expireDate = new Date(); + expireDate.setSeconds(expireDate.getSeconds() + this.options.cacheTTL); + + this._getCachedItems().then(cachedItems => { + let existingCacheKey = undefined; + + cachedItems.forEach((cachedItem, key) => { + const queryEquals = cachedItem.query === this.options.query; + const northEast = L.latLng(cachedItem.bounds._northEast); + const southWest = L.latLng(cachedItem.bounds._southWest); + const cachedItemBounds = L.latLngBounds(northEast, southWest); + if (queryEquals && bounds.equals(cachedItemBounds)) { + existingCacheKey = key; + } + }); + + this._cacheDb.put( + 'cache', + { + query: this.options.query, + result: result, + bounds: bounds, + expires: expireDate + }, + existingCacheKey + ); + }); + } this._onRequestLoadCallback(bounds); } else { @@ -379,6 +431,39 @@ const OverPassLayer = L.FeatureGroup.extend({ } }, + async _getCachedItems() { + const cacheDbInitialized = typeof this._cacheDb !== 'undefined'; + if (!cacheDbInitialized) return; + + const keys = await this._cacheDb.getAllKeys('cache'); + const items = await this._cacheDb.getAll('cache'); + + const itemsMap = new Map(); + items.forEach((item, i) => { + const key = keys[i]; + itemsMap.set(key, item); + }); + + return itemsMap; + }, + + async _loadCachedItems() { + const cacheDbInitialized = typeof this._cacheDb !== 'undefined'; + if (!cacheDbInitialized || !this._map) return; + + const cachedItems = await this._getCachedItems(); + cachedItems.forEach((cachedItem, key) => { + if (new Date() > cachedItem.expires) { + this._cacheDb.delete('cache', key); + } else { + if (cachedItem.query === this.options.query) { + this.options.onSuccess.call(this, cachedItem.result); + this._onRequestLoadCallback(cachedItem.bounds); + } + } + }); + }, + onAdd(map) { this._map = map; @@ -404,6 +489,13 @@ const OverPassLayer = L.FeatureGroup.extend({ this._markers = L.featureGroup().addTo(this._map); + if (this.options.cacheEnabled) { + const cacheDbInitialized = typeof this._cacheDb !== 'undefined'; + if (cacheDbInitialized) { + this._loadCachedItems(); + } + } + if (!this.options.noInitialRequest) { this._prepareRequest(); } diff --git a/webpack.config.js b/webpack.config.js index e3eca58..9eb314c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,19 +1,22 @@ const webpack = require('webpack'); const path = require('path'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const UglifyJsPlugin = require('uglifyjs-webpack-plugin') const extractCSS = new ExtractTextPlugin('OverPassLayer.css'); const plugins = [extractCSS]; plugins.push( - new webpack.optimize.UglifyJsPlugin({ - minimize: true, - mangle: true, - output: { - comments: false - }, - compress: { - warnings: false + new UglifyJsPlugin({ + uglifyOptions: { + minimize: true, + mangle: true, + output: { + comments: false + }, + compress: { + warnings: false + } } }) ); @@ -23,7 +26,7 @@ module.exports = { plugins: plugins, context: path.join(__dirname, 'src'), entry: { - OverPassLayer: './OverPassLayer' + OverPassLayer: ['babel-polyfill', './OverPassLayer'] }, output: { path: path.join(__dirname, 'dist'),