diff --git a/main.js b/main.js index e88c0f7..ac467cd 100644 --- a/main.js +++ b/main.js @@ -15,9 +15,4 @@ const Termap = require(__dirname+'/src/Termap'); const Tile = require(__dirname+'/src/Tile') termap = new Termap(); - -// TODO: abstracing this class, create loader class -data = fs.readFileSync(__dirname+"/tiles/world.pbf.gz"); -tile = new Tile(data); -termap.renderer.features = tile.layers -termap._draw(); +termap.init(); diff --git a/package.json b/package.json index aff8bc8..e3e73aa 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "author": "Michael Straßburger ", "license": "MIT", "dependencies": { + "bluebird": "^3.4.6", "bresenham": "0.0.4", "coffee-script": "^1.10.0", "earcut": "^2.1.1", diff --git a/src/Renderer.coffee b/src/Renderer.coffee index 4097998..db360ca 100644 --- a/src/Renderer.coffee +++ b/src/Renderer.coffee @@ -5,10 +5,14 @@ The Console Vector Tile renderer - bäm! ### x256 = require 'x256' +mercator = new (require('sphericalmercator'))() +tilebelt = require 'tilebelt' +MBTiles = require 'mbtiles' Canvas = require './Canvas' LabelBuffer = require './LabelBuffer' Styler = require './Styler' +Tile = require './Tile' utils = require './utils' module.exports = class Renderer @@ -68,8 +72,9 @@ module.exports = class Renderer lastDrawAt: 0 labelBuffer: null + tileSource: null - constructor: (@output) -> + constructor: (@output, @tileSource) -> @labelBuffer = new LabelBuffer() loadStyleFile: (file) -> @@ -78,8 +83,8 @@ module.exports = class Renderer setSize: (@width, @height) -> @canvas = new Canvas @width, @height - draw: (@view, @zoom, @degree) -> - return if @isDrawing + draw: (@center, @zoom, @degree) -> + return Promise.reject() if @isDrawing @isDrawing = true @notify "rendering..." @@ -90,55 +95,109 @@ module.exports = class Renderer @canvas.setBackground x256 utils.hex2rgb color @canvas.clear() - @canvas.reset() - @canvas.translate @view[0], @view[1] - @_renderLayers() + # TODO: tiles = @_tilesInBBox @_getBBox() + z = Math.max 0, Math.floor @zoom + xyz = tilebelt.pointToTileFraction @center.lon, @center.lat, z + tile = + size: tileSize + x: Math.floor xyz[0] + y: Math.floor xyz[1] + z: z + + tileSize = @config.tileSize / @_scaleAtZoom() + position = [ + @width/2-(xyz[0]-Math.floor(xyz[0]))*tileSize + @height/2-(xyz[1]-Math.floor(xyz[1]))*tileSize + ] + + @_renderTile tile, position + .then => + @_writeFrame() + + @isDrawing = false + @lastDrawAt = Date.now() + + _writeFrame: -> unless @lastDrawAt @_clearScreen() @output.write "\x1B[?6h" @output.write @canvas.frame() - @isDrawing = false - @lastDrawAt = Date.now() - featuresAt: (x, y) -> @labelBuffer.featuresAt x, y + _getBBox: (center = @center, zoom = @zoom) -> + [x, y] = utils.ll2xy center.lon, center.lat + meterPerPixel = utils.metersPerPixel zoom, center.lat + + width = @width * meterPerPixel + height = @height * meterPerPixel + + west = x - width*.5 + east = x + width*.5 + south = y + height*.5 + north = y - height*.5 + + box = mercator + .inverse([west+1, south]) + .concat mercator.inverse([east-1, north]) + + _tilesInBBox: (bbox, zoom = @zoom) -> + tiles = {} + [tiles.minX, tiles.minY] = utils.ll2tile bbox[0], bbox[1], Math.floor zoom + [tiles.maxX, tiles.maxY] = utils.ll2tile bbox[2], bbox[3], Math.floor zoom + tiles + _clearScreen: -> @output.write "\x1B[2J" _write: (output) -> @output.write output - _renderLayers: -> - for layer in @config.drawOrder - if layer.indexOf(':') isnt -1 - [layer, filter] = layer.split /:/ - [filterField, filterValue] = filter.split /=/ - else - filter = false + _renderTile: (tile, position) -> + @tileSource + .getTile tile.z, tile.x, tile.y + .then (tile) => + @canvas.reset() + @canvas.translate position[0], position[1] - continue unless @features?[layer] - - scale = (@config.tileSize/@config.projectSize)/Math.pow(2, @zoom) - - if @config.layers[layer]?.minZoom and @zoom > @config.layers[layer].minZoom - continue + scale = @_scaleAtZoom() box = - minX: -@view[0]*scale - minY: -@view[1]*scale - maxX: (@width-@view[0])*scale - maxY: (@height-@view[1])*scale + minX: -position[0]*scale + minY: -position[1]*scale + maxX: (@width-position[0])*scale + maxY: (@height-position[1])*scale + # console.log box + # process.exit 0 - features = @features[layer].tree.search box - @notify "rendering #{features.length} #{layer} features.." - for feature in features - if not filter or feature.data.properties[filterField] is filterValue - @_drawFeature layer, feature, scale + for layer in @config.drawOrder + if layer.indexOf(':') isnt -1 + [layer, filter] = layer.split /:/ + [filterField, filterValue] = filter.split /=/ + else + filter = false + + continue unless tile?[layer] + + if @config.layers[layer]?.minZoom and @zoom > @config.layers[layer].minZoom + continue + + features = tile[layer].tree.search box + + @notify "rendering #{features.length} #{layer} features.." + for feature in features + if not filter or feature.data.properties[filterField] is filterValue + @_drawFeature layer, feature, scale + + #@draw @center, @zoom+.3, @degree + + _scaleAtZoom: (zoom = @zoom) -> + baseZoom = Math.floor Math.max 0, zoom + (@config.tileSize/@config.projectSize)/Math.pow(2, zoom-baseZoom) _drawFeature: (layer, data, scale) -> feature = data.data diff --git a/src/Termap.coffee b/src/Termap.coffee index 01e6725..008c695 100644 --- a/src/Termap.coffee +++ b/src/Termap.coffee @@ -7,10 +7,11 @@ keypress = require 'keypress' TermMouse = require 'term-mouse' - +Promise = require 'bluebird' mercator = new (require('sphericalmercator'))() Renderer = require './Renderer' +TileSource = require './TileSource' utils = require './utils' module.exports = class Termap @@ -18,6 +19,7 @@ module.exports = class Termap input: process.stdin output: process.stdout + source: __dirname+"/../tiles/planet.z0-z8.mbtiles" styleFile: __dirname+"/../styles/bright.json" zoomStep: 0.2 @@ -29,12 +31,14 @@ module.exports = class Termap mousePosition: [0, 0] mouseDragging: false + tileSource: null + degree: 0 center: - lat: 49.0189 - lon: 12.0990 - #lat: 0 #26.7 - #lon: 0 #20.2 + #lat: 49.0189 + #lon: 12.0990 + lat: 54.133028 + lon: 10.609505 zoom: 0 view: [0, 0] @@ -44,10 +48,26 @@ module.exports = class Termap constructor: (options) -> @config[key] = val for key, val of options - @_initKeyboard() - @_initMouse() + init: -> + Promise + .resolve() + .then => + @_initKeyboard() + @_initMouse() - @_initRenderer() + console.log "loading tilesource" + @_initTileSource() + + .then => + console.log "loaded" + @_initRenderer() + + .then => + @_draw() + + _initTileSource: -> + @tileSource = new TileSource() + @tileSource.init @config.source _initKeyboard: -> keypress @config.input @@ -65,7 +85,7 @@ module.exports = class Termap @mouse.on 'move', (event) => @_onMouseMove event _initRenderer: -> - @renderer = new Renderer @config.output + @renderer = new Renderer @config.output, @tileSource @renderer.loadStyleFile @config.styleFile @config.output.on 'resize', => @@ -129,10 +149,10 @@ module.exports = class Termap when "k" then @degree += 15 when "l" then @degree -= 15 - when "left" then @view[0] += 5 - when "right" then @view[0] -= 5 - when "up" then @view[1]+= 5 - when "down" then @view[1]-= 5 + when "left" then @center.lon -= 1 + when "right" then @center.lon += 1 + when "up" then @center.lat += 1 + when "down" then @center.lat -= 1 else null @@ -143,40 +163,11 @@ module.exports = class Termap # display debug info for unhandled keys @renderer.notify JSON.stringify key - _draw: -> - @renderer.draw @view, @zoom, @degree - @renderer.notify @_getFooter() - - _getTiles: -> - - _getBBox: (zoom = @zoom) -> - [x, y] = utils.ll2xy @center.lon, @center.lat - meterPerPixel = utils.metersPerPixel zoom, @center.lat - - width = @width * meterPerPixel - height = @height * meterPerPixel - - west = x - width*.5 - east = x + width*.5 - south = y + height*.5 - north = y - height*.5 - - box = mercator - .inverse([west+1, south]) - .concat mercator.inverse([east-1, north]) - - _tilesInBBox: (bbox, zoom = @zoom) -> - tile = utils.ll2tile bbox[0], bbox[1], Math.floor zoom - tiles = - minX: Math.max 0, tile[0] - minY: Math.max 0, tile[1] - - tile = utils.ll2tile bbox[2], bbox[3], Math.floor zoom - tiles.maxX = Math.max 0, tile[0] - tiles.maxY = Math.max 0, tile[1] - - tiles + @renderer + .draw @center, @zoom, @degree + .then => + @renderer.notify @_getFooter() _getFooter: -> # features = @renderer.featuresAt @mousePosition.x-1-(@view[0]>>1), @mousePosition.y-1-(@view[1]>>2) @@ -188,11 +179,11 @@ module.exports = class Termap # ).join(", ")+"] "+ # "#{@mousePosition.x} #{@mousePosition.y}" #"center: [#{utils.digits @center.lat, 2}, #{utils.digits @center.lng, 2}]}" - bbox = @_getBBox() - - "zoom: #{utils.digits @zoom, 2} "+ + # bbox = @_getBBox() + # tiles = @_tilesInBBox(bbox) + "zoom: #{utils.digits @zoom, 2} " #{}"bbox: [#{bbox.map((z) -> utils.digits(z, 2)).join(', ')}]"+ - "tiles: "+(v for k,v of @_tilesInBBox(bbox) when typeof v is "number").join(",") + # "tiles: "+("#{k}: #{v}" for k,v of @_tilesInBBox(bbox) when typeof v is "number").join(",") #features.map((f) -> JSON.stringify f.feature.properties).join(" - ") diff --git a/src/TileSource.coffee b/src/TileSource.coffee new file mode 100644 index 0000000..caced7a --- /dev/null +++ b/src/TileSource.coffee @@ -0,0 +1,52 @@ +### + termap - Terminal Map Viewer + by Michael Strassburger + + Source for VectorTiles - supports + * remote TileServer + * local MBTiles and VectorTiles +### + +Promise = require 'bluebird' +MBTiles = require 'mbtiles' + +Tile = require './Tile' + +module.exports = class TileSource + modes: + MBTiles: 1 + VectorTile: 2 + + mode: null + cache: {} + + mbtiles: null + + init: (source) -> + if source.endsWith ".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) => + return reject err if err + resolve() + + getTile: (z, x, y) -> + unless @mode + throw new Error "no TileSource defined" + + z = Math.max 0, Math.floor z + + cacheKey = [z, x, y].join "-" + + return if cached = @cache[cacheKey] + Promise.resolve cached + else if @mode is @modes.MBTiles + new Promise (resolve, reject) => + @mbtiles.getTile z, x, y, (err, tileData) => + return reject err if err + resolve @cache[cacheKey] = new Tile tileData diff --git a/src/utils.coffee b/src/utils.coffee index 312edcf..424f942 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -48,6 +48,7 @@ utils = deg2rad: (angle) -> # (angle / 180) * Math.PI angle * 0.017453292519943295 + rad2deg: (angle) -> angle / Math.PI * 180