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
|
|
|
|
|
*/
|
2022-04-29 20:08:49 +00:00
|
|
|
|
import fs from 'fs';
|
|
|
|
|
import path from 'path';
|
|
|
|
|
import fetch from 'node-fetch';
|
|
|
|
|
import envPaths from 'env-paths';
|
2020-08-17 10:02:07 +00:00
|
|
|
|
const paths = envPaths('mapscii');
|
2017-12-21 10:18:34 +00:00
|
|
|
|
|
2022-04-29 20:08:49 +00:00
|
|
|
|
import Tile from './Tile.js';
|
|
|
|
|
import config from './config.js';
|
2017-12-21 10:18:34 +00:00
|
|
|
|
|
|
|
|
|
// https://github.com/mapbox/node-mbtiles has native build dependencies (sqlite3)
|
2018-11-19 02:47:24 +00:00
|
|
|
|
// To maximize MapSCII’s compatibility, MBTiles support must be manually added via
|
2018-11-19 08:58:09 +00:00
|
|
|
|
// $> npm install -g @mapbox/mbtiles
|
2017-12-21 10:18:34 +00:00
|
|
|
|
let MBTiles = null;
|
|
|
|
|
try {
|
2018-11-19 08:58:09 +00:00
|
|
|
|
MBTiles = require('@mapbox/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,
|
|
|
|
|
};
|
|
|
|
|
|
2022-04-29 20:08:49 +00:00
|
|
|
|
export default class TileSource {
|
2017-12-21 10:18:34 +00:00
|
|
|
|
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) {
|
2018-11-19 08:58:09 +00:00
|
|
|
|
throw new Error('MBTiles support must be installed with following command: \'npm install -g @mapbox/mbtiles\'');
|
2017-12-21 10:18:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.mode = modes.MBTiles;
|
2018-11-19 08:29:22 +00:00
|
|
|
|
this.loadMBTiles(source);
|
2017-12-21 10:18:34 +00:00
|
|
|
|
} 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
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-19 08:29:22 +00:00
|
|
|
|
loadMBTiles(source) {
|
2017-12-21 10:18:34 +00:00
|
|
|
|
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')
|
2022-04-29 20:08:49 +00:00
|
|
|
|
.then((res) => res.arrayBuffer())
|
|
|
|
|
.then((arrayBuffer) => {
|
|
|
|
|
const buffer = Buffer.from(arrayBuffer);
|
2017-12-23 08:07:23 +00:00
|
|
|
|
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 {
|
2020-08-17 10:02:07 +00:00
|
|
|
|
this._createFolder(paths.cache);
|
2017-12-21 10:18:34 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
config.persistDownloadedTiles = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_persistTile(z, x, y, buffer) {
|
|
|
|
|
const zoom = z.toString();
|
2020-08-17 10:02:07 +00:00
|
|
|
|
this._createFolder(path.join(paths.cache, zoom));
|
|
|
|
|
const filePath = path.join(paths.cache, zoom, `${x}-${y}.pbf`);
|
2017-12-21 10:18:34 +00:00
|
|
|
|
return fs.writeFile(filePath, buffer, () => null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_getPersited(z, x, y) {
|
|
|
|
|
try {
|
2020-08-17 10:02:07 +00:00
|
|
|
|
return fs.readFileSync(path.join(paths.cache, z.toString(), `${x}-${y}.pbf`));
|
2017-12-21 10:18:34 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_createFolder(path) {
|
|
|
|
|
try {
|
|
|
|
|
fs.mkdirSync(path);
|
|
|
|
|
return true;
|
|
|
|
|
} catch (error) {
|
2019-03-25 00:59:46 +00:00
|
|
|
|
if (error.code === 'EEXIST') return true;
|
2018-11-19 05:34:07 +00:00
|
|
|
|
throw error;
|
2017-12-21 10:18:34 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|