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