mirror of
https://github.com/rastapasta/mapscii.git
synced 2024-11-21 23:53:08 +01:00
🚱 bringing polygon rendering back, todo: holes
This commit is contained in:
parent
cfd8e24342
commit
e69321dfb6
11
README.md
11
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.
|
||||||
|
|
||||||
<img src="http://i.imgur.com/yYVt7No.png" width="100%" />
|
<img src="http://i.imgur.com/yYVt7No.png" width="100%" />
|
||||||
|
|
||||||
* Discover the globe or zoom in to explore your neighbourhood
|
* Discover the globe or zoom in to explore your neighbourhood
|
||||||
* See Point-of-Interest around any given location
|
* 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/)
|
* 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:
|
* 100% pure Coffee-/JavaScript! :sunglasses:
|
||||||
|
|
||||||
## How to install
|
## 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] filled polygons
|
||||||
* [x] convert polygons to triangles
|
* [x] convert polygons to triangles
|
||||||
* [x] use triangulation for filling
|
* [x] use triangulation for filling
|
||||||
* [ ] respect fill/line style file based setting
|
* [x] respect fill/line style file based setting
|
||||||
|
|
||||||
* Tile
|
* Tile
|
||||||
* [x] directly throw away features that aren't covered by any style
|
* [x] directly throw away features that aren't covered by any style
|
||||||
|
@ -51,25 +51,19 @@ module.exports = class Canvas
|
|||||||
|
|
||||||
xs = {}
|
xs = {}
|
||||||
ys = {}
|
ys = {}
|
||||||
|
#
|
||||||
|
# for points in polylines
|
||||||
|
# if vertices.length
|
||||||
|
# continue
|
||||||
|
# holes.push vertices.length/2
|
||||||
|
|
||||||
for points in polylines
|
for point in polylines
|
||||||
if vertices.length
|
vertices = vertices.concat point
|
||||||
continue
|
xs[point[0]] = ys[point[1]] = true
|
||||||
holes.push vertices.length/2
|
|
||||||
|
|
||||||
lastPoint = [-1, -1]
|
# Check if we actually got a valid polygon after projection and clamping
|
||||||
for point in points
|
if Object.keys(xs).length is 1 or Object.keys(ys).length is 1
|
||||||
vertices = vertices.concat point[0], point[1]
|
return false
|
||||||
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
|
|
||||||
|
|
||||||
try
|
try
|
||||||
triangles = earcut vertices, holes
|
triangles = earcut vertices, holes
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
The Console Vector Tile renderer - bäm!
|
The Console Vector Tile renderer - bäm!
|
||||||
###
|
###
|
||||||
x256 = require 'x256'
|
x256 = require 'x256'
|
||||||
mercator = new (require('sphericalmercator'))()
|
|
||||||
tilebelt = require 'tilebelt'
|
tilebelt = require 'tilebelt'
|
||||||
Promise = require 'bluebird'
|
Promise = require 'bluebird'
|
||||||
|
|
||||||
@ -98,6 +97,7 @@ module.exports = class Renderer
|
|||||||
@isDrawing = true
|
@isDrawing = true
|
||||||
|
|
||||||
@labelBuffer.clear()
|
@labelBuffer.clear()
|
||||||
|
@_seen = {}
|
||||||
|
|
||||||
if color = @styler.styleById['background']?.paint['background-color']
|
if color = @styler.styleById['background']?.paint['background-color']
|
||||||
@canvas.setBackground x256 utils.hex2rgb color
|
@canvas.setBackground x256 utils.hex2rgb color
|
||||||
@ -173,6 +173,7 @@ module.exports = class Renderer
|
|||||||
maxY: (@height-position.y)*scale
|
maxY: (@height-position.y)*scale
|
||||||
|
|
||||||
features = {}
|
features = {}
|
||||||
|
|
||||||
for layer in @config.drawOrder
|
for layer in @config.drawOrder
|
||||||
continue unless tile.data.layers?[layer]
|
continue unless tile.data.layers?[layer]
|
||||||
features[layer] = tile.data.layers[layer].search box
|
features[layer] = tile.data.layers[layer].search box
|
||||||
@ -186,7 +187,6 @@ module.exports = class Renderer
|
|||||||
for layer in @config.drawOrder
|
for layer in @config.drawOrder
|
||||||
for tile in tiles
|
for tile in tiles
|
||||||
continue unless tile.features[layer]?.length
|
continue unless tile.features[layer]?.length
|
||||||
|
|
||||||
for feature in tile.features[layer]
|
for feature in tile.features[layer]
|
||||||
# continue if feature.id and drawn[feature.id]
|
# continue if feature.id and drawn[feature.id]
|
||||||
# drawn[feature.id] = true
|
# drawn[feature.id] = true
|
||||||
@ -208,10 +208,12 @@ module.exports = class Renderer
|
|||||||
@config.tileSize / @config.projectSize / Math.pow(2, zoom-baseZoom)
|
@config.tileSize / @config.projectSize / Math.pow(2, zoom-baseZoom)
|
||||||
|
|
||||||
_drawFeature: (tile, feature) ->
|
_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
|
points = @_scaleAndReduce tile, feature
|
||||||
return false unless toDraw.length
|
unless points.length
|
||||||
|
return false
|
||||||
|
|
||||||
color =
|
color =
|
||||||
feature.style.paint['line-color'] or
|
feature.style.paint['line-color'] or
|
||||||
@ -227,12 +229,12 @@ module.exports = class Renderer
|
|||||||
switch feature.style.type
|
switch feature.style.type
|
||||||
when "line"
|
when "line"
|
||||||
width = feature.style.paint['line-width']?.base*1.4 or 1
|
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"
|
when "fill"
|
||||||
@canvas.polygon toDraw, colorCode
|
@canvas.polygon points, colorCode
|
||||||
|
|
||||||
when "symbol"
|
when "symbola"
|
||||||
text = feature.properties["name_"+@config.language] or
|
text = feature.properties["name_"+@config.language] or
|
||||||
feature.properties["name_en"] or
|
feature.properties["name_en"] or
|
||||||
feature.properties["name"] or
|
feature.properties["name"] or
|
||||||
@ -240,60 +242,53 @@ module.exports = class Renderer
|
|||||||
#@config.icons[feature.properties.maki] or
|
#@config.icons[feature.properties.maki] or
|
||||||
"◉"
|
"◉"
|
||||||
|
|
||||||
# TODO: check in definition if points can actually own multiple geometries
|
for point in points
|
||||||
for points in toDraw
|
x = point[0] - text.length
|
||||||
for point in points
|
margin = @config.layers[feature.layer]?.margin or @config.labelMargin
|
||||||
x = point[0] - text.length
|
|
||||||
margin = @config.layers[feature.layer]?.margin or @config.labelMargin
|
|
||||||
|
|
||||||
if @labelBuffer.writeIfPossible text, x, point[1], feature, margin
|
if @labelBuffer.writeIfPossible text, x, point[1], feature, margin
|
||||||
@canvas.text text, x, point[1], colorCode
|
@canvas.text text, x, point[1], colorCode
|
||||||
else if @config.layers[feature.layer]?.cluster and @labelBuffer.writeIfPossible "X", point[0], point[1], feature, 3
|
else if @config.layers[feature.layer]?.cluster and @labelBuffer.writeIfPossible "X", point[0], point[1], feature, 3
|
||||||
@canvas.text "◉", point[0], point[1], colorCode
|
@canvas.text "◉", point[0], point[1], colorCode
|
||||||
|
|
||||||
|
_seen: {}
|
||||||
_scaleAndReduce: (tile, feature) ->
|
_scaleAndReduce: (tile, feature) ->
|
||||||
reduced = []
|
lastX = null
|
||||||
for points in feature.points
|
lastY = null
|
||||||
seen = {}
|
outside = false
|
||||||
lastX = null
|
scaled = []
|
||||||
lastY = null
|
|
||||||
|
|
||||||
outside = null
|
for point in feature.points
|
||||||
scaled = []
|
x = Math.floor tile.position.x+point.x/tile.scale
|
||||||
|
y = Math.floor tile.position.y+point.y/tile.scale
|
||||||
|
|
||||||
for point in points
|
if lastX is x and lastY is y
|
||||||
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"
|
|
||||||
continue
|
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
|
||||||
|
@ -24,9 +24,9 @@ module.exports = class Termap
|
|||||||
|
|
||||||
initialZoom: null
|
initialZoom: null
|
||||||
maxZoom: 18
|
maxZoom: 18
|
||||||
zoomStep: 0.1
|
zoomStep: 0.25
|
||||||
|
|
||||||
headless: false
|
headless: false
|
||||||
|
|
||||||
# size:
|
# size:
|
||||||
# width: 40*2
|
# width: 40*2
|
||||||
# height: 10*4
|
# height: 10*4
|
||||||
|
@ -41,29 +41,37 @@ class Tile
|
|||||||
layers = {}
|
layers = {}
|
||||||
for name, layer of tile.layers
|
for name, layer of tile.layers
|
||||||
tree = rbush()
|
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
|
# TODO: caching of similar attributes to avoid looking up the style each time
|
||||||
#continue if @styler and not @styler.getStyleFor layer, feature
|
#continue if @styler and not @styler.getStyleFor layer, feature
|
||||||
|
|
||||||
feature = layer.feature i
|
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
|
if @styler
|
||||||
style = @styler.getStyleFor name, feature
|
style = @styler.getStyleFor name, feature
|
||||||
continue unless style
|
continue unless style
|
||||||
|
|
||||||
# TODO: monkey patching test case for tiles with a reduced extent
|
# TODO: monkey patching test case for tiles with a reduced extent 4096 / 8 -> 512
|
||||||
points = @_reduceGeometry feature, 8
|
# use feature.loadGeometry() again as soon as we got a 512 extent tileset
|
||||||
|
geometries = @_reduceGeometry feature, 8
|
||||||
|
|
||||||
data =
|
if style.type is "fill"
|
||||||
style: style
|
@_addToTree tree,
|
||||||
points: points
|
id: feature.id
|
||||||
properties: feature.properties
|
layer: name
|
||||||
id: feature.id
|
style: style
|
||||||
layer: name
|
properties: feature.properties
|
||||||
|
points: geometries[0]
|
||||||
|
|
||||||
@_addToTree tree, data
|
else
|
||||||
data
|
for points in geometries
|
||||||
|
@_addToTree tree,
|
||||||
|
id: feature.id
|
||||||
|
layer: name
|
||||||
|
style: style
|
||||||
|
properties: feature.properties
|
||||||
|
points: points
|
||||||
|
|
||||||
layers[name] = tree
|
layers[name] = tree
|
||||||
|
|
||||||
@ -71,12 +79,12 @@ class Tile
|
|||||||
|
|
||||||
_addToTree: (tree, data) ->
|
_addToTree: (tree, data) ->
|
||||||
[minX, maxX, minY, maxY] = [Infinity, -Infinity, Infinity, -Infinity]
|
[minX, maxX, minY, maxY] = [Infinity, -Infinity, Infinity, -Infinity]
|
||||||
for outer in data.points
|
|
||||||
for p in outer
|
for p in data.points
|
||||||
minX = p.x if p.x < minX
|
minX = p.x if p.x < minX
|
||||||
maxX = p.x if p.x > maxX
|
maxX = p.x if p.x > maxX
|
||||||
minY = p.y if p.y < minY
|
minY = p.y if p.y < minY
|
||||||
maxY = p.y if p.y > maxY
|
maxY = p.y if p.y > maxY
|
||||||
|
|
||||||
data.minX = minX
|
data.minX = minX
|
||||||
data.maxX = maxX
|
data.maxX = maxX
|
||||||
@ -89,7 +97,7 @@ class Tile
|
|||||||
for points, i in feature.loadGeometry()
|
for points, i in feature.loadGeometry()
|
||||||
reduced = []
|
reduced = []
|
||||||
last = null
|
last = null
|
||||||
for point, j in points
|
for point in points
|
||||||
p =
|
p =
|
||||||
x: Math.floor point.x/factor
|
x: Math.floor point.x/factor
|
||||||
y: Math.floor point.y/factor
|
y: Math.floor point.y/factor
|
||||||
|
@ -33,45 +33,6 @@
|
|||||||
"background-color": "@background"
|
"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",
|
"type": "fill",
|
||||||
"id": "landuse_hospital",
|
"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",
|
"type": "line",
|
||||||
"id": "waterway",
|
"id": "waterway",
|
||||||
"paint": {
|
"paint": {
|
||||||
|
Loading…
Reference in New Issue
Block a user