mirror of
https://github.com/rastapasta/mapscii.git
synced 2024-11-07 16:54:22 +01:00
🎨 implementing mapbox style compiler, turning filter into call chains
This commit is contained in:
parent
d7c7500a7f
commit
0877eaca86
@ -1,39 +0,0 @@
|
||||
###
|
||||
termap - Terminal Map Viewer
|
||||
by Michael Strassburger <codepoet@cpan.org>
|
||||
|
||||
Using 2D spatial indexing to avoid overlapping labels and markers
|
||||
Future: to detect collision on mouse interaction
|
||||
###
|
||||
|
||||
rbush = require 'rbush'
|
||||
|
||||
module.exports = class LabelBuffer
|
||||
tree: null
|
||||
margin: 5
|
||||
|
||||
constructor: (@width, @height) ->
|
||||
@tree = rbush()
|
||||
|
||||
clear: ->
|
||||
@tree.clear()
|
||||
|
||||
project: (x, y) ->
|
||||
[Math.floor(x/2), Math.floor(y/4)]
|
||||
|
||||
writeIfPossible: (text, x, y) ->
|
||||
point = @project x, y
|
||||
|
||||
if @_hasSpace text, point[0], point[1]
|
||||
@tree.insert @_calculateArea text, point[0], point[1]
|
||||
else
|
||||
false
|
||||
|
||||
_hasSpace: (text, x, y) ->
|
||||
not @tree.collides @_calculateArea text, x, y, 0
|
||||
|
||||
_calculateArea: (text, x, y, margin = @margin) ->
|
||||
minX: x-margin
|
||||
minY: y-margin
|
||||
maxX: x+margin+text.length
|
||||
maxY: y+margin
|
10
README.md
10
README.md
@ -3,7 +3,7 @@
|
||||
No web browser around? No worries - discover the planet in your console!
|
||||
|
||||
* Use your mouse or keys to navigate
|
||||
* Discover the globe or zoom in to learn about house numbers
|
||||
* Discover the globe or zoom in to explore your neighbourhood
|
||||
* See Point-of-Interest around any given location
|
||||
* Use an online map server or work offline with VectorTile/MBTiles
|
||||
* Highly customizable styling (colors, feature visibility, ...)
|
||||
@ -46,8 +46,11 @@ No web browser around? No worries - discover the planet in your console!
|
||||
### TODOs
|
||||
* [ ] cli linking
|
||||
* [ ] mapping of view to tiles to show
|
||||
* [x] abstracted MapBox style JSON support
|
||||
* [ ] giving render priority to features across layers (collect before render vs. direct)?
|
||||
* [ ] line drawing
|
||||
* [ ] support for stroke width
|
||||
* [ ] support for dashed/dotted lines?
|
||||
* [ ] label drawing
|
||||
* [x] support for point labels
|
||||
* [x] dynamic decluttering of labels
|
||||
@ -63,6 +66,8 @@ No web browser around? No worries - discover the planet in your console!
|
||||
* [ ] lat/lng-center + zoom based viewport
|
||||
* [ ] bbox awareness
|
||||
* [ ] zoom -> scale calculation
|
||||
* [ ] Tile parsing
|
||||
* [ ] directly throw away features that aren't covered by any style
|
||||
* [ ] TileSource class (abstracting URL, mbtiles, single vector tile source)
|
||||
* [ ] tile request system
|
||||
* [ ] from local mbtiles
|
||||
@ -75,8 +80,7 @@ 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
|
||||
* [x] styling
|
||||
* [ ] turn this into a [`blessed-contrib`](https://github.com/yaronn/blessed-contrib) widget
|
||||
|
||||
## License
|
||||
|
@ -20,7 +20,7 @@ module.exports = class Renderer
|
||||
fillPolygons: true
|
||||
language: 'de'
|
||||
|
||||
drawOrder: ["admin", "water", "building", "road", "poi_label", "place_label", "housenum_label"]
|
||||
drawOrder: ["admin", "building", "road", "water", "poi_label", "place_label", "housenum_label"]
|
||||
|
||||
icons:
|
||||
car: "🚗"
|
||||
@ -42,29 +42,15 @@ module.exports = class Renderer
|
||||
cinema: "C" #"🎦"
|
||||
|
||||
layers:
|
||||
housenum_label:
|
||||
minZoom: 1.5
|
||||
color: 8
|
||||
building:
|
||||
minZoom: 3.8
|
||||
color: 8
|
||||
housenum_label: minZoom: 1.5
|
||||
building: minZoom: 3.8
|
||||
|
||||
place_label:
|
||||
color: "yellow"
|
||||
place_label: true
|
||||
poi_label: minZoom: 3
|
||||
|
||||
poi_label:
|
||||
minZoom: 3
|
||||
color: "yellow"
|
||||
|
||||
road:
|
||||
color: 15
|
||||
|
||||
landuse:
|
||||
color: "green"
|
||||
water:
|
||||
color: "blue"
|
||||
admin:
|
||||
color: "red"
|
||||
road: true
|
||||
water: true
|
||||
admin: true
|
||||
|
||||
isDrawing: false
|
||||
lastDrawAt: 0
|
||||
@ -108,6 +94,8 @@ module.exports = class Renderer
|
||||
@isDrawing = true
|
||||
@lastDrawAt = Date.now()
|
||||
|
||||
@notify "rendering..."
|
||||
|
||||
@labelBuffer.clear()
|
||||
|
||||
# TODO: better way for background color instead of setting filling FG?
|
||||
@ -122,16 +110,17 @@ module.exports = class Renderer
|
||||
@_drawLayers()
|
||||
@canvas.restore()
|
||||
|
||||
@write @canvas._canvas.frame()
|
||||
@_write @canvas._canvas.frame()
|
||||
|
||||
@isDrawing = false
|
||||
|
||||
write: (output) ->
|
||||
_write: (output) ->
|
||||
process.stdout.write output
|
||||
|
||||
_drawLayers: ->
|
||||
drawn = []
|
||||
for layer in @config.drawOrder
|
||||
@notify "rendering #{layer}..."
|
||||
scale = Math.pow 2, @zoom
|
||||
continue unless @features?[layer]
|
||||
|
||||
@ -240,3 +229,6 @@ module.exports = class Renderer
|
||||
point.x+@view[0]<@width-4 and
|
||||
point.y+@view[1]>=0 and
|
||||
point.y+@view[1]<@height
|
||||
|
||||
notify: (text) ->
|
||||
@_write "\r\x1B[K"+text
|
||||
|
@ -5,8 +5,8 @@
|
||||
Minimalistic parser and compiler for Mapbox (Studio) Map Style files
|
||||
See: https://www.mapbox.com/mapbox-gl-style-spec/
|
||||
|
||||
Verrrrry MVP implementation
|
||||
TODO: should be optimized by compiling the json to method&cb based filters
|
||||
Compiles layer filter instructions into a chain of true/false returning
|
||||
anonymous functions to improve rendering speed compared to realtime parsing.
|
||||
###
|
||||
|
||||
fs = require 'fs'
|
||||
@ -19,47 +19,48 @@ module.exports = class Styler
|
||||
json = JSON.parse fs.readFileSync(file).toString()
|
||||
@styleName = json.name
|
||||
|
||||
for layer in json.layers
|
||||
continue if layer.ref
|
||||
style = layer
|
||||
for style in json.layers
|
||||
continue if style.ref
|
||||
|
||||
@styleByLayer[layer['source-layer']] ?= []
|
||||
@styleByLayer[layer['source-layer']].push style
|
||||
style.appliesTo = @_compileFilter style.filter
|
||||
|
||||
@styleById[layer.id] = style
|
||||
@styleByLayer[style['source-layer']] ?= []
|
||||
@styleByLayer[style['source-layer']].push style
|
||||
|
||||
@styleById[style.id] = style
|
||||
|
||||
getStyleFor: (layer, feature, zoom) ->
|
||||
# Skip all layers that don't have any styles set
|
||||
return false unless @styleByLayer[layer]
|
||||
|
||||
for style in @styleByLayer[layer]
|
||||
return style unless style.filter
|
||||
|
||||
if @_passesFilter feature, style.filter
|
||||
return style
|
||||
return style if style.appliesTo feature
|
||||
|
||||
false
|
||||
|
||||
_passesFilter: (feature, filter) ->
|
||||
_compileFilter: (filter) ->
|
||||
if not filter or not filter.length
|
||||
return -> true
|
||||
|
||||
switch filter[0]
|
||||
when "all"
|
||||
for subFilter in filter[1..]
|
||||
return false unless @_passesFilter feature, subFilter
|
||||
true
|
||||
filters = (@_compileFilter subFilter for subFilter in filter[1..])
|
||||
(feature) ->
|
||||
return false for appliesTo in filters when not appliesTo feature
|
||||
true
|
||||
|
||||
when "=="
|
||||
feature.properties[filter[1]] is filter[2]
|
||||
(feature) -> feature.properties[filter[1]] is filter[2]
|
||||
|
||||
when "!="
|
||||
feature.properties[filter[2]] isnt filter[2]
|
||||
(feature) -> feature.properties[filter[1]] isnt filter[2]
|
||||
|
||||
when "in"
|
||||
field = filter[1]
|
||||
for value in filter[2..]
|
||||
return true if feature.properties[field] is value
|
||||
false
|
||||
(feature) ->
|
||||
return true for value in filter[2..] when feature.properties[filter[1]] is value
|
||||
false
|
||||
|
||||
when "!in"
|
||||
field = filter[1]
|
||||
for value in filter[2..]
|
||||
return false if feature.properties[field] is value
|
||||
true
|
||||
(feature) ->
|
||||
return false for value in filter[2..] when feature.properties[filter[1]] is value
|
||||
true
|
||||
|
@ -119,7 +119,7 @@ module.exports = class Termap
|
||||
|
||||
_draw: ->
|
||||
@renderer.draw @view, @zoom
|
||||
@renderer.write @_getFooter()
|
||||
@renderer.notify @_getFooter()
|
||||
|
||||
_getBBox: ->
|
||||
[x, y] = mercator.forward [@center.lng, @center.lat]
|
||||
@ -132,10 +132,6 @@ module.exports = class Termap
|
||||
"center: [#{utils.digits @center.lat, 2}, #{utils.digits @center.lng, 2}] zoom: #{utils.digits @zoom, 2}"
|
||||
# bbox: [#{@_getBBox().map((z) -> utils.digits(z, 2)).join(',')}]"
|
||||
|
||||
notify: (text) ->
|
||||
return if @renderer.isDrawing
|
||||
@renderer.write "\r\x1B[K#{@_getFooter()} #{text}"
|
||||
|
||||
zoomBy: (step) ->
|
||||
return unless @scale+step > 0
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user