├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── browser-test
└── index.html
├── example
├── index.html
└── index.ts
├── karma.conf.js
├── package-lock.json
├── package.json
├── test
└── index.js
├── ts
├── cached-tile-layer.spec.ts
├── cached-tile-layer.ts
└── index.ts
├── tsconfig.json
└── tslint.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Node template
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 | *.pid.lock
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # Bower dependency directory (https://bower.io/)
29 | bower_components
30 |
31 | # node-waf configuration
32 | .lock-wscript
33 |
34 | # Compiled binary addons (http://nodejs.org/api/addons.html)
35 | build/Release
36 |
37 | # Dependency directories
38 | node_modules/
39 | jspm_packages/
40 |
41 | # Typescript v1 declaration files
42 | typings/
43 |
44 | # Optional npm cache directory
45 | .npm
46 |
47 | # Optional eslint cache
48 | .eslintcache
49 |
50 | # Optional REPL history
51 | .node_repl_history
52 |
53 | # Output of 'npm pack'
54 | *.tgz
55 |
56 | # Yarn Integrity file
57 | .yarn-integrity
58 |
59 | # dotenv environment variables file
60 | .env
61 |
62 | # Webstorm
63 | .idea/
64 | lib/
65 | typedoc/
66 |
67 | bundle.js
68 | dist*.js
69 |
70 | example/index.js
71 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Node template
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 | *.pid.lock
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # Bower dependency directory (https://bower.io/)
29 | bower_components
30 |
31 | # node-waf configuration
32 | .lock-wscript
33 |
34 | # Compiled binary addons (http://nodejs.org/api/addons.html)
35 | build/Release
36 |
37 | # Dependency directories
38 | node_modules/
39 | jspm_packages/
40 |
41 | # Typescript v1 declaration files
42 | typings/
43 |
44 | # Optional npm cache directory
45 | .npm
46 |
47 | # Optional eslint cache
48 | .eslintcache
49 |
50 | # Optional REPL history
51 | .node_repl_history
52 |
53 | # Output of 'npm pack'
54 | *.tgz
55 |
56 | # Yarn Integrity file
57 | .yarn-integrity
58 |
59 | # dotenv environment variables file
60 | .env
61 |
62 | .idea/
63 | typedoc/
64 |
65 | bundle.js
66 | dist*.js
67 |
68 | example/index.js
69 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: node_js
4 |
5 | node_js:
6 | - "6"
7 |
8 | cache:
9 | directories:
10 | - node_modules
11 |
12 | before_script:
13 | - npm install
14 | - export DISPLAY=:99.0
15 | - sh -e /etc/init.d/xvfb start
16 |
17 | script:
18 | - npm run travis-test
19 |
20 | after_success:
21 | - "cat coverage/Firefox*/lcov.info | ./node_modules/.bin/coveralls"
22 |
23 | branches:
24 | only:
25 | - develop
26 | - master
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 by Arne Schubert
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted,
4 | provided that the above copyright notice and this permission notice appear in all copies.
5 |
6 | THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
7 | WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
8 | CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
9 | CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
10 | SOFTWARE.
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # YAGA cached Tile-Layer for Leaflet
2 |
3 | [](https://travis-ci.org/yagajs/leaflet-cached-tile-layer)
4 | [](https://coveralls.io/github/yagajs/leaflet-cached-tile-layer?branch=develop)
5 | [](https://app.fossa.io/projects/git%2Bgithub.com%2Fyagajs%2Fleaflet-cached-tile-layer?ref=badge_shield)
6 |
7 | A cached tile-layer for [Leaflet](http://leafletjs.com/) realized with the browsers IndexedDB over
8 | [@yaga/indexed-db-tile-cache](https://www.npmjs.com/package/@yaga/indexed-db-tile-cache).
9 |
10 | ## How to use
11 |
12 | At first you have to install this library with `npm` or `yarn`:
13 |
14 | ```bash
15 | npm install --save @yaga/leaflet-cached-tile-layer
16 | # OR
17 | yarn install --save @yaga/leaflet-cached-tile-layer
18 | ```
19 |
20 | After that you can import this module into your application with the typical node.js or TypeScript way.
21 |
22 | *keep in mind that you have to use browserify to package the libraries from the node.js environment into your browser
23 | ones, such as `Buffer` or `request`.*
24 |
25 | ### Working with the cached Leaflet tile layer
26 |
27 | #### JavaScript
28 | ```javascript
29 | const CachedTileLayer = require('@yaga/leaflet-cached-tile-layer').CachedTileLayer;
30 | const Map = require('leaflet').Map;
31 |
32 | document.addEventListener('DOMContentLoaded', function() {
33 | const map = new Map('map').setView([51.505, -0.09], 13);
34 |
35 | const leafletCachedTileLayer = new CachedTileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
36 | attribution: '© OpenStreetMap contributors',
37 | databaseName: 'tile-cache-data', // optional
38 | databaseVersion: 1, // optional
39 | objectStoreName: 'OSM', // optional
40 | crawlDelay: 500, // optional
41 | maxAge: 1000 * 60 * 60 * 24 * 7 // optional
42 | }).addTo(map);
43 |
44 | // The layer caches itself on tile load.
45 | // You can also seed explicit with:
46 | // - `leafletCachedTileLayer.seedCurrentView();`
47 | // - `leafletCachedTileLayer.seedBBox(/* ... */);`
48 | //
49 | // or clear the cache with:
50 | // - `leafletCachedTileLayer.clearCache();`
51 | });
52 |
53 | ```
54 |
55 | #### TypeScript
56 | ```typescript
57 | import { CachedTileLayer, ICachedTileLayerSeedProgress } from "@yaga/leaflet-cached-tile-layer";
58 | import { Map } from "leaflet";
59 |
60 | document.addEventListener("DOMContentLoaded", () => {
61 | const map = new Map("map").setView([51.505, -0.09], 13);
62 |
63 | const leafletCachedTileLayer = new CachedTileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
64 | attribution: `© OpenStreetMap contributors`,
65 | databaseName: "tile-cache-data", // optional
66 | databaseVersion: 1, // optional
67 | objectStoreName: "OSM", // optional
68 | crawlDelay: 500, // optional
69 | maxAge: 1000 * 60 * 60 * 24 * 7, // optional
70 | }).addTo(map);
71 |
72 | // The layer caches itself on tile load.
73 | // You can also seed explicit with:
74 | // - `leafletCachedTileLayer.seedCurrentView();`
75 | // - `leafletCachedTileLayer.seedBBox(/* ... */);`
76 | //
77 | // or clear the cache with:
78 | // - `leafletCachedTileLayer.clearCache();`
79 | });
80 | ```
81 |
82 | *There are more methods available, for further information take a look at the API documentation or the example...*
83 |
84 | ## NPM script tasks
85 |
86 | * `npm test`: Runs the software tests with karma and leaves a coverage report under the folder `coverage`.
87 | * `npm run travis-test`: Runs the software tests optimized for the [Travis-CI](https://travis-ci.org/).
88 | * `npm run browser-test`: Prepares the tests to run directly in your browser. After running this command you have to
89 | open `browser-test/index.html` in your browser of choice.
90 | * `npm run doc`: Creates the API documentation with `typedoc` and places the documentation in the folder `typedoc`.
91 |
92 | ## Contribution
93 |
94 | Make an issue on [GitHub](https://github.com/yagajs/leaflet-cached-tile-layer/), or even better a pull request and try
95 | to fulfill the software tests.
96 |
97 | ## License
98 |
99 | This library is under [ISC License](https://spdx.org/licenses/ISC.html) © by Arne Schubert and the YAGA Development
100 | Team.
101 |
102 | [](https://app.fossa.io/projects/git%2Bgithub.com%2Fyagajs%2Fleaflet-cached-tile-layer?ref=badge_large)
--------------------------------------------------------------------------------
/browser-test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | YAGA Leaflet cached Tile-Layer | Unit-Tests
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | YAGA | leaflet-cached-tile-layer | Example
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
This example is taken from the original Leaflet example and
35 | is enhanced with the cached tile layer of
36 | the YAGA Development Team.
37 |
38 |
39 |
--------------------------------------------------------------------------------
/example/index.ts:
--------------------------------------------------------------------------------
1 | import { Map } from "leaflet";
2 | import { marker } from "leaflet";
3 | import { CachedTileLayer, ICachedTileLayerSeedProgress } from "../lib";
4 |
5 | document.addEventListener("DOMContentLoaded", () => {
6 | (window as any).map = new Map("map").setView([51.505, -0.09], 13);
7 |
8 | (window as any).leafletCachedTileLayer = new CachedTileLayer("http://{s}.tile.osm.org/{z}/{x}/{y}.png", {
9 | attribution: `© OpenStreetMap contributors`,
10 | }).addTo((window as any).map);
11 |
12 | marker([51.5, -0.09]).addTo((window as any).map)
13 | .bindPopup("A pretty CSS3 popup.
Easily customizable.")
14 | .openPopup();
15 |
16 | const seedButton = document.createElement("button");
17 | seedButton.setAttribute("class", "btn btn-primary");
18 | seedButton.appendChild(document.createTextNode("Seed current view"));
19 | seedButton.addEventListener("click", () => {
20 | const progressDiv = document.createElement("div");
21 | progressDiv.setAttribute("class", "progress");
22 |
23 | const progressBarDiv = document.createElement("div");
24 | progressBarDiv.setAttribute("class", "progress-bar progress-bar-success");
25 | progressBarDiv.setAttribute("style", "width: 0%;");
26 | progressDiv.appendChild(progressBarDiv);
27 | document.getElementById("progress-wrapper").appendChild(progressDiv);
28 |
29 | (window as any).leafletCachedTileLayer.seedCurrentView(
30 | undefined,
31 | undefined,
32 | (progress: ICachedTileLayerSeedProgress) => {
33 | const percentage = Math.ceil(((progress.total - progress.remains) / progress.total) * 100);
34 | progressBarDiv.setAttribute("style", `width: ${ percentage }%;`);
35 | if (progress.remains === 0) {
36 | progressBarDiv.appendChild(document.createTextNode("Done..."));
37 | setTimeout(() => {
38 | progressDiv.parentNode.removeChild(progressDiv);
39 | }, 3000);
40 | }
41 | },
42 | );
43 | });
44 |
45 | const purgeButton = document.createElement("button");
46 | purgeButton.setAttribute("class", "btn btn-danger");
47 | purgeButton.appendChild(document.createTextNode("Purge cache"));
48 | purgeButton.addEventListener("click", () => {
49 | (window as any).leafletCachedTileLayer.clearCache();
50 | });
51 |
52 | document.getElementById("button-wrapper").appendChild(seedButton);
53 | document.getElementById("button-wrapper").appendChild(purgeButton);
54 | });
55 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Sun Aug 06 2017 22:57:04 GMT+0200 (CEST)
3 |
4 | module.exports = function(config) {
5 | config.set({
6 |
7 | // base path that will be used to resolve all patterns (eg. files, exclude)
8 | basePath: '',
9 |
10 |
11 | // frameworks to use
12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
13 | frameworks: ['mocha'],
14 |
15 |
16 | // list of files / patterns to load in the browser
17 | files: [
18 | 'test/bundle.js',
19 | 'test/*.png'
20 | // 'test-tmp/*.js'
21 | ],
22 |
23 |
24 | // list of files to exclude
25 | exclude: [
26 | ],
27 |
28 |
29 | // preprocess matching files before serving them to the browser
30 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
31 | preprocessors: {
32 | },
33 |
34 |
35 | // optionally, configure the reporter
36 | coverageReporter: {
37 | reporters: [
38 | {
39 | type : 'html',
40 | dir : 'coverage/'
41 | },
42 | {
43 | type : 'lcov',
44 | dir : 'coverage/'
45 | }
46 | ]
47 | },
48 | // test results reporter to use
49 | // possible values: 'dots', 'progress'
50 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
51 | reporters: ['progress', 'coverage'],
52 |
53 |
54 | // web server port
55 | port: 9876,
56 |
57 |
58 | // enable / disable colors in the output (reporters and logs)
59 | colors: true,
60 |
61 |
62 | // level of logging
63 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
64 | logLevel: config.LOG_INFO,
65 |
66 |
67 | // enable / disable watching file and executing tests whenever any file changes
68 | autoWatch: true,
69 |
70 |
71 | // start these browsers
72 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
73 | browsers: ['Chrome', 'Firefox'],
74 |
75 |
76 | // Continuous Integration mode
77 | // if true, Karma captures browsers, runs the tests and exits
78 | singleRun: true,
79 |
80 | // Concurrency level
81 | // how many browser should be started simultaneous
82 | concurrency: Infinity
83 | })
84 | };
85 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@yaga/leaflet-cached-tile-layer",
3 | "version": "1.0.0",
4 | "description": "A leaflet tile layer cached with @yaga/indexed-db-tile-cache",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "test": "tslint ts/*.ts && tsc && istanbul instrument lib -o test-tmp && browserify test/index.js -o test/bundle.js && karma start karma.conf.js && rm -Rf test-tmp test/bundle.js",
8 | "travis-test": "tslint ts/*.ts && tsc && istanbul instrument lib -o test-tmp && browserify test/index.js -o test/bundle.js && karma start karma.conf.js --browsers Firefox && rm -Rf test-tmp test/bundle.js",
9 | "browser-test": "tsc; cp -R lib test-tmp && browserify test/index.js -o browser-test/bundle.js && rm -Rf test-tmp",
10 | "doc": "typedoc --out ./typedoc/ --exclude ts/tile-layer.directive.spec.ts --mode file ts/",
11 | "build-example": "tsc && cd example && tsc index.ts && cd .. && browserify example/index.js -o example/bundle.js"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/yagajs/leaflet-cached-tile-layer.git"
16 | },
17 | "directories": {
18 | "lib": "lib",
19 | "test": "test",
20 | "doc": "typedoc",
21 | "typescript": "ts"
22 | },
23 | "keywords": [
24 | "spatial",
25 | "tile",
26 | "cache",
27 | "store",
28 | "storage",
29 | "browser",
30 | "indexed-db"
31 | ],
32 | "devDependencies": {
33 | "@types/chai": "^4.1.2",
34 | "@types/mocha": "^2.2.48",
35 | "bootstrap": "^3.3.7",
36 | "browserify": "^14.5.0",
37 | "chai": "^4.1.2",
38 | "font-awesome": "^4.7.0",
39 | "istanbul": "^0.4.5",
40 | "karma": "^1.7.1",
41 | "karma-chrome-launcher": "^2.2.0",
42 | "karma-coverage": "^1.1.1",
43 | "karma-firefox-launcher": "^1.1.0",
44 | "karma-mocha": "^1.3.0",
45 | "karma-safari-launcher": "^1.0.0",
46 | "mocha": "^3.5.3",
47 | "tslint": "^5.9.1",
48 | "typedoc": "^0.8.0",
49 | "typescript": "^2.7.2",
50 | "uglify-js": "^3.3.12"
51 | },
52 | "author": "Arne Schubert ",
53 | "license": "ISC",
54 | "dependencies": {
55 | "@types/leaflet": "^1.2.5",
56 | "@yaga/indexed-db-tile-cache": "^1.0.0",
57 | "@yaga/tile-utils": "^1.0.0",
58 | "leaflet": "^1.3.1"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | require('../test-tmp/cached-tile-layer.spec');
2 |
3 |
--------------------------------------------------------------------------------
/ts/cached-tile-layer.spec.ts:
--------------------------------------------------------------------------------
1 | import { IndexedDbTileCache } from "@yaga/indexed-db-tile-cache";
2 | import { IBBox } from "@yaga/tile-utils";
3 | import { expect } from "chai";
4 | import { LatLngBounds } from "leaflet";
5 | import { CachedTileLayer, ICachedTileLayerOptions } from "./index";
6 |
7 | const TEST_URL_TEMPLATE: string = "http://{s}.example.com/{z}/{x}/{y}.png";
8 | const TRANSPARENT_PIXEL: string = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42m" +
9 | "NkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
10 |
11 | /* tslint:disable:no-empty */
12 | /* istanbul ignore next */
13 | function noop() {}
14 | /* tslint:enable */
15 |
16 | describe("CachedTileLayer", () => {
17 | describe(".createTile", () => {
18 | it("should return an HTML image tag", () => {
19 | const cachedTileLayer = new CachedTileLayer(TEST_URL_TEMPLATE);
20 | cachedTileLayer.instantiateIndexedDbTileCache = () => {
21 | return {
22 | getTileAsDataUrl: () => Promise.resolve(TRANSPARENT_PIXEL),
23 | };
24 | };
25 | expect(cachedTileLayer.createTile({x: 1, y: 2}, noop)).to.be.an.instanceOf(HTMLElement);
26 | });
27 | it("should change the source of the HTML image tag", (done) => {
28 | const cachedTileLayer = new CachedTileLayer(TEST_URL_TEMPLATE);
29 | cachedTileLayer.instantiateIndexedDbTileCache = () => {
30 | return {
31 | getTileAsDataUrl: () => {
32 | return Promise.resolve(TRANSPARENT_PIXEL);
33 | },
34 | };
35 | };
36 | const createdTile: HTMLImageElement = cachedTileLayer.createTile({x: 1, y: 2}, noop) as HTMLImageElement;
37 | setTimeout(() => {
38 | expect(createdTile.src).to.equal(TRANSPARENT_PIXEL);
39 | done();
40 | }, 10);
41 |
42 | });
43 | it("should give an error tile", (done) => {
44 | const cachedTileLayer = new CachedTileLayer(TEST_URL_TEMPLATE, {
45 | errorTileUrl: TRANSPARENT_PIXEL,
46 | });
47 | cachedTileLayer.instantiateIndexedDbTileCache = () => {
48 | return {
49 | getTileAsDataUrl: () => {
50 | return Promise.reject(new Error("No further reason... Just for testing..."));
51 | },
52 | };
53 | };
54 | const createdTile: HTMLImageElement = cachedTileLayer.createTile({x: 1, y: 2}, noop) as HTMLImageElement;
55 | setTimeout(() => {
56 | expect(createdTile.src).to.equal(TRANSPARENT_PIXEL);
57 | done();
58 | }, 10);
59 |
60 | });
61 | it("should support the cross-origin event if there is no need for", () => {
62 | const cachedTileLayer = new CachedTileLayer(TEST_URL_TEMPLATE, {
63 | crossOrigin: true,
64 | });
65 | cachedTileLayer.instantiateIndexedDbTileCache = () => {
66 | return {
67 | getTileAsDataUrl: () => Promise.resolve(TRANSPARENT_PIXEL),
68 | };
69 | };
70 | expect((cachedTileLayer.createTile({x: 1, y: 2}, noop) as HTMLImageElement).crossOrigin)
71 | .to.equal("anonymous");
72 | });
73 |
74 | });
75 | describe(".instantiateIndexedDbTileCache", () => {
76 | it("should have the right url template even when calling without options", () => {
77 | const tileCacheApi: IndexedDbTileCache = new CachedTileLayer(TEST_URL_TEMPLATE)
78 | .instantiateIndexedDbTileCache();
79 | expect(tileCacheApi).to.be.an.instanceOf(IndexedDbTileCache);
80 | expect(tileCacheApi.options.tileUrl).to.equal(TEST_URL_TEMPLATE);
81 | });
82 | it("should return an instance of IndexedDbTileCache with specific options", () => {
83 | const layerOptions: ICachedTileLayerOptions = {
84 | crawlDelay: 1234,
85 | databaseName: "test-db",
86 | databaseVersion: 1,
87 | errorTileUrl: "error.tile",
88 | maxAge: 54321,
89 | objectStoreName: "test-os",
90 | subdomains: ["z", "x", "y"],
91 | };
92 | const tileCacheApi: IndexedDbTileCache = new CachedTileLayer(TEST_URL_TEMPLATE, layerOptions)
93 | .instantiateIndexedDbTileCache();
94 | expect(tileCacheApi).to.be.an.instanceOf(IndexedDbTileCache);
95 | expect(tileCacheApi.options.databaseName).to.equal(layerOptions.databaseName);
96 | expect(tileCacheApi.options.databaseVersion).to.equal(layerOptions.databaseVersion);
97 | expect(tileCacheApi.options.objectStoreName).to.equal(layerOptions.objectStoreName);
98 | expect(tileCacheApi.options.tileUrl).to.equal(TEST_URL_TEMPLATE);
99 | expect(tileCacheApi.options.tileUrlSubDomains).to.equal(layerOptions.subdomains);
100 | expect(tileCacheApi.options.crawlDelay).to.equal(layerOptions.crawlDelay);
101 | expect(tileCacheApi.options.maxAge).to.equal(layerOptions.maxAge);
102 | });
103 | });
104 | describe(".seedBBox", () => {
105 | it("should call the seedBBox of the IndexedDbTileCache instance", (done) => {
106 | const cachedTileLayer = new CachedTileLayer(TEST_URL_TEMPLATE);
107 | const testBBox: IBBox = {
108 | maxLat: 1,
109 | maxLng: 1,
110 | minLat: -1,
111 | minLng: -1,
112 | };
113 | const testLeafletBounds: LatLngBounds = new LatLngBounds([-1, -1], [1, 1]);
114 | cachedTileLayer.instantiateIndexedDbTileCache = () => {
115 | return {
116 | seedBBox: (bbox: IBBox, maxZ: number, minZ: number) => {
117 | expect(bbox).to.deep.equal(testBBox);
118 | expect(maxZ).to.equal(20);
119 | expect(minZ).to.equal(10);
120 | done();
121 | },
122 | };
123 | };
124 | cachedTileLayer.seedBBox(testLeafletBounds, 20, 10);
125 | });
126 | it("should call the seedBBox of the IndexedDbTileCache instance with current zoom", (done) => {
127 | const cachedTileLayer = new CachedTileLayer(TEST_URL_TEMPLATE);
128 | const testBBox: IBBox = {
129 | maxLat: 1,
130 | maxLng: 1,
131 | minLat: -1,
132 | minLng: -1,
133 | };
134 | const testLeafletBounds: LatLngBounds = new LatLngBounds([-1, -1], [1, 1]);
135 |
136 | (cachedTileLayer as any)._map = {
137 | getZoom: () => 11,
138 | };
139 |
140 | cachedTileLayer.instantiateIndexedDbTileCache = () => {
141 | return {
142 | seedBBox: (bbox: IBBox, maxZ: number, minZ: number) => {
143 | expect(bbox).to.deep.equal(testBBox);
144 | expect(maxZ).to.equal(11);
145 | expect(minZ).to.equal(0);
146 | done();
147 | },
148 | };
149 | };
150 | cachedTileLayer.seedBBox(testLeafletBounds);
151 | });
152 | it("should call the callback when event emitter fires", (done) => {
153 | const cachedTileLayer = new CachedTileLayer(TEST_URL_TEMPLATE);
154 | const testLeafletBounds: LatLngBounds = new LatLngBounds([-1, -1], [1, 1]);
155 |
156 | (cachedTileLayer as any)._map = {
157 | getZoom: () => 11,
158 | };
159 |
160 | cachedTileLayer.instantiateIndexedDbTileCache = () => {
161 | return {
162 | on: (name: string, cb: () => void) => {
163 | expect(name).to.equal("seed-progress");
164 | expect(cb).to.equal(noop);
165 | done();
166 | },
167 | seedBBox: noop,
168 | };
169 | };
170 | cachedTileLayer.seedBBox(testLeafletBounds, undefined, undefined, noop);
171 | });
172 | });
173 | describe(".seedCurrentView", () => {
174 | it("should call the seedBBox of the IndexedDbTileCache instance with the current bounding box", (done) => {
175 | const cachedTileLayer = new CachedTileLayer(TEST_URL_TEMPLATE);
176 | (cachedTileLayer as any)._map = {
177 | getBounds: () => (new LatLngBounds([1, 2], [4, 3])),
178 | };
179 | cachedTileLayer.instantiateIndexedDbTileCache = () => {
180 | return {
181 | seedBBox: (bbox: IBBox, maxZ: number, minZ: number) => {
182 | expect(bbox.maxLat).to.equal(4);
183 | expect(bbox.maxLng).to.equal(3);
184 | expect(bbox.minLat).to.equal(1);
185 | expect(bbox.minLng).to.equal(2);
186 | expect(maxZ).to.equal(20);
187 | expect(minZ).to.equal(10);
188 | done();
189 | },
190 | };
191 | };
192 | cachedTileLayer.seedCurrentView(20, 10);
193 | });
194 | it(
195 | "should call the seedBBox of the IndexedDbTileCache instance with the current bounding box and zoom level",
196 | (done) => {
197 | const cachedTileLayer = new CachedTileLayer(TEST_URL_TEMPLATE);
198 | (cachedTileLayer as any)._map = {
199 | getBounds: () => (new LatLngBounds([1, 2], [4, 3])),
200 | getZoom: () => 11,
201 | };
202 | cachedTileLayer.instantiateIndexedDbTileCache = () => {
203 | return {
204 | seedBBox: (bbox: IBBox, maxZ: number, minZ: number) => {
205 | expect(bbox.maxLat).to.equal(4);
206 | expect(bbox.maxLng).to.equal(3);
207 | expect(bbox.minLat).to.equal(1);
208 | expect(bbox.minLng).to.equal(2);
209 | expect(maxZ).to.equal(11);
210 | expect(minZ).to.equal(0);
211 | done();
212 | },
213 | };
214 | };
215 | cachedTileLayer.seedCurrentView();
216 | },
217 | );
218 | });
219 | describe(".clearCache", () => {
220 | it("should call the purgeStore of the IndexedDbTileCache instance", (done) => {
221 | const cachedTileLayer = new CachedTileLayer(TEST_URL_TEMPLATE);
222 | cachedTileLayer.instantiateIndexedDbTileCache = () => {
223 | return {
224 | purgeStore: () => {
225 | done();
226 | },
227 | };
228 | };
229 | cachedTileLayer.clearCache();
230 | });
231 | });
232 | });
233 |
--------------------------------------------------------------------------------
/ts/cached-tile-layer.ts:
--------------------------------------------------------------------------------
1 | import {
2 | IIndexedDbTileCacheSeedProgress as ICachedTileLayerSeedProgress,
3 | IndexedDbTileCache,
4 | } from "@yaga/indexed-db-tile-cache";
5 | import { DomEvent, LatLngBounds, Map, TileLayer, TileLayerOptions, Util } from "leaflet";
6 |
7 | /**
8 | * Interface for the tile layer options. It is a mixin of the original Leaflet `TileLayerOptions` and the
9 | * `IndexedDbTileCacheOptions` of the YAGA Development Team.
10 | */
11 | export interface ICachedTileLayerOptions extends TileLayerOptions {
12 | /**
13 | * Name of the database
14 | *
15 | * The default value is equal to the constance DEFAULT_DATABASE_NAME
16 | * @default "tile-cache-data"
17 | */
18 | databaseName?: string;
19 | /**
20 | * Version of the IndexedDB store. Should not be changed normally! But can provide an "upgradeneeded" event from
21 | * IndexedDB.
22 | *
23 | * The default value is equal to the constance DEFAULT_DATABASE_VERSION
24 | * @default 1
25 | */
26 | databaseVersion?: number;
27 | /**
28 | * Name of the object-store. Should correspond with the name of the tile server
29 | *
30 | * The default value is equal to the constance DEFAULT_OBJECT_STORE_NAME
31 | * @default "OSM";
32 | */
33 | objectStoreName?: string;
34 | /**
35 | * The delay in milliseconds used for not stressing the tile server while seeding.
36 | *
37 | * The default value is equal to the constance DEFAULT_CRAWL_DELAY
38 | * @default 500
39 | */
40 | crawlDelay?: number;
41 | /**
42 | * The maximum age in milliseconds of a stored tile.
43 | *
44 | * The default value is equal to the constance DEFAULT_MAX_AGE
45 | * @default 1000 * 60 * 60 * 24 * 7
46 | */
47 | maxAge?: number;
48 | }
49 |
50 | /**
51 | * Original Leaflet `TileLayer` enhanced with the `IndexedDbTileCache` of the YAGA Development Team.
52 | */
53 | export class CachedTileLayer extends TileLayer {
54 | /**
55 | * Options of Leaflets `TileLayer`enhanced with the options for the `IndexedDbTileCache`.
56 | */
57 | public options: ICachedTileLayerOptions;
58 | constructor(urlTemplate: string, options?: ICachedTileLayerOptions) {
59 | super(urlTemplate, options);
60 | }
61 |
62 | /**
63 | * Rewritten method that serves the tiles from the `IndexedDbTileCache`
64 | */
65 | public createTile(coords, done): HTMLElement {
66 | // Rewrite of the original method...
67 | const tile = document.createElement("img");
68 |
69 | DomEvent.on(tile, "load", Util.bind((this as any)._tileOnLoad, this, done, tile));
70 | DomEvent.on(tile, "error", Util.bind((this as any)._tileOnError, this, done, tile));
71 |
72 | if (this.options.crossOrigin) {
73 | tile.crossOrigin = "";
74 | }
75 |
76 | /*
77 | Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
78 | http://www.w3.org/TR/WCAG20-TECHS/H67
79 | */
80 | tile.alt = "";
81 |
82 | /*
83 | Set role="presentation" to force screen readers to ignore this
84 | https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
85 | */
86 | tile.setAttribute("role", "presentation");
87 |
88 | const tc: IndexedDbTileCache = this.instantiateIndexedDbTileCache();
89 | tc.getTileAsDataUrl({
90 | x: coords.x,
91 | y: coords.y,
92 | z: (this as any)._getZoomForUrl(),
93 | }).then((dataUrl: string) => {
94 | tile.src = dataUrl;
95 | }).catch(() => {
96 | tile.src = this.options.errorTileUrl;
97 | });
98 |
99 | return tile;
100 | }
101 |
102 | /**
103 | * Method that creates an instance of the `IndexedDbTileCache` from the options of this object.
104 | *
105 | * You can use this method to make advances operations on the tile cache.
106 | */
107 | public instantiateIndexedDbTileCache(): IndexedDbTileCache {
108 | return new IndexedDbTileCache({
109 | crawlDelay: this.options.crawlDelay,
110 | databaseName: this.options.databaseName,
111 | databaseVersion: this.options.databaseVersion,
112 | maxAge: this.options.maxAge,
113 | objectStoreName: this.options.objectStoreName,
114 | tileUrl: (this as any)._url,
115 | tileUrlSubDomains: this.options.subdomains as string[],
116 | });
117 | }
118 |
119 | /**
120 | * Seed an area with a Leaflet `LatLngBound` and the given zoom range.
121 | *
122 | * The callback will be called before starting to download a tile and once after it is finished.
123 | *
124 | * The default value for `maxZoom` is the current zoom level of the map and the default value for `minZoom` is
125 | * always `0`.
126 | */
127 | public seedBBox(
128 | bbox: LatLngBounds,
129 | maxZoom?: number,
130 | minZoom: number = 0,
131 | cb?: (progress: ICachedTileLayerSeedProgress) => void,
132 | ): Promise {
133 | if (maxZoom === undefined) {
134 | maxZoom = ((this as any)._map as Map).getZoom();
135 | }
136 | const tc: IndexedDbTileCache = this.instantiateIndexedDbTileCache();
137 | if (cb) {
138 | tc.on("seed-progress", cb);
139 | }
140 | return tc.seedBBox({
141 | maxLat: bbox.getNorth(),
142 | maxLng: bbox.getEast(),
143 | minLat: bbox.getSouth(),
144 | minLng: bbox.getWest(),
145 | }, maxZoom, minZoom);
146 | }
147 |
148 | /**
149 | * Seeds like `this.seedBBox`, but uses the current map bounds as bounding box.
150 | */
151 | public seedCurrentView(
152 | maxZoom?: number,
153 | minZoom: number = 0,
154 | cb?: (progress: ICachedTileLayerSeedProgress) => void,
155 | ): Promise {
156 | return this.seedBBox(((this as any)._map as Map).getBounds(), maxZoom, minZoom, cb);
157 | }
158 |
159 | /**
160 | * Clears the whole cache.
161 | */
162 | public clearCache(): Promise {
163 | const tc: IndexedDbTileCache = this.instantiateIndexedDbTileCache();
164 | return tc.purgeStore();
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/ts/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./cached-tile-layer";
2 |
3 | export {
4 | IIndexedDbTileCacheSeedProgress as ICachedTileLayerSeedProgress,
5 | IndexedDbTileCache,
6 | } from "@yaga/indexed-db-tile-cache";
7 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es5",
5 | "noImplicitAny": false,
6 | "sourceMap": true,
7 | "declaration": true,
8 | "outDir": "./lib",
9 | "emitDecoratorMetadata": true,
10 | "experimentalDecorators": true,
11 | "lib": ["es6", "dom", "es2015.iterable", "es2015.promise"]
12 | },
13 | "exclude": [
14 | "node_modules",
15 | "lib",
16 | "test",
17 | "coverage",
18 | "example",
19 | "browser-test"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint:recommended"
5 | ],
6 | "jsRules": {},
7 | "rules": {},
8 | "rulesDirectory": []
9 | }
--------------------------------------------------------------------------------