diff --git a/README.md b/README.md index fe815a0..a1a097f 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ -# MapSCII - the whole world in your console. +# MapSCII - The Whole World In Your Console. -No web browser around? Don't worry - and discover the planet in your console! +MapSCII is node.js based [Vector Tile](https://github.com/mapbox/vector-tile-spec) to [Braille](http://www.fileformat.info/info/unicode/block/braille_patterns/utf8test.htm) renderer for [xterm](https://en.wikipedia.org/wiki/Xterm)-compatible terminals. * Discover the globe or zoom in to explore your neighbourhood * See Point-of-Interest around any given location -* Highly customizable styling (reuse your [mapbox-gl-styles](https://github.com/mapbox/mapbox-gl-styles)) +* Highly customizable layer styling with [Mapbox Styles](https://www.mapbox.com/mapbox-gl-style-spec/) * Compatible with Linux and OSX terminals, Windows support via [PuTTY](http://www.putty.org/) -* Use the default or your own map server - or work offline with VectorTile/MBTiles +* Connect to any vector tile server - or just use my custom [OpenStreetMap](https://en.wikipedia.org/wiki/OpenStreetMap) based one +* Work offline and discover local VectorTile/MBTiles * 100% pure Coffee-/JavaScript! :sunglasses: ## How to install @@ -123,7 +124,7 @@ If your terminal supports mouse events you can drag the map and use your scroll * [x] filled polygons * [x] convert polygons to triangles * [x] use triangulation for filling - * [ ] respect fill/line style file based setting + * [x] respect fill/line style file based setting * Tile * [x] directly throw away features that aren't covered by any style diff --git a/src/Canvas.coffee b/src/Canvas.coffee index 198be7f..ef6b6eb 100644 --- a/src/Canvas.coffee +++ b/src/Canvas.coffee @@ -51,25 +51,19 @@ module.exports = class Canvas xs = {} ys = {} + # + # for points in polylines + # if vertices.length + # continue + # holes.push vertices.length/2 - for points in polylines - if vertices.length - continue - holes.push vertices.length/2 + for point in polylines + vertices = vertices.concat point + xs[point[0]] = ys[point[1]] = true - lastPoint = [-1, -1] - for point in points - vertices = vertices.concat point[0], point[1] - xs[point[0]] = ys[point[1]] = true - - # Check if we actually got a valid polygon after projection and clamping - if Object.keys(xs).length is 1 or Object.keys(ys).length is 1 - if vertices.length - # TODO: a line-hole - skip it for now - continue - else - # TODO: a line instead of a polygon - skip it for now - return false + # Check if we actually got a valid polygon after projection and clamping + if Object.keys(xs).length is 1 or Object.keys(ys).length is 1 + return false try triangles = earcut vertices, holes diff --git a/src/Renderer.coffee b/src/Renderer.coffee index 3fac4d8..e85f9c0 100644 --- a/src/Renderer.coffee +++ b/src/Renderer.coffee @@ -5,7 +5,6 @@ The Console Vector Tile renderer - bäm! ### x256 = require 'x256' -mercator = new (require('sphericalmercator'))() tilebelt = require 'tilebelt' Promise = require 'bluebird' @@ -98,6 +97,7 @@ module.exports = class Renderer @isDrawing = true @labelBuffer.clear() + @_seen = {} if color = @styler.styleById['background']?.paint['background-color'] @canvas.setBackground x256 utils.hex2rgb color @@ -173,6 +173,7 @@ module.exports = class Renderer maxY: (@height-position.y)*scale features = {} + for layer in @config.drawOrder continue unless tile.data.layers?[layer] features[layer] = tile.data.layers[layer].search box @@ -186,7 +187,6 @@ module.exports = class Renderer for layer in @config.drawOrder for tile in tiles continue unless tile.features[layer]?.length - for feature in tile.features[layer] # continue if feature.id and drawn[feature.id] # drawn[feature.id] = true @@ -208,10 +208,12 @@ module.exports = class Renderer @config.tileSize / @config.projectSize / Math.pow(2, zoom-baseZoom) _drawFeature: (tile, feature) -> - return false if feature.style.minzoom and tile.zoom < feature.style.minzoom + if feature.style.minzoom and tile.zoom < feature.style.minzoom + return false - toDraw = @_scaleAndReduce tile, feature - return false unless toDraw.length + points = @_scaleAndReduce tile, feature + unless points.length + return false color = feature.style.paint['line-color'] or @@ -227,12 +229,12 @@ module.exports = class Renderer switch feature.style.type when "line" width = feature.style.paint['line-width']?.base*1.4 or 1 - @canvas.polyline points, colorCode, width for points in toDraw + @canvas.polyline points, colorCode, width when "fill" - @canvas.polygon toDraw, colorCode + @canvas.polygon points, colorCode - when "symbol" + when "symbola" text = feature.properties["name_"+@config.language] or feature.properties["name_en"] or feature.properties["name"] or @@ -240,60 +242,53 @@ module.exports = class Renderer #@config.icons[feature.properties.maki] or "◉" - # TODO: check in definition if points can actually own multiple geometries - for points in toDraw - for point in points - x = point[0] - text.length - margin = @config.layers[feature.layer]?.margin or @config.labelMargin + for point in points + x = point[0] - text.length + margin = @config.layers[feature.layer]?.margin or @config.labelMargin - if @labelBuffer.writeIfPossible text, x, point[1], feature, margin - @canvas.text text, x, point[1], colorCode - else if @config.layers[feature.layer]?.cluster and @labelBuffer.writeIfPossible "X", point[0], point[1], feature, 3 - @canvas.text "◉", point[0], point[1], colorCode + if @labelBuffer.writeIfPossible text, x, point[1], feature, margin + @canvas.text text, x, point[1], colorCode + else if @config.layers[feature.layer]?.cluster and @labelBuffer.writeIfPossible "X", point[0], point[1], feature, 3 + @canvas.text "◉", point[0], point[1], colorCode + _seen: {} _scaleAndReduce: (tile, feature) -> - reduced = [] - for points in feature.points - seen = {} - lastX = null - lastY = null + lastX = null + lastY = null + outside = false + scaled = [] - outside = null - scaled = [] + for point in feature.points + x = Math.floor tile.position.x+point.x/tile.scale + y = Math.floor tile.position.y+point.y/tile.scale - for point in points - x = Math.floor tile.position.x+point.x/tile.scale - y = Math.floor tile.position.y+point.y/tile.scale - - if lastX is x and lastY is y - continue - - lastY = y - lastX = x - - if x < -@tilePadding or - y < -@tilePadding or - x > @width+@tilePadding or - y > @height+@tilePadding - continue if outside - outside = true - else - if outside - outside = null - scaled.push [lastX, lastY] - - scaled.push [x, y] - - if scaled.length is 2 - if seen[ka = scaled[0].concat(scaled[1]).join '-'] or - seen[kb = scaled[1].concat(scaled[0]).join '-'] - continue - - seen[ka] = seen[kb] = true - - unless scaled.length > 1 or feature.type is "symbol" + if lastX is x and lastY is y continue - reduced.push scaled + lastY = y + lastX = x + # + # if x < -@tilePadding or + # y < -@tilePadding or + # x > @width+@tilePadding or + # y > @height+@tilePadding + # continue if outside + # outside = true + # else + # if outside + # outside = null + # scaled.push [lastX, lastY] - reduced + scaled.push [x, y] + + if scaled.length is 2 + if @_seen[ka = scaled[0].concat(scaled[1]).join '-'] or + @_seen[kb = scaled[1].concat(scaled[0]).join '-'] + return [] + + @_seen[ka] = @_seen[kb] = true + + unless scaled.length > 1 or feature.type is "symbol" + return [] + + scaled diff --git a/src/Termap.coffee b/src/Termap.coffee index 4dde0a3..b0cdade 100644 --- a/src/Termap.coffee +++ b/src/Termap.coffee @@ -24,9 +24,9 @@ module.exports = class Termap initialZoom: null maxZoom: 18 - zoomStep: 0.1 - + zoomStep: 0.25 headless: false + # size: # width: 40*2 # height: 10*4 diff --git a/src/Tile.coffee b/src/Tile.coffee index 2a8ffde..de52ec2 100644 --- a/src/Tile.coffee +++ b/src/Tile.coffee @@ -41,29 +41,37 @@ class Tile layers = {} for name, layer of tile.layers tree = rbush() - features = for i in [0...layer.length] + for i in [0...layer.length] # TODO: caching of similar attributes to avoid looking up the style each time #continue if @styler and not @styler.getStyleFor layer, feature feature = layer.feature i - feature.properties.$type = [undefined, "Point", "LineString", "Polygon"][feature.type] + feature.properties.$type = type = [undefined, "Point", "LineString", "Polygon"][feature.type] if @styler style = @styler.getStyleFor name, feature continue unless style - # TODO: monkey patching test case for tiles with a reduced extent - points = @_reduceGeometry feature, 8 + # TODO: monkey patching test case for tiles with a reduced extent 4096 / 8 -> 512 + # use feature.loadGeometry() again as soon as we got a 512 extent tileset + geometries = @_reduceGeometry feature, 8 - data = - style: style - points: points - properties: feature.properties - id: feature.id - layer: name + if style.type is "fill" + @_addToTree tree, + id: feature.id + layer: name + style: style + properties: feature.properties + points: geometries[0] - @_addToTree tree, data - data + else + for points in geometries + @_addToTree tree, + id: feature.id + layer: name + style: style + properties: feature.properties + points: points layers[name] = tree @@ -71,12 +79,12 @@ class Tile _addToTree: (tree, data) -> [minX, maxX, minY, maxY] = [Infinity, -Infinity, Infinity, -Infinity] - for outer in data.points - for p in outer - minX = p.x if p.x < minX - maxX = p.x if p.x > maxX - minY = p.y if p.y < minY - maxY = p.y if p.y > maxY + + for p in data.points + minX = p.x if p.x < minX + maxX = p.x if p.x > maxX + minY = p.y if p.y < minY + maxY = p.y if p.y > maxY data.minX = minX data.maxX = maxX @@ -89,7 +97,7 @@ class Tile for points, i in feature.loadGeometry() reduced = [] last = null - for point, j in points + for point in points p = x: Math.floor point.x/factor y: Math.floor point.y/factor diff --git a/styles/bright.json b/styles/bright.json index 74544ac..7b34752 100644 --- a/styles/bright.json +++ b/styles/bright.json @@ -33,45 +33,6 @@ "background-color": "@background" } }, - { - "type": "fill", - "id": "landuse_overlay_national_park", - "paint": { - "fill-color": "#d8e8c8" - }, - "source-layer": "landuse_overlay", - "filter": [ - "==", - "class", - "national_park" - ] - }, - { - "type": "fill", - "id": "landuse_park", - "paint": { - "fill-color": "#d8e8c8" - }, - "source-layer": "landuse", - "filter": [ - "==", - "class", - "park" - ] - }, - { - "type": "fill", - "id": "landuse_cemetery", - "paint": { - "fill-color": "#e0e4dd" - }, - "source-layer": "landuse", - "filter": [ - "==", - "class", - "cemetery" - ] - }, { "type": "fill", "id": "landuse_hospital", @@ -86,33 +47,6 @@ ] }, { - "type": "fill", - "id": "landuse_school", - "paint": { - "fill-color": "#f0e8f8" - }, - "source-layer": "landuse", - "filter": [ - "==", - "class", - "school" - ] - }, - { - "type": "fill", - "id": "landuse_wood", - "paint": { - "fill-color": "#6a4" - }, - "source-layer": "landuse", - "filter": [ - "==", - "class", - "wood" - ] - }, - { - "hide": true, "type": "line", "id": "waterway", "paint": {