mapscii/src/TileSource.js

177 wiersze
4.1 KiB
JavaScript
Czysty Zwykły widok Historia

2017-12-21 10:18:34 +00:00
/*
termap - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
Source for VectorTiles - supports
* remote TileServer
* local MBTiles and VectorTiles
*/
'use strict';
const userhome = require('userhome');
const fetch = require('node-fetch');
const fs = require('fs');
const Tile = require('./Tile');
const config = require('./config');
// https://github.com/mapbox/node-mbtiles has native build dependencies (sqlite3)
// To maximize mapscii's compatibility, MBTiles support must be manually added via
// $> npm install -g mbtiles
let MBTiles = null;
try {
MBTiles = require('mbtiles');
2017-12-23 08:07:23 +00:00
} catch (err) {void 0;}
2017-12-21 10:18:34 +00:00
const modes = {
MBTiles: 1,
VectorTile: 2,
HTTP: 3,
};
class TileSource {
init(source) {
this.source = source;
this.cache = {};
this.cacheSize = 16;
this.cached = [];
this.mode = null;
this.mbtiles = null;
this.styler = null;
if (this.source.startsWith('http')) {
if (config.persistDownloadedTiles) {
this._initPersistence();
}
this.mode = modes.HTTP;
} else if (this.source.endsWith('.mbtiles')) {
if (!MBTiles) {
2017-12-23 08:07:23 +00:00
throw new Error('MBTiles support must be installed with following command: \'npm install -g mbtiles\'');
2017-12-21 10:18:34 +00:00
}
this.mode = modes.MBTiles;
this.loadMBtils(source);
} else {
2017-12-23 08:07:23 +00:00
throw new Error('source type isn\'t supported yet');
2017-12-21 10:18:34 +00:00
}
}
loadMBtils(source) {
return new Promise((resolve, reject) => {
new MBTiles(source, (err, mbtiles) => {
if (err) {
reject(err);
}
this.mbtiles = mbtiles;
resolve();
});
});
}
useStyler(styler) {
this.styler = styler;
}
getTile(z, x, y) {
if (!this.mode) {
2017-12-23 08:07:23 +00:00
throw new Error('no TileSource defined');
2017-12-21 10:18:34 +00:00
}
2017-12-23 08:07:23 +00:00
const cached = this.cache[[z, x, y].join('-')];
2017-12-21 10:18:34 +00:00
if (cached) {
return Promise.resolve(cached);
}
if (this.cached.length > this.cacheSize) {
const overflow = Math.abs(this.cacheSize - this.cache.length);
for (const tile in this.cached.splice(0, overflow)) {
delete this.cache[tile];
}
}
switch (this.mode) {
case modes.MBTiles:
return this._getMBTile(z, x, y);
case modes.HTTP:
return this._getHTTP(z, x, y);
}
}
_getHTTP(z, x, y) {
let promise;
const persistedTile = this._getPersited(z, x, y);
if (config.persistDownloadedTiles && persistedTile) {
promise = Promise.resolve(persistedTile);
} else {
promise = fetch(this.source + [z,x,y].join('/') + '.pbf')
2017-12-23 08:07:23 +00:00
.then((res) => res.buffer())
.then((buffer) => {
if (config.persistDownloadedTiles) {
this._persistTile(z, x, y, buffer);
return buffer;
}
});
2017-12-21 10:18:34 +00:00
}
return promise.then((buffer) => {
return this._createTile(z, x, y, buffer);
});
}
_getMBTile(z, x, y) {
return new Promise((resolve, reject) => {
this.mbtiles.getTile(z, x, y, (err, buffer) => {
if (err) {
reject(err);
}
resolve(this._createTile(z, x, y, buffer));
});
});
}
_createTile(z, x, y, buffer) {
const name = [z, x, y].join('-');
this.cached.push(name);
const tile = this.cache[name] = new Tile(this.styler);
return tile.load(buffer);
}
_initPersistence() {
try {
this._createFolder(userhome('.mapscii'));
this._createFolder(userhome('.mapscii', 'cache'));
} catch (error) {
config.persistDownloadedTiles = false;
}
}
_persistTile(z, x, y, buffer) {
const zoom = z.toString();
this._createFolder(userhome('.mapscii', 'cache', zoom));
const filePath = userhome('.mapscii', 'cache', zoom, `${x}-${y}.pbf`);
return fs.writeFile(filePath, buffer, () => null);
}
_getPersited(z, x, y) {
try {
return fs.readFileSync(userhome('.mapscii', 'cache', z.toString(), `${x}-${y}.pbf`));
} catch (error) {
return false;
}
}
_createFolder(path) {
try {
fs.mkdirSync(path);
return true;
} catch (error) {
2017-12-23 08:07:23 +00:00
return error.code === 'EEXIST';
2017-12-21 10:18:34 +00:00
}
}
}
module.exports = TileSource;