diff --git a/src/TileSource.coffee b/src/TileSource.coffee deleted file mode 100644 index dd0e100..0000000 --- a/src/TileSource.coffee +++ /dev/null @@ -1,133 +0,0 @@ -### - termap - Terminal Map Viewer - by Michael Strassburger - - Source for VectorTiles - supports - * remote TileServer - * local MBTiles and VectorTiles -### - -Promise = require 'bluebird' -userhome = require 'userhome' -fetch = require 'node-fetch' -fs = require 'fs' - -Tile = require './Tile' -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 -MBTiles = try - require 'mbtiles' -catch - null - -module.exports = class TileSource - cache: {} - cacheSize: 16 - cached: [] - - modes: - MBTiles: 1 - VectorTile: 2 - HTTP: 3 - - mode: null - mbtiles: null - styler: null - - init: (@source) -> - if @source.startsWith "http" - @_initPersistence() if config.persistDownloadedTiles - - @mode = @modes.HTTP - - else if @source.endsWith ".mbtiles" - unless MBTiles - throw new Error "MBTiles support must be installed with following command: 'npm install -g mbtiles'" - - @mode = @modes.MBTiles - @loadMBtils source - - else - throw new Error "source type isn't supported yet" - - loadMBtils: (source) -> - new Promise (resolve, reject) => - new MBTiles source, (err, @mbtiles) => - if err then reject err - else resolve() - - useStyler: (@styler) -> - - getTile: (z, x, y) -> - unless @mode - throw new Error "no TileSource defined" - - z = Math.max 0, Math.floor z - - if cached = @cache[[z,x,y].join("-")] - return Promise.resolve cached - - if @cached.length > @cacheSize - for tile in @cached.splice 0, Math.abs(@cacheSize-@cached.length) - delete @cache[tile] - - switch @mode - when @modes.MBTiles then @_getMBTile z, x, y - when @modes.HTTP then @_getHTTP z, x, y - - _getHTTP: (z, x, y) -> - promise = - if config.persistDownloadedTiles and tile = @_getPersited z, x, y - Promise.resolve tile - else - fetch @source+[z,x,y].join("/")+".pbf" - .then (res) => res.buffer() - .then (buffer) => - @_persistTile z, x, y, buffer if config.persistDownloadedTiles - buffer - - promise - .then (buffer) => - @_createTile z, x, y, buffer - - _getMBTile: (z, x, y) -> - new Promise (resolve, reject) => - @mbtiles.getTile z, x, y, (err, buffer) => - return reject err if err - resolve @_createTile z, x, y, buffer - - _createTile: (z, x, y, buffer) -> - name = [z,x,y].join("-") - @cached.push name - - tile = @cache[name] = new Tile @styler - tile.load buffer - - _initPersistence: -> - try - @_createFolder userhome ".mapscii" - @_createFolder userhome ".mapscii", "cache" - catch error - config.persistDownloadedTiles = false - return - - _persistTile: (z, x, y, buffer) -> - zoom = z.toString() - @_createFolder userhome ".mapscii", "cache", zoom - fs.writeFile userhome(".mapscii", "cache", zoom, "#{x}-#{y}.pbf"), buffer, -> null - - _getPersited: (z, x, y) -> - try - fs.readFileSync userhome ".mapscii", "cache", z.toString(), "#{x}-#{y}.pbf" - catch error - false - - _createFolder: (path) -> - try - fs.mkdirSync path - true - catch e - e.code is "EEXIST" diff --git a/src/TileSource.js b/src/TileSource.js new file mode 100644 index 0000000..314ce49 --- /dev/null +++ b/src/TileSource.js @@ -0,0 +1,180 @@ +/* + termap - Terminal Map Viewer + by Michael Strassburger + + 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'); +} catch (err) {} + +const cache = {}; +const cacheSize = 16; +const cached = []; + +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) { + throw new Error("MBTiles support must be installed with following command: 'npm install -g mbtiles'"); + } + + this.mode = modes.MBTiles; + this.loadMBtils(source); + } else { + throw new Error("source type isn't supported yet"); + } + } + + 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) { + 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.buffer()) + .then((buffer) => { + 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(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) { + return error.code === "EEXIST"; + } + } +} + +module.exports = TileSource;