Merge pull request #19 from rastapasta/stringwidth

Support for wide characters (Chinese/Japanese/...), cache, speed and general optimization
This commit is contained in:
Michael Straßburger 2017-05-10 14:40:56 +02:00 committed by GitHub
commit 82c05d6932
10 changed files with 106 additions and 51 deletions

View File

@ -10,6 +10,8 @@ A node.js based [Vector Tile](http://wiki.openstreetmap.org/wiki/Vector_tiles) t
$ telnet mapscii.me $ telnet mapscii.me
``` ```
If you're on Windows, use the open source telnet client [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) to connect.
## Features ## Features
* Use your mouse to drag and zoom in and out! * Use your mouse to drag and zoom in and out!
@ -106,8 +108,22 @@ If your terminal supports mouse events you can drag the map and use your scroll
* [lukasmartinelli](https://github.com/lukasmartinelli) & [manuelroth](https://github.com/manuelroth) for all their work on [OSM2VectorTiles](https://github.com/osm2vectortiles) (global vector tiles from [OSM Planet](https://wiki.openstreetmap.org/wiki/Planet.osm)) * [lukasmartinelli](https://github.com/lukasmartinelli) & [manuelroth](https://github.com/manuelroth) for all their work on [OSM2VectorTiles](https://github.com/osm2vectortiles) (global vector tiles from [OSM Planet](https://wiki.openstreetmap.org/wiki/Planet.osm))
* [mourner](https://github.com/mourner) for all his work on mindblowing GIS algorithms (like the used [earcut](https://github.com/mapbox/earcut), [rbush](https://github.com/mourner/rbush), [simplify-js](https://github.com/mourner/simplify-js), ..) * [mourner](https://github.com/mourner) for all his work on mindblowing GIS algorithms (like the used [earcut](https://github.com/mapbox/earcut), [rbush](https://github.com/mourner/rbush), [simplify-js](https://github.com/mourner/simplify-js), ..)
## License ## Licenses
### Map data
#### The Open Data Commons Open Database License (oDbl)
[OpenStreetMap](https://www.openstreetmap.org) is open data, licensed under the [Open Data Commons Open Database License](http://opendatacommons.org/licenses/odbl/) (ODbL) by the [OpenStreetMap Foundation](http://osmfoundation.org/) (OSMF).
You are free to copy, distribute, transmit and adapt our data, as long as you credit OpenStreetMap and its contributors. If you alter or build upon our data, you may distribute the result only under the same licence. The full [legal code](http://opendatacommons.org/licenses/odbl/1.0/) explains your rights and responsibilities.
The cartography in our map tiles, and our documentation, are licenced under the [Creative Commons Attribution-ShareAlike 2.0](http://creativecommons.org/licenses/by-sa/2.0/) licence (CC BY-SA).
### MapSCII
#### The MIT License (MIT) #### The MIT License (MIT)
Copyright (c) 2017 Michael Straßburger Copyright (c) 2017 Michael Straßburger
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@ -38,6 +38,7 @@
"pbf": "^3.0.0", "pbf": "^3.0.0",
"rbush": "^2.0.1", "rbush": "^2.0.1",
"simplify-js": "^1.2.1", "simplify-js": "^1.2.1",
"string-width": "^2.0.0",
"term-mouse": "^0.1.1", "term-mouse": "^0.1.1",
"userhome": "^1.0.0", "userhome": "^1.0.0",
"vector-tile": "^1.3.0", "vector-tile": "^1.3.0",

View File

@ -13,6 +13,8 @@
Will either be merged into node-drawille or become an own module at some point Will either be merged into node-drawille or become an own module at some point
### ###
stringWidth = require 'string-width'
config = require './config'
module.exports = class BrailleBuffer module.exports = class BrailleBuffer
characterMap: [[0x1, 0x8],[0x2, 0x10],[0x4, 0x20],[0x40, 0x80]] characterMap: [[0x1, 0x8],[0x2, 0x10],[0x4, 0x20],[0x40, 0x80]]
@ -78,20 +80,34 @@ module.exports = class BrailleBuffer
frame: -> frame: ->
output = [] output = []
currentColor = null currentColor = null
delimeter = "\n" skip = 0
for idx in [0...@pixelBuffer.length] for y in [0...@height/4]
output.push delimeter if idx and (idx % (@width/2)) is 0 skip = 0
if currentColor isnt colorCode = @_termColor @foregroundBuffer[idx], @backgroundBuffer[idx] for x in [0...@width/2]
output.push currentColor = colorCode idx = y*@width/2 + x
output.push if @charBuffer[idx] if idx and not x
@charBuffer[idx] output.push config.delimeter
else
String.fromCharCode 0x2800+@pixelBuffer[idx]
output.push @termReset+delimeter if currentColor isnt colorCode = @_termColor @foregroundBuffer[idx], @backgroundBuffer[idx]
output.push currentColor = colorCode
output.push if char = @charBuffer[idx]
skip += stringWidth(char)-1
if skip+x >= @width/2
''
else
char
else
if not skip
String.fromCharCode 0x2800+@pixelBuffer[idx]
else
skip--
''
output.push @termReset+config.delimeter
output.join '' output.join ''
setChar: (char, x, y, color) -> setChar: (char, x, y, color) ->

View File

@ -5,8 +5,8 @@
Using 2D spatial indexing to avoid overlapping labels and markers Using 2D spatial indexing to avoid overlapping labels and markers
and to find labels underneath a mouse cursor's position and to find labels underneath a mouse cursor's position
### ###
rbush = require 'rbush' rbush = require 'rbush'
stringWidth = require 'string-width'
module.exports = class LabelBuffer module.exports = class LabelBuffer
tree: null tree: null
@ -41,5 +41,5 @@ module.exports = class LabelBuffer
_calculateArea: (text, x, y, margin = 0) -> _calculateArea: (text, x, y, margin = 0) ->
minX: x-margin minX: x-margin
minY: y-margin/2 minY: y-margin/2
maxX: x+margin+text.length maxX: x+margin+stringWidth(text)
maxY: y+margin/2 maxY: y+margin/2

View File

@ -55,6 +55,7 @@ module.exports = class Mapscii
.then => .then =>
@_draw() @_draw()
.then => @notify("Welcome to MapSCII! Use your cursors to navigate, a/z to zoom, q to quit.")
_initTileSource: -> _initTileSource: ->
@tileSource = new TileSource() @tileSource = new TileSource()
@ -62,7 +63,7 @@ module.exports = class Mapscii
_initKeyboard: -> _initKeyboard: ->
keypress config.input keypress config.input
config.input.setRawMode true config.input.setRawMode true if config.input.setRawMode
config.input.resume() config.input.resume()
config.input.on 'keypress', (ch, key) => @_onKey key config.input.on 'keypress', (ch, key) => @_onKey key
@ -108,9 +109,11 @@ module.exports = class Mapscii
z = utils.baseZoom @zoom z = utils.baseZoom @zoom
center = utils.ll2tile @center.lon, @center.lat, z center = utils.ll2tile @center.lon, @center.lat, z
@mousePosition = utils.tile2ll center.x+(dx/size), center.y+(dy/size), z
@mousePosition = utils.normalize utils.tile2ll center.x+(dx/size), center.y+(dy/size), z
_onClick: (event) -> _onClick: (event) ->
return if event.x < 0 or event.x > @width/2 or event.y < 0 or event.y > @height/4
@_updateMousePosition event @_updateMousePosition event
if @mouseDragging and event.button is "left" if @mouseDragging and event.button is "left"
@ -127,6 +130,9 @@ module.exports = class Mapscii
@_draw() @_draw()
_onMouseMove: (event) -> _onMouseMove: (event) ->
return if event.x < 0 or event.x > @width/2 or event.y < 0 or event.y > @height/4
return if config.mouseCallback and not config.mouseCallback event
# start dragging # start dragging
if event.button is "left" if event.button is "left"
if @mouseDragging if @mouseDragging
@ -153,16 +159,20 @@ module.exports = class Mapscii
@notify @_getFooter() @notify @_getFooter()
_onKey: (key) -> _onKey: (key) ->
if config.keyCallback and not config.keyCallback key
return
# check if the pressed key is configured # check if the pressed key is configured
draw = switch key?.name draw = switch key?.name
when "q" when "q"
process.exit 0 if config.quitCallback
config.quitCallback()
when "w" then @zoomy = 1 else
when "s" then @zoomy = -1 process.exit 0
when "a" then @zoomBy config.zoomStep when "a" then @zoomBy config.zoomStep
when "z" then @zoomBy -config.zoomStep when "z", "y"
@zoomBy -config.zoomStep
when "left" then @moveBy 0, -8/Math.pow(2, @zoom) when "left" then @moveBy 0, -8/Math.pow(2, @zoom)
when "right" then @moveBy 0, 8/Math.pow(2, @zoom) when "right" then @moveBy 0, 8/Math.pow(2, @zoom)
@ -174,9 +184,6 @@ module.exports = class Mapscii
if draw isnt null if draw isnt null
@_draw() @_draw()
else
# display debug info for unhandled keys
@notify JSON.stringify key
_draw: -> _draw: ->
@renderer @renderer
@ -186,13 +193,6 @@ module.exports = class Mapscii
@notify @_getFooter() @notify @_getFooter()
.catch => .catch =>
@notify "renderer is busy" @notify "renderer is busy"
.then =>
if @zoomy
if (@zoomy > 0 and @zoom < config.maxZoom) or (@zoomy < 0 and @zoom > @minZoom)
@zoom += @zoomy * config.zoomStep
else
@zoomy *= -1
setImmediate => @_draw()
_getFooter: -> _getFooter: ->
# tile = utils.ll2tile @center.lon, @center.lat, @zoom # tile = utils.ll2tile @center.lon, @center.lat, @zoom
@ -203,6 +203,7 @@ module.exports = class Mapscii
"mouse: #{utils.digits @mousePosition.lat, 3}, #{utils.digits @mousePosition.lon, 3} " "mouse: #{utils.digits @mousePosition.lat, 3}, #{utils.digits @mousePosition.lon, 3} "
notify: (text) -> notify: (text) ->
config.onUpdate() if config.onUpdate
@_write "\r\x1B[K"+text unless config.headless @_write "\r\x1B[K"+text unless config.headless
_write: (output) -> _write: (output) ->
@ -218,11 +219,4 @@ module.exports = class Mapscii
@setCenter @center.lat+lat, @center.lon+lon @setCenter @center.lat+lat, @center.lon+lon
setCenter: (lat, lon) -> setCenter: (lat, lon) ->
lon += 360 if lon < -180 @center = utils.normalize lon: lon, lat: lat
lon -= 360 if lon > 180
lat = 85.0511 if lat > 85.0511
lat = -85.0511 if lat < -85.0511
@center.lat = lat
@center.lon = lon

View File

@ -140,10 +140,7 @@ module.exports = class Renderer
@_drawFeature tile, feature, layer.scale @_drawFeature tile, feature, layer.scale
labels.sort (a, b) -> labels.sort (a, b) ->
if a.feature.properties.localrank a.feature.sorty-b.feature.sort
a.feature.properties.localrank-b.feature.properties.localrank
else
a.feature.properties.scalerank-b.feature.properties.scalerank
for label in labels for label in labels
@_drawFeature label.tile, label.feature, label.scale @_drawFeature label.tile, label.feature, label.scale
@ -178,10 +175,8 @@ module.exports = class Renderer
@canvas.polygon points, feature.color @canvas.polygon points, feature.color
when "symbol" when "symbol"
text = feature.properties["name_"+config.language] or genericSymbol = null
feature.properties["name_en"] or text = feature.label or
feature.properties["name"] or
feature.properties.house_num or
genericSymbol = "" genericSymbol = ""
return false if @_seen[text] and not genericSymbol return false if @_seen[text] and not genericSymbol

View File

@ -13,6 +13,7 @@ rbush = require 'rbush'
x256 = require 'x256' x256 = require 'x256'
earcut = require 'earcut' earcut = require 'earcut'
config = require "./config"
utils = require "./utils" utils = require "./utils"
class Tile class Tile
@ -74,22 +75,34 @@ class Tile
# use feature.loadGeometry() again as soon as we got a 512 extent tileset # use feature.loadGeometry() again as soon as we got a 512 extent tileset
geometries = feature.loadGeometry() #@_reduceGeometry feature, 8 geometries = feature.loadGeometry() #@_reduceGeometry feature, 8
sort = feature.properties.localrank or feature.properties.scalerank
label = if style.type is "symbol"
feature.properties["name_"+config.language] or
feature.properties.name_en or
feature.properties.name or
feature.properties.house_num
else
undefined
if style.type is "fill" if style.type is "fill"
nodes.push @_addBoundaries true, nodes.push @_addBoundaries true,
id: feature.id # id: feature.id
layer: name layer: name
style: style style: style
properties: feature.properties label: label
sort: sort
points: geometries points: geometries
color: colorCode color: colorCode
else else
for points in geometries for points in geometries
nodes.push @_addBoundaries false, nodes.push @_addBoundaries false,
id: feature.id # id: feature.id
layer: name layer: name
style: style style: style
properties: feature.properties label: label
sort: sort
points: points points: points
color: colorCode color: colorCode

View File

@ -25,6 +25,9 @@ catch
module.exports = class TileSource module.exports = class TileSource
cache: {} cache: {}
cacheSize: 16
cached: []
modes: modes:
MBTiles: 1 MBTiles: 1
VectorTile: 2 VectorTile: 2
@ -67,6 +70,10 @@ module.exports = class TileSource
if cached = @cache[[z,x,y].join("-")] if cached = @cache[[z,x,y].join("-")]
return Promise.resolve cached return Promise.resolve cached
if @cached.length > @cacheSize
for tile in @cached.splice 0, Math.abs(@cacheSize-@cached.length)
delete @cache[tile]
switch @mode switch @mode
when @modes.MBTiles then @_getMBTile z, x, y when @modes.MBTiles then @_getMBTile z, x, y
when @modes.HTTP then @_getHTTP z, x, y when @modes.HTTP then @_getHTTP z, x, y
@ -93,7 +100,10 @@ module.exports = class TileSource
resolve @_createTile z, x, y, buffer resolve @_createTile z, x, y, buffer
_createTile: (z, x, y, buffer) -> _createTile: (z, x, y, buffer) ->
tile = @cache[[z,x,y].join("-")] = new Tile @styler name = [z,x,y].join("-")
@cached.push name
tile = @cache[name] = new Tile @styler
tile.load buffer tile.load buffer
_initPersistence: -> _initPersistence: ->

View File

@ -33,3 +33,5 @@ module.exports =
output: process.stdout output: process.stdout
headless: false headless: false
delimeter: "\n\r"

View File

@ -55,5 +55,13 @@ utils =
digits: (number, digits) -> digits: (number, digits) ->
Math.floor(number*Math.pow(10, digits))/Math.pow(10, digits) Math.floor(number*Math.pow(10, digits))/Math.pow(10, digits)
normalize: (ll) ->
ll.lon += 360 if ll.lon < -180
ll.lon -= 360 if ll.lon > 180
ll.lat = 85.0511 if ll.lat > 85.0511
ll.lat = -85.0511 if ll.lat < -85.0511
ll
module.exports = utils module.exports = utils