🎨 MVP version of mapbox style parser

This commit is contained in:
Michael Straßburger 2016-09-21 05:18:20 +02:00
parent 87340c1d3c
commit 1afd8ec9c2
6 changed files with 1808 additions and 18 deletions

View File

@ -28,6 +28,7 @@ No web browser around? No worries - discover the planet in your console!
### Libraries
#### Mastering the console
* [`drawille-canvas-blessed-contrib`](https://github.com/yaronn/drawille-canvas-blessed-contrib/) for [braille](http://www.fileformat.info/info/unicode/block/braille_patterns/utf8test.htm) rendering
* [`x256`](https://github.com/substack/node-x256) for finding nearest xterm-256 [color codes](https://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg)
* [`term-mouse`](https://github.com/CoderPuppy/term-mouse) for mouse handling
* [`keypress`](https://github.com/TooTallNate/keypress) for input handling
@ -41,7 +42,6 @@ No web browser around? No worries - discover the planet in your console!
* [`rbush`](https://github.com/mourner/rbush) for 2D spatial indexing based label and mouse collision detection
* [`sphericalmercator`](https://github.com/mapbox/node-sphericalmercator) for [EPSG:3857](http://spatialreference.org/ref/sr-org/6864/) <> [WGS84](http://spatialreference.org/ref/epsg/wgs-84/) conversions
## Wishlist
* node port of [libdrawille](https://github.com/Huulivoide/libdrawille) - well optimized library, supporting filled polygons
@ -71,6 +71,8 @@ No web browser around? No worries - discover the planet in your console!
* [x] start with zoom level which shows full vector tile
* [x] accurate mouse drag&drop
* [x] handle console resize
* [ ] styling
* [ ] abstracted MapBox style JSON support
* [ ] turn this into a [`blessed-contrib`](https://github.com/yaronn/blessed-contrib) widget
## License

View File

@ -28,6 +28,7 @@
"rbush": "^2.0.1",
"sphericalmercator": "^1.0.5",
"term-mouse": "^0.1.1",
"vector-tile": "^1.3.0"
"vector-tile": "^1.3.0",
"x256": "0.0.2"
}
}

View File

@ -2,7 +2,7 @@ rbush = require 'rbush'
module.exports = class LabelBuffer
tree: null
margin: 1
margin: 5
constructor: (@width, @height) ->
@tree = rbush()

92
src/Styler.coffee Normal file
View File

@ -0,0 +1,92 @@
fs = require 'fs'
# 'text-field'
# Verrrrry MVP implementation
# TODO: should be optimized by compiling the json to method&cb based filters
module.exports = class Styler
styleById: {}
styleByLayer: {}
constructor: (file) ->
json = JSON.parse fs.readFileSync(file).toString()
@styleName = json.name
for layer in json.layers
continue if layer.ref
style = layer
@styleByLayer[layer['source-layer']] ?= []
@styleByLayer[layer['source-layer']].push style
@styleById[layer.id] = style
getStyleFor: (layer, feature, zoom) ->
return false unless @styleByLayer[layer]
for style in @styleByLayer[layer]
return style unless style.filter
if @_passesFilter feature, style.filter
return style
false
_passesFilter: (feature, filter) ->
switch filter.shift()
when "all"
for subFilter in filter
return false unless @_passesFilter feature, subFilter
true
when "=="
feature.properties[filter[0]] is filter[1]
when "!="
feature.properties[filter[0]] isnt filter[1]
when "in"
field = filter.shift()
for value in filter
return true if feature.properties[field] is value
false
when "!in"
field = filter.shift()
for value in filter
return false if feature.properties[field] is value
true
###
cleanStyle: (file) ->
json = JSON.parse fs.readFileSync(file).toString()
cleanedStyle =
name: json.name
layers: []
for layer in json.layers
continue if layer.ref
cleanLayer =
type: layer.type
id: layer.id
paint: {}
'source-layer': layer['source-layer']
for key in ['filter', 'minzoom']
cleanLayer[key] = layer[key] if layer[key]
if layer.layout?['text-size']
cleanLayer.layout = 'text-size': layer.layout?['text-size']
# TODO: opacity
for key in ['fill-color', 'line-color', 'text-color', 'background-color']
cleanLayer.paint[key] = layer.paint[key] if layer.paint?[key]
if Object.keys(cleanLayer.paint).length
cleanedStyle.layers.push cleanLayer
console.log JSON.stringify cleanedStyle, null, ' '
###

1657
styles/bright.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,18 @@
Canvas = require '../drawille-canvas-blessed-contrib'
VectorTile = require('vector-tile').VectorTile
Protobuf = require 'pbf'
keypress = require 'keypress'
TermMouse = require 'term-mouse'
x256 = require 'x256'
Protobuf = require 'pbf'
VectorTile = require('vector-tile').VectorTile
fs = require 'fs'
zlib = require 'zlib'
TermMouse = require 'term-mouse'
mercator = new (require('sphericalmercator'))()
LabelBuffer = require __dirname+'/src/LabelBuffer'
triangulator = new (require('pnltri')).Triangulator()
LabelBuffer = require __dirname+'/src/LabelBuffer'
Styler = require __dirname+'/src/Styler'
utils =
deg2rad: (angle) ->
# (angle / 180) * Math.PI
@ -16,6 +20,21 @@ utils =
rad2deg: (angle) ->
angle / Math.PI * 180
hex2rgb: (color) ->
return [255, 0, 0] unless color?.match
unless color.match /^#[a-fA-F0-9]{3,6}$/
throw new Error "#{color} isn\'t a supported hex color"
color = color.substr 1
decimal = parseInt color, 16
if color.length is 3
rgb = [decimal>>8, (decimal>>4)&15, decimal&15]
rgb.map (c) => c + (c<<4)
else
[(decimal>>16)&255, (decimal>>8)&255, decimal&255]
digits: (number, digits) ->
Math.floor(number*Math.pow(10, digits))/Math.pow(10, digits)
@ -25,10 +44,13 @@ utils =
class Termap
config:
styleFile: __dirname+"/styles/bright.json"
fillPolygons: true
zoomStep: 0.5
# landuse
drawOrder: ["admin", "water", "building", "road", "poi_label", "housenum_label"]
# landuse "poi_label"
drawOrder: ["admin", "water", "building", "road", "housenum_label"]
icons:
car: "🚗"
@ -54,7 +76,7 @@ class Termap
minZoom: 2
color: 8
building:
minZoom: 3.5
minZoom: 2.5
color: 8
poi_label:
@ -76,6 +98,7 @@ class Termap
height: null
canvas: null
styler: null
isDrawing: false
lastDrawAt: 0
@ -122,6 +145,7 @@ class Termap
@zoom = Math.log(4096/@width)/Math.LN2
@labelBuffer = new LabelBuffer()
@styler = new Styler @config.styleFile
_onResize: (cb) ->
process.stdout.on 'resize', cb
@ -184,7 +208,7 @@ class Termap
features[name] = for i in [0...layer.length]
feature = layer.feature i
type: [undefined, "point", "line", "polygon"][feature.type]
type: [undefined, "Point", "LineString", "Polygon"][feature.type]
id: feature.id
properties: feature.properties
points: feature.loadGeometry()
@ -194,9 +218,12 @@ class Termap
_draw: ->
return if @isDrawing
@isDrawing = true
@lastDrawAt = Date.now()
# if color = @styler.styleById['background']?.paint['background-color']
# @canvas.strokeStyle = x256 utils.hex2rgb(color)...
# @canvas.fillRect 0, 0, @width, @height
# else
@canvas.clearRect 0, 0, @width, @height
@canvas.save()
@ -225,12 +252,12 @@ class Termap
@canvas.strokeStyle = @canvas.fillStyle = @config.layers[layer].color
for feature in @features[layer]
if @_drawFeature feature, scale
if @_drawFeature layer, feature, scale
drawn.push feature
drawn
_drawFeature: (feature, scale) ->
_drawFeature: (layer, feature, scale) ->
toDraw = []
for idx, points of feature.points
visible = false
@ -249,17 +276,28 @@ class Termap
continue unless visible
toDraw.push projectedPoints
if style = @styler.getStyleFor layer, feature, 8
color = style.paint['line-color'] or style.paint['fill-color']
# TODO: zoom calculation todo for perfect styling
if color instanceof Object
color = color.stops[0][1]
@canvas.fillStyle = @canvas.strokeStyle = x256 utils.hex2rgb color
else
@canvas.strokeStyle = @canvas.fillStyle = @config.layers[layer].color
switch feature.type
when "line"
when "LineString"
@_drawWithLines points for points in toDraw
true
when "polygon"
unless @_drawWithTriangles toDraw
when "Polygon"
unless @config.fillPolygons and @_drawWithTriangles toDraw
@_drawWithLines points for points in toDraw
true
when "point"
when "Point"
text = feature.properties.house_num or @config.icons[feature.properties.maki] or ""
wasDrawn = false