From 0877eaca864e75bf628ceefd7a83b4d383445660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Stra=C3=9Fburger?= Date: Wed, 21 Sep 2016 23:40:08 +0200 Subject: [PATCH] :art: implementing mapbox style compiler, turning filter into call chains --- LabelBuffer.coffee | 39 --------------------------------- README.md | 10 ++++++--- src/Renderer.coffee | 40 ++++++++++++++-------------------- src/Styler.coffee | 53 +++++++++++++++++++++++---------------------- src/Termap.coffee | 6 +---- 5 files changed, 51 insertions(+), 97 deletions(-) delete mode 100644 LabelBuffer.coffee diff --git a/LabelBuffer.coffee b/LabelBuffer.coffee deleted file mode 100644 index a8fc693..0000000 --- a/LabelBuffer.coffee +++ /dev/null @@ -1,39 +0,0 @@ -### - termap - Terminal Map Viewer - by Michael Strassburger - - 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 diff --git a/README.md b/README.md index 31ee452..f270c2e 100644 --- a/README.md +++ b/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 diff --git a/src/Renderer.coffee b/src/Renderer.coffee index 4b6080a..b6d0428 100644 --- a/src/Renderer.coffee +++ b/src/Renderer.coffee @@ -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 diff --git a/src/Styler.coffee b/src/Styler.coffee index 70d425b..d711201 100644 --- a/src/Styler.coffee +++ b/src/Styler.coffee @@ -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 diff --git a/src/Termap.coffee b/src/Termap.coffee index 060f8b3..561e818 100644 --- a/src/Termap.coffee +++ b/src/Termap.coffee @@ -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