mapscii/src/TileSource.js

177 wiersze
4.1 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
termap - Terminal Map Viewer
by Michael Strassburger <codepoet@cpan.org>
Source for VectorTiles - supports
* remote TileServer
* local MBTiles and VectorTiles
*/
import fs from 'fs';
import path from 'path';
import fetch from 'node-fetch';
import envPaths from 'env-paths';
const paths = envPaths('mapscii');
import Tile from './Tile.js';
import config from './config.js';
// https://github.com/mapbox/node-mbtiles has native build dependencies (sqlite3)
// To maximize MapSCIIs compatibility, MBTiles support must be manually added via
// $> npm install -g @mapbox/mbtiles
let MBTiles = null;
try {
MBTiles = require('@mapbox/mbtiles');
} catch (err) {void 0;}
const modes = {
MBTiles: 1,
VectorTile: 2,
HTTP: 3,
};
export default 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) {
throw new Error('MBTiles support must be installed with following command: \'npm install -g @mapbox/mbtiles\'');
}
this.mode = modes.MBTiles;
this.loadMBTiles(source);
} else {
throw new Error('source type isn\'t supported yet');
}
}
loadMBTiles(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) {
throw new Error('no TileSource defined');
}
const cached = this.cache[[z, x, y].join('-')];
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')
.then((res) => res.arrayBuffer())
.then((arrayBuffer) => {
const buffer = Buffer.from(arrayBuffer);
if (config.persistDownloadedTiles) {
this._persistTile(z, x, y, buffer);
return buffer;
}
});
}
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(paths.cache);
} catch (error) {
config.persistDownloadedTiles = false;
}
}
_persistTile(z, x, y, buffer) {
const zoom = z.toString();
this._createFolder(path.join(paths.cache, zoom));
const filePath = path.join(paths.cache, zoom, `${x}-${y}.pbf`);
return fs.writeFile(filePath, buffer, () => null);
}
_getPersited(z, x, y) {
try {
return fs.readFileSync(path.join(paths.cache, z.toString(), `${x}-${y}.pbf`));
} catch (error) {
return false;
}
}
_createFolder(path) {
try {
fs.mkdirSync(path);
return true;
} catch (error) {
if (error.code === 'EEXIST') return true;
throw error;
}
}
}