2016-09-18 02:44:31 +02:00
|
|
|
Canvas = require 'drawille-canvas-blessed-contrib'
|
|
|
|
VectorTile = require('vector-tile').VectorTile
|
|
|
|
Protobuf = require 'pbf'
|
|
|
|
keypress = require 'keypress'
|
|
|
|
fs = require 'fs'
|
|
|
|
zlib = require 'zlib'
|
|
|
|
mouse = require('term-mouse')()
|
|
|
|
|
|
|
|
keypress process.stdin
|
2016-09-18 06:32:26 +02:00
|
|
|
process.stdin.setRawMode(true)
|
|
|
|
process.stdin.resume()
|
|
|
|
|
|
|
|
mouse.start()
|
2016-09-18 02:44:31 +02:00
|
|
|
|
2016-09-18 04:20:35 +02:00
|
|
|
width = null
|
|
|
|
height = null
|
2016-09-18 02:44:31 +02:00
|
|
|
|
2016-09-18 15:26:09 +02:00
|
|
|
config =
|
2016-09-18 16:10:33 +02:00
|
|
|
drawOrder: ["admin", "water", "landuse", "building", "road", "poi_label"]
|
|
|
|
|
|
|
|
icons:
|
|
|
|
car: "🚗"
|
|
|
|
school: "🏫"
|
|
|
|
marker: "⭐"
|
|
|
|
'art-gallery': "🎨"
|
|
|
|
attraction: "❕"
|
|
|
|
stadium: "🏈"
|
|
|
|
toilet: "🚽"
|
|
|
|
cafe: "☕"
|
|
|
|
laundry: "👚"
|
|
|
|
bus: "🚌"
|
|
|
|
restaurant: "🍕"
|
|
|
|
lodging: "🏨"
|
|
|
|
'fire-station': "🚒"
|
|
|
|
shop: "🏬"
|
|
|
|
pharmacy: "💊"
|
|
|
|
beer: "🍺"
|
|
|
|
cinema: "🎦"
|
2016-09-18 15:26:09 +02:00
|
|
|
|
|
|
|
layers:
|
2016-09-18 16:10:33 +02:00
|
|
|
poi_label:
|
|
|
|
color: "red"
|
2016-09-18 15:26:09 +02:00
|
|
|
road:
|
|
|
|
color: "white"
|
|
|
|
landuse:
|
|
|
|
color: "green"
|
|
|
|
water:
|
|
|
|
color: "blue"
|
|
|
|
admin:
|
|
|
|
color: "red"
|
|
|
|
building:
|
|
|
|
color: 8
|
2016-09-18 02:44:31 +02:00
|
|
|
|
2016-09-18 04:20:35 +02:00
|
|
|
canvas = null
|
|
|
|
|
|
|
|
init = ->
|
|
|
|
width = Math.floor((process.stdout.columns-1)/2)*2*2
|
|
|
|
height = Math.ceil(process.stdout.rows/4)*4*4
|
|
|
|
canvas = new Canvas width, height
|
|
|
|
init()
|
2016-09-18 02:44:31 +02:00
|
|
|
|
|
|
|
features = {}
|
|
|
|
data = fs.readFileSync __dirname+"/tiles/regensburg.pbf.gz"
|
|
|
|
zlib.gunzip data, (err, buffer) ->
|
|
|
|
throw new Error err if err
|
|
|
|
|
|
|
|
tile = new VectorTile new Protobuf buffer
|
2016-09-18 15:26:09 +02:00
|
|
|
|
|
|
|
# Load all layers and preparse the included geometries
|
2016-09-18 02:44:31 +02:00
|
|
|
for name,layer of tile.layers
|
2016-09-18 15:26:09 +02:00
|
|
|
if config.layers[name]
|
2016-09-18 02:44:31 +02:00
|
|
|
features[name] = []
|
2016-09-18 15:26:09 +02:00
|
|
|
|
2016-09-18 02:44:31 +02:00
|
|
|
for i in [0...layer.length]
|
2016-09-18 15:26:09 +02:00
|
|
|
feature = layer.feature i
|
|
|
|
features[name].push
|
2016-09-18 16:10:33 +02:00
|
|
|
type: [undefined, "point", "line", "polygon"][feature.type]
|
2016-09-18 15:26:09 +02:00
|
|
|
id: feature.id
|
|
|
|
properties: feature.properties
|
|
|
|
points: feature.loadGeometry()
|
2016-09-18 02:44:31 +02:00
|
|
|
|
|
|
|
draw()
|
|
|
|
|
|
|
|
view = [-400, -80]
|
2016-09-18 06:32:26 +02:00
|
|
|
scale = 4
|
2016-09-18 02:44:31 +02:00
|
|
|
|
|
|
|
flush = ->
|
|
|
|
process.stdout.write canvas._canvas.frame()
|
|
|
|
|
2016-09-18 06:32:26 +02:00
|
|
|
lastDraw = null
|
2016-09-18 04:20:35 +02:00
|
|
|
drawing = false
|
2016-09-18 02:44:31 +02:00
|
|
|
draw = ->
|
2016-09-18 04:20:35 +02:00
|
|
|
return if drawing
|
2016-09-18 06:32:26 +02:00
|
|
|
lastDraw = Date.now()
|
2016-09-18 04:20:35 +02:00
|
|
|
drawing = true
|
2016-09-18 02:44:31 +02:00
|
|
|
canvas.clearRect(0, 0, width, height)
|
|
|
|
|
|
|
|
canvas.save()
|
|
|
|
|
|
|
|
canvas.translate view[0], view[1]
|
2016-09-18 15:26:09 +02:00
|
|
|
for layer in config.drawOrder
|
2016-09-18 02:44:31 +02:00
|
|
|
continue unless features[layer]
|
|
|
|
|
2016-09-18 16:10:33 +02:00
|
|
|
canvas.strokeStyle = canvas.fillStyle = config.layers[layer].color
|
|
|
|
|
2016-09-18 02:44:31 +02:00
|
|
|
for feature in features[layer]
|
2016-09-18 16:10:33 +02:00
|
|
|
for points in feature.points
|
|
|
|
|
|
|
|
visible = false
|
|
|
|
points = for point in points
|
2016-09-18 06:32:26 +02:00
|
|
|
p = [point.x/scale, point.y/scale]
|
2016-09-18 16:10:33 +02:00
|
|
|
if not visible and p[0]+view[0]>=4 and p[0]+view[0]<width-4 and p[1]+view[1]>=0 and p[1]+view[1]<height
|
|
|
|
visible = true
|
2016-09-18 02:44:31 +02:00
|
|
|
p
|
2016-09-18 16:10:33 +02:00
|
|
|
continue unless visible
|
|
|
|
|
|
|
|
switch feature.type
|
|
|
|
when "polygon", "line"
|
|
|
|
canvas.beginPath()
|
|
|
|
canvas.moveTo points.shift()...
|
|
|
|
canvas.lineTo point... for point in points
|
|
|
|
canvas.stroke()
|
2016-09-18 02:44:31 +02:00
|
|
|
|
2016-09-18 16:10:33 +02:00
|
|
|
when "point"
|
|
|
|
canvas.fillText (config.icons[feature.properties.maki] or "X"), point... for point in points
|
2016-09-18 02:44:31 +02:00
|
|
|
|
|
|
|
canvas.restore()
|
2016-09-18 15:26:09 +02:00
|
|
|
|
2016-09-18 02:44:31 +02:00
|
|
|
flush()
|
2016-09-18 04:20:35 +02:00
|
|
|
process.stdout.write getStatus()
|
|
|
|
|
|
|
|
drawing = false
|
2016-09-18 02:44:31 +02:00
|
|
|
|
2016-09-18 04:20:35 +02:00
|
|
|
getStatus = ->
|
2016-09-18 06:32:26 +02:00
|
|
|
"TerMap up and running!"
|
2016-09-18 02:44:31 +02:00
|
|
|
|
2016-09-18 04:33:59 +02:00
|
|
|
notify = (text) ->
|
|
|
|
return if drawing
|
|
|
|
process.stdout.write "\r\x1B[K#{getStatus()} #{text}"
|
|
|
|
|
2016-09-18 06:32:26 +02:00
|
|
|
# moving = null
|
|
|
|
# process.stdin.on 'mousepress', (info) ->
|
|
|
|
# # TODO: file bug @keypress, fails after x>95 / sequence: '\u001b[M#B'
|
|
|
|
# if info.x > 2048
|
|
|
|
# info.x = 100
|
|
|
|
#
|
|
|
|
# if info.button is "left"
|
|
|
|
# moving = info
|
|
|
|
#
|
|
|
|
# else if moving and info.release
|
|
|
|
#
|
|
|
|
# draw()
|
2016-09-18 02:44:31 +02:00
|
|
|
|
2016-09-18 06:32:26 +02:00
|
|
|
zoomBy = (step) ->
|
|
|
|
return unless scale+step > 0
|
|
|
|
|
|
|
|
before = scale
|
|
|
|
scale += step
|
|
|
|
|
|
|
|
view[0] = view[0]*before/scale + if step > 0 then 8 else -8
|
|
|
|
view[1] = view[1]*before/scale + if step > 0 then 8 else -8
|
2016-09-18 02:44:31 +02:00
|
|
|
|
|
|
|
process.stdin.on 'keypress', (ch, key) ->
|
|
|
|
result = switch key?.name
|
|
|
|
when "q"
|
|
|
|
process.exit 0
|
|
|
|
|
2016-09-18 06:32:26 +02:00
|
|
|
when "a" then zoomBy(.5)
|
|
|
|
when "z" then zoomBy(-.5)
|
2016-09-18 02:44:31 +02:00
|
|
|
when "left" then view[0] += 5
|
|
|
|
when "right" then view[0] -= 5
|
|
|
|
when "up" then view[1]+= 5
|
|
|
|
when "down" then view[1]-= 5
|
|
|
|
|
|
|
|
else
|
|
|
|
false
|
|
|
|
|
2016-09-18 04:33:59 +02:00
|
|
|
if result
|
|
|
|
draw()
|
|
|
|
else
|
|
|
|
notify JSON.stringify key
|
2016-09-18 02:44:31 +02:00
|
|
|
|
2016-09-18 04:20:35 +02:00
|
|
|
process.stdout.on 'resize', ->
|
|
|
|
init()
|
|
|
|
draw()
|
|
|
|
|
2016-09-18 06:32:26 +02:00
|
|
|
moving = null
|
|
|
|
mousePosition = null
|
|
|
|
|
|
|
|
mouse.on 'click', (event) ->
|
|
|
|
if moving and event.button is "left"
|
|
|
|
view[0] -= (moving.x-mousePosition.x)*2
|
|
|
|
view[1] -= (moving.y-mousePosition.y)*4
|
|
|
|
draw()
|
|
|
|
|
|
|
|
moving = null
|
|
|
|
|
|
|
|
mouse.on 'scroll', (event) ->
|
|
|
|
# TODO: handle .x/y for directed zoom
|
|
|
|
zoomBy .5 * if event.button is "up" then 1 else -1
|
|
|
|
draw()
|
|
|
|
|
|
|
|
mouse.on 'move', (event) ->
|
|
|
|
return unless event.x <= process.stdout.columns and event.y <= process.stdout.rows
|
|
|
|
if not moving and event.button is "left"
|
|
|
|
moving = x: event.x, y: event.y
|
|
|
|
|
|
|
|
mousePosition = x: event.x, y: event.y
|