🎭 more async refactoring in preperation for multitile support

This commit is contained in:
Michael Straßburger 2016-11-03 01:25:09 +01:00
parent da6c398e6d
commit 3895fd1fd3
6 changed files with 129 additions and 93 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ node_modules
bundle* bundle*
*.log *.log
tmp tmp
mbtiles

View File

@ -26,6 +26,7 @@
"earcut": "^2.1.1", "earcut": "^2.1.1",
"gl-matrix": "^2.3.2", "gl-matrix": "^2.3.2",
"keypress": "^0.2.1", "keypress": "^0.2.1",
"mbtiles": "^0.9.0",
"pbf": "^3.0.0", "pbf": "^3.0.0",
"rbush": "^2.0.1", "rbush": "^2.0.1",
"sphericalmercator": "^1.0.5", "sphericalmercator": "^1.0.5",

View File

@ -16,6 +16,7 @@ Tile = require './Tile'
utils = require './utils' utils = require './utils'
module.exports = class Renderer module.exports = class Renderer
cache: {}
config: config:
baseZoom: 4 baseZoom: 4
fillPolygons: true fillPolygons: true
@ -83,7 +84,7 @@ module.exports = class Renderer
setSize: (@width, @height) -> setSize: (@width, @height) ->
@canvas = new Canvas @width, @height @canvas = new Canvas @width, @height
draw: (@center, @zoom, @degree) -> draw: (center, zoom, rotation) ->
return Promise.reject() if @isDrawing return Promise.reject() if @isDrawing
@isDrawing = true @isDrawing = true
@ -98,27 +99,75 @@ module.exports = class Renderer
# TODO: tiles = @_tilesInBBox @_getBBox() # TODO: tiles = @_tilesInBBox @_getBBox()
z = Math.max 0, Math.floor @zoom z = Math.max 0, Math.floor zoom
xyz = tilebelt.pointToTileFraction @center.lon, @center.lat, z xyz = tilebelt.pointToTileFraction center.lon, center.lat, z
tile = tile =
size: tileSize size: tileSize
x: Math.floor xyz[0] x: Math.floor xyz[0]
y: Math.floor xyz[1] y: Math.floor xyz[1]
z: z z: z
tileSize = @config.tileSize / @_scaleAtZoom() tileSize = @config.tileSize / @_scaleAtZoom(zoom)
position = [ position = [
@width/2-(xyz[0]-Math.floor(xyz[0]))*tileSize @width/2-(xyz[0]-tile.x)*tileSize
@height/2-(xyz[1]-Math.floor(xyz[1]))*tileSize @height/2-(xyz[1]-tile.y)*tileSize
] ]
@_renderTile tile, position @_getTile tile
.then => .then (data) =>
@_renderTile data, zoom, position
@_writeFrame() @_writeFrame()
@isDrawing = false @isDrawing = false
@lastDrawAt = Date.now() @lastDrawAt = Date.now()
_getTile: (tile) ->
cacheKey = [tile.z, tile.x, tile.y].join "-"
# if data = @cache[cacheKey]
# console.log cacheKey
# console.log data
# return Promise.resolve data
@tileSource
.getTile tile.z, tile.x, tile.y
.then (data) =>
@cache[cacheKey] = data
_renderTile: (tile, zoom, position) ->
@canvas.reset()
@canvas.translate position[0], position[1]
scale = @_scaleAtZoom zoom
box =
minX: -position[0]*scale
minY: -position[1]*scale
maxX: (@width-position[0])*scale
maxY: (@height-position[1])*scale
# console.log box
# process.exit 0
for layer in @config.drawOrder
if layer.indexOf(':') isnt -1
[layer, filter] = layer.split /:/
[filterField, filterValue] = filter.split /=/
else
filter = false
continue unless tile.layers?[layer]
if @config.layers[layer]?.minZoom and zoom > @config.layers[layer].minZoom
continue
#features = tile.layers[layer].tree.search box
#@notify "rendering #{features.length} #{layer} features.."
for feature in tile.layers[layer].features
feature = data: feature
if not filter or feature.data.properties[filterField] is filterValue
@_drawFeature layer, feature, scale, zoom
_writeFrame: -> _writeFrame: ->
unless @lastDrawAt unless @lastDrawAt
@_clearScreen() @_clearScreen()
@ -129,7 +178,7 @@ module.exports = class Renderer
featuresAt: (x, y) -> featuresAt: (x, y) ->
@labelBuffer.featuresAt x, y @labelBuffer.featuresAt x, y
_getBBox: (center = @center, zoom = @zoom) -> _getBBox: (center, zoom) ->
[x, y] = utils.ll2xy center.lon, center.lat [x, y] = utils.ll2xy center.lon, center.lat
meterPerPixel = utils.metersPerPixel zoom, center.lat meterPerPixel = utils.metersPerPixel zoom, center.lat
@ -145,7 +194,7 @@ module.exports = class Renderer
.inverse([west+1, south]) .inverse([west+1, south])
.concat mercator.inverse([east-1, north]) .concat mercator.inverse([east-1, north])
_tilesInBBox: (bbox, zoom = @zoom) -> _tilesInBBox: (bbox, zoom) ->
tiles = {} tiles = {}
[tiles.minX, tiles.minY] = utils.ll2tile bbox[0], bbox[1], Math.floor zoom [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.maxX, tiles.maxY] = utils.ll2tile bbox[2], bbox[3], Math.floor zoom
@ -157,49 +206,11 @@ module.exports = class Renderer
_write: (output) -> _write: (output) ->
@output.write output @output.write output
_renderTile: (tile, position) -> _scaleAtZoom: (zoom) ->
@tileSource
.getTile tile.z, tile.x, tile.y
.then (tile) =>
@canvas.reset()
@canvas.translate position[0], position[1]
scale = @_scaleAtZoom()
box =
minX: -position[0]*scale
minY: -position[1]*scale
maxX: (@width-position[0])*scale
maxY: (@height-position[1])*scale
# console.log box
# process.exit 0
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 baseZoom = Math.floor Math.max 0, zoom
(@config.tileSize/@config.projectSize)/Math.pow(2, zoom-baseZoom) (@config.tileSize/@config.projectSize)/Math.pow(2, zoom-baseZoom)
_drawFeature: (layer, data, scale) -> _drawFeature: (layer, data, scale, zoom) ->
feature = data.data feature = data.data
# TODO: this is ugly :) need to be fixed @style # TODO: this is ugly :) need to be fixed @style
@ -207,7 +218,7 @@ module.exports = class Renderer
feature.type = "LineString" if layer is "building" or layer is "road" feature.type = "LineString" if layer is "building" or layer is "road"
# TODO: zoom level # TODO: zoom level
unless style = @styler.getStyleFor layer, feature, 19-@zoom unless style = @styler.getStyleFor layer, feature, 19-zoom
return false return false
toDraw = (@_scaleAndReduce points, scale for points in feature.points) toDraw = (@_scaleAndReduce points, scale for points in feature.points)

View File

@ -19,7 +19,7 @@ module.exports = class Termap
input: process.stdin input: process.stdin
output: process.stdout output: process.stdout
source: __dirname+"/../tiles/planet.z0-z8.mbtiles" source: __dirname+"/../mbtiles/regensburg.mbtiles"
styleFile: __dirname+"/../styles/bright.json" styleFile: __dirname+"/../styles/bright.json"
zoomStep: 0.2 zoomStep: 0.2
@ -33,15 +33,19 @@ module.exports = class Termap
tileSource: null tileSource: null
degree: 0
center:
#lat: 49.0189
#lon: 12.0990
lat: 54.133028
lon: 10.609505
zoom: 0 zoom: 0
view: [0, 0] rotation: 0
center:
# sf
# lat: 37.787946
# lon: -122.407522
# iceland
# lat: 64.124229
# lon: -21.811552
# rgbg
lat: 49.0189
lon: 12.0990
minZoom: null minZoom: null
@ -105,8 +109,9 @@ module.exports = class Termap
_onClick: (event) -> _onClick: (event) ->
if @mouseDragging and event.button is "left" if @mouseDragging and event.button is "left"
@view[0] -= (@mouseDragging.x-@mousePosition.x)<<1 # TODO lat/lng based drag&drop
@view[1] -= (@mouseDragging.y-@mousePosition.y)<<2 # @view[0] -= (@mouseDragging.x-@mousePosition.x)<<1
# @view[1] -= (@mouseDragging.y-@mousePosition.y)<<2
@_draw() @_draw()
@mouseDragging = false @mouseDragging = false
@ -123,8 +128,9 @@ module.exports = class Termap
# start dragging # start dragging
if event.button is "left" if event.button is "left"
if @mouseDragging if @mouseDragging
@view[0] -= (@mouseDragging.x-event.x)<<1 # TODO lat/lng based drag&drop
@view[1] -= (@mouseDragging.y-event.y)<<2 # @view[0] -= (@mouseDragging.x-event.x)<<1
# @view[1] -= (@mouseDragging.y-event.y)<<2
if not @renderer.isDrawing and @renderer.lastDrawAt < Date.now()-100 if not @renderer.isDrawing and @renderer.lastDrawAt < Date.now()-100
@_draw() @_draw()
@ -146,8 +152,8 @@ module.exports = class Termap
when "a" then @zoomBy @config.zoomStep when "a" then @zoomBy @config.zoomStep
when "z" then @zoomBy -@config.zoomStep when "z" then @zoomBy -@config.zoomStep
when "k" then @degree += 15 when "k" then @rotation += 15
when "l" then @degree -= 15 when "l" then @rotation -= 15
when "left" then @center.lon -= 1 when "left" then @center.lon -= 1
when "right" then @center.lon += 1 when "right" then @center.lon += 1
@ -165,7 +171,7 @@ module.exports = class Termap
_draw: -> _draw: ->
@renderer @renderer
.draw @center, @zoom, @degree .draw @center, @zoom, @rotation
.then => .then =>
@renderer.notify @_getFooter() @renderer.notify @_getFooter()

View File

@ -7,32 +7,41 @@
VectorTile = require('vector-tile').VectorTile VectorTile = require('vector-tile').VectorTile
Protobuf = require 'pbf' Protobuf = require 'pbf'
Promise = require 'bluebird'
zlib = require 'zlib' zlib = require 'zlib'
Rbush = require 'rbush' rbush = require 'rbush'
module.exports = class Tile class Tile
tree: null
layers: {} layers: {}
constructor: (buffer, @styler = null) -> load: (buffer) ->
@tree = new Rbush() @_unzipIfNeeded buffer
@tile = @_loadTile buffer .then (data) => @_loadTile data
.then (tile) => @_loadLayers tile
@_loadFeatures() .then => this
_loadTile: (buffer) -> _loadTile: (buffer) ->
buffer = zlib.gunzipSync buffer if @_isGzipped buffer @tile = new VectorTile new Protobuf buffer
new VectorTile new Protobuf buffer
_unzipIfNeeded: (buffer) ->
new Promise (resolve, reject) =>
if @_isGzipped buffer
zlib.gunzip buffer, (err, data) ->
return reject err if err
resolve data
else
resolve buffer
_isGzipped: (buffer) -> _isGzipped: (buffer) ->
buffer.slice(0,2).indexOf(Buffer.from([0x1f, 0x8b])) is 0 buffer.slice(0,2).indexOf(Buffer.from([0x1f, 0x8b])) is 0
_loadFeatures: -> _loadLayers: (tile) ->
for name, layer of @tile.layers layers = {}
tree = new Rbush() for name, layer of tile.layers
tree = rbush()
features = for i in [0...layer.length] features = for i in [0...layer.length]
# TODO: caching of similar attributes to avoid looking up the style each time # TODO: caching of similar attributes to avoid looking up the style each time
continue if @styler and not @styler.getStyleFor layer, feature #continue if @styler and not @styler.getStyleFor layer, feature
feature = layer.feature i feature = layer.feature i
@ -48,7 +57,9 @@ module.exports = class Tile
@_addToTree tree, data @_addToTree tree, data
data data
@layers[name] = tree: tree, features: features layers[name] = tree: tree, features: features
@layers = layers
_addToTree: (tree, data) -> _addToTree: (tree, data) ->
[minX, maxX, minY, maxY] = [Infinity, -Infinity, Infinity, -Infinity] [minX, maxX, minY, maxY] = [Infinity, -Infinity, Infinity, -Infinity]
@ -65,3 +76,5 @@ module.exports = class Tile
minY: minY minY: minY
maxY: maxY maxY: maxY
data: data data: data
module.exports = Tile

View File

@ -13,12 +13,12 @@ MBTiles = require 'mbtiles'
Tile = require './Tile' Tile = require './Tile'
module.exports = class TileSource module.exports = class TileSource
cache: {}
modes: modes:
MBTiles: 1 MBTiles: 1
VectorTile: 2 VectorTile: 2
mode: null mode: null
cache: {}
mbtiles: null mbtiles: null
@ -32,8 +32,8 @@ module.exports = class TileSource
loadMBtils: (source) -> loadMBtils: (source) ->
new Promise (resolve, reject) => new Promise (resolve, reject) =>
new MBTiles source, (err, @mbtiles) => new MBTiles source, (err, @mbtiles) =>
return reject err if err if err then reject err
resolve() else resolve()
getTile: (z, x, y) -> getTile: (z, x, y) ->
unless @mode unless @mode
@ -41,12 +41,16 @@ module.exports = class TileSource
z = Math.max 0, Math.floor z z = Math.max 0, Math.floor z
cacheKey = [z, x, y].join "-" if cached = @cache[[z,x,y].join("-")]
return Promise.resolve cached
return if cached = @cache[cacheKey] if @mode is @modes.MBTiles
Promise.resolve cached @_getMBTile z, x, y
else if @mode is @modes.MBTiles
new Promise (resolve, reject) => _getMBTile: (z, x, y) ->
@mbtiles.getTile z, x, y, (err, tileData) => new Promise (resolve, reject) =>
return reject err if err @mbtiles.getTile z, x, y, (err, tileData) =>
resolve @cache[cacheKey] = new Tile tileData return reject err if err
tile = @cache[[z,x,y].join("-")] = new Tile()
resolve tile.load tileData