mirror of
https://github.com/rastapasta/mapscii.git
synced 2024-11-21 15:43:08 +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
|
||||
#### 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
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
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'
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user