mirror of
https://github.com/rastapasta/mapscii.git
synced 2025-02-16 17:30:47 +01:00
🎨 MVP version of mapbox style parser
This commit is contained in:
parent
87340c1d3c
commit
1afd8ec9c2
@ -28,6 +28,7 @@ No web browser around? No worries - discover the planet in your console!
|
|||||||
### Libraries
|
### Libraries
|
||||||
#### Mastering the console
|
#### 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
|
* [`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
|
* [`term-mouse`](https://github.com/CoderPuppy/term-mouse) for mouse handling
|
||||||
* [`keypress`](https://github.com/TooTallNate/keypress) for input 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
|
* [`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
|
* [`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
|
## Wishlist
|
||||||
|
|
||||||
* node port of [libdrawille](https://github.com/Huulivoide/libdrawille) - well optimized library, supporting filled polygons
|
* 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] start with zoom level which shows full vector tile
|
||||||
* [x] accurate mouse drag&drop
|
* [x] accurate mouse drag&drop
|
||||||
* [x] handle console resize
|
* [x] handle console resize
|
||||||
|
* [ ] styling
|
||||||
|
* [ ] abstracted MapBox style JSON support
|
||||||
* [ ] turn this into a [`blessed-contrib`](https://github.com/yaronn/blessed-contrib) widget
|
* [ ] turn this into a [`blessed-contrib`](https://github.com/yaronn/blessed-contrib) widget
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
"rbush": "^2.0.1",
|
"rbush": "^2.0.1",
|
||||||
"sphericalmercator": "^1.0.5",
|
"sphericalmercator": "^1.0.5",
|
||||||
"term-mouse": "^0.1.1",
|
"term-mouse": "^0.1.1",
|
||||||
"vector-tile": "^1.3.0"
|
"vector-tile": "^1.3.0",
|
||||||
|
"x256": "0.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ rbush = require 'rbush'
|
|||||||
|
|
||||||
module.exports = class LabelBuffer
|
module.exports = class LabelBuffer
|
||||||
tree: null
|
tree: null
|
||||||
margin: 1
|
margin: 5
|
||||||
|
|
||||||
constructor: (@width, @height) ->
|
constructor: (@width, @height) ->
|
||||||
@tree = rbush()
|
@tree = rbush()
|
||||||
|
92
src/Styler.coffee
Normal file
92
src/Styler.coffee
Normal 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
1657
styles/bright.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,18 @@
|
|||||||
Canvas = require '../drawille-canvas-blessed-contrib'
|
Canvas = require '../drawille-canvas-blessed-contrib'
|
||||||
VectorTile = require('vector-tile').VectorTile
|
|
||||||
Protobuf = require 'pbf'
|
|
||||||
keypress = require 'keypress'
|
keypress = require 'keypress'
|
||||||
|
TermMouse = require 'term-mouse'
|
||||||
|
x256 = require 'x256'
|
||||||
|
Protobuf = require 'pbf'
|
||||||
|
VectorTile = require('vector-tile').VectorTile
|
||||||
fs = require 'fs'
|
fs = require 'fs'
|
||||||
zlib = require 'zlib'
|
zlib = require 'zlib'
|
||||||
TermMouse = require 'term-mouse'
|
|
||||||
mercator = new (require('sphericalmercator'))()
|
mercator = new (require('sphericalmercator'))()
|
||||||
LabelBuffer = require __dirname+'/src/LabelBuffer'
|
|
||||||
triangulator = new (require('pnltri')).Triangulator()
|
triangulator = new (require('pnltri')).Triangulator()
|
||||||
|
|
||||||
|
LabelBuffer = require __dirname+'/src/LabelBuffer'
|
||||||
|
Styler = require __dirname+'/src/Styler'
|
||||||
|
|
||||||
utils =
|
utils =
|
||||||
deg2rad: (angle) ->
|
deg2rad: (angle) ->
|
||||||
# (angle / 180) * Math.PI
|
# (angle / 180) * Math.PI
|
||||||
@ -16,6 +20,21 @@ utils =
|
|||||||
rad2deg: (angle) ->
|
rad2deg: (angle) ->
|
||||||
angle / Math.PI * 180
|
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) ->
|
digits: (number, digits) ->
|
||||||
Math.floor(number*Math.pow(10, digits))/Math.pow(10, digits)
|
Math.floor(number*Math.pow(10, digits))/Math.pow(10, digits)
|
||||||
|
|
||||||
@ -25,10 +44,13 @@ utils =
|
|||||||
|
|
||||||
class Termap
|
class Termap
|
||||||
config:
|
config:
|
||||||
|
styleFile: __dirname+"/styles/bright.json"
|
||||||
|
|
||||||
|
fillPolygons: true
|
||||||
zoomStep: 0.5
|
zoomStep: 0.5
|
||||||
|
|
||||||
# landuse
|
# landuse "poi_label"
|
||||||
drawOrder: ["admin", "water", "building", "road", "poi_label", "housenum_label"]
|
drawOrder: ["admin", "water", "building", "road", "housenum_label"]
|
||||||
|
|
||||||
icons:
|
icons:
|
||||||
car: "🚗"
|
car: "🚗"
|
||||||
@ -54,7 +76,7 @@ class Termap
|
|||||||
minZoom: 2
|
minZoom: 2
|
||||||
color: 8
|
color: 8
|
||||||
building:
|
building:
|
||||||
minZoom: 3.5
|
minZoom: 2.5
|
||||||
color: 8
|
color: 8
|
||||||
|
|
||||||
poi_label:
|
poi_label:
|
||||||
@ -76,6 +98,7 @@ class Termap
|
|||||||
height: null
|
height: null
|
||||||
canvas: null
|
canvas: null
|
||||||
|
|
||||||
|
styler: null
|
||||||
isDrawing: false
|
isDrawing: false
|
||||||
lastDrawAt: 0
|
lastDrawAt: 0
|
||||||
|
|
||||||
@ -122,6 +145,7 @@ class Termap
|
|||||||
@zoom = Math.log(4096/@width)/Math.LN2
|
@zoom = Math.log(4096/@width)/Math.LN2
|
||||||
|
|
||||||
@labelBuffer = new LabelBuffer()
|
@labelBuffer = new LabelBuffer()
|
||||||
|
@styler = new Styler @config.styleFile
|
||||||
|
|
||||||
_onResize: (cb) ->
|
_onResize: (cb) ->
|
||||||
process.stdout.on 'resize', cb
|
process.stdout.on 'resize', cb
|
||||||
@ -184,7 +208,7 @@ class Termap
|
|||||||
features[name] = for i in [0...layer.length]
|
features[name] = for i in [0...layer.length]
|
||||||
feature = layer.feature i
|
feature = layer.feature i
|
||||||
|
|
||||||
type: [undefined, "point", "line", "polygon"][feature.type]
|
type: [undefined, "Point", "LineString", "Polygon"][feature.type]
|
||||||
id: feature.id
|
id: feature.id
|
||||||
properties: feature.properties
|
properties: feature.properties
|
||||||
points: feature.loadGeometry()
|
points: feature.loadGeometry()
|
||||||
@ -194,9 +218,12 @@ class Termap
|
|||||||
_draw: ->
|
_draw: ->
|
||||||
return if @isDrawing
|
return if @isDrawing
|
||||||
@isDrawing = true
|
@isDrawing = true
|
||||||
|
|
||||||
@lastDrawAt = Date.now()
|
@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.clearRect 0, 0, @width, @height
|
||||||
|
|
||||||
@canvas.save()
|
@canvas.save()
|
||||||
@ -225,12 +252,12 @@ class Termap
|
|||||||
@canvas.strokeStyle = @canvas.fillStyle = @config.layers[layer].color
|
@canvas.strokeStyle = @canvas.fillStyle = @config.layers[layer].color
|
||||||
|
|
||||||
for feature in @features[layer]
|
for feature in @features[layer]
|
||||||
if @_drawFeature feature, scale
|
if @_drawFeature layer, feature, scale
|
||||||
drawn.push feature
|
drawn.push feature
|
||||||
|
|
||||||
drawn
|
drawn
|
||||||
|
|
||||||
_drawFeature: (feature, scale) ->
|
_drawFeature: (layer, feature, scale) ->
|
||||||
toDraw = []
|
toDraw = []
|
||||||
for idx, points of feature.points
|
for idx, points of feature.points
|
||||||
visible = false
|
visible = false
|
||||||
@ -249,17 +276,28 @@ class Termap
|
|||||||
continue unless visible
|
continue unless visible
|
||||||
toDraw.push projectedPoints
|
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
|
switch feature.type
|
||||||
when "line"
|
when "LineString"
|
||||||
@_drawWithLines points for points in toDraw
|
@_drawWithLines points for points in toDraw
|
||||||
true
|
true
|
||||||
|
|
||||||
when "polygon"
|
when "Polygon"
|
||||||
unless @_drawWithTriangles toDraw
|
unless @config.fillPolygons and @_drawWithTriangles toDraw
|
||||||
@_drawWithLines points for points in toDraw
|
@_drawWithLines points for points in toDraw
|
||||||
true
|
true
|
||||||
|
|
||||||
when "point"
|
when "Point"
|
||||||
text = feature.properties.house_num or @config.icons[feature.properties.maki] or "◉"
|
text = feature.properties.house_num or @config.icons[feature.properties.maki] or "◉"
|
||||||
|
|
||||||
wasDrawn = false
|
wasDrawn = false
|
||||||
|
Loading…
Reference in New Issue
Block a user