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
```
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
* 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))
* [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)
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:

View File

@ -38,6 +38,7 @@
"pbf": "^3.0.0",
"rbush": "^2.0.1",
"simplify-js": "^1.2.1",
"string-width": "^2.0.0",
"term-mouse": "^0.1.1",
"userhome": "^1.0.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
###
stringWidth = require 'string-width'
config = require './config'
module.exports = class BrailleBuffer
characterMap: [[0x1, 0x8],[0x2, 0x10],[0x4, 0x20],[0x40, 0x80]]
@ -78,20 +80,34 @@ module.exports = class BrailleBuffer
frame: ->
output = []
currentColor = null
delimeter = "\n"
skip = 0
for idx in [0...@pixelBuffer.length]
output.push delimeter if idx and (idx % (@width/2)) is 0
for y in [0...@height/4]
skip = 0
for x in [0...@width/2]
idx = y*@width/2 + x
if idx and not x
output.push config.delimeter
if currentColor isnt colorCode = @_termColor @foregroundBuffer[idx], @backgroundBuffer[idx]
output.push currentColor = colorCode
output.push if @charBuffer[idx]
@charBuffer[idx]
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+delimeter
output.push @termReset+config.delimeter
output.join ''
setChar: (char, x, y, color) ->

View File

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

View File

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

View File

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

View File

@ -13,6 +13,7 @@ rbush = require 'rbush'
x256 = require 'x256'
earcut = require 'earcut'
config = require "./config"
utils = require "./utils"
class Tile
@ -74,22 +75,34 @@ class Tile
# use feature.loadGeometry() again as soon as we got a 512 extent tileset
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"
nodes.push @_addBoundaries true,
id: feature.id
# id: feature.id
layer: name
style: style
properties: feature.properties
label: label
sort: sort
points: geometries
color: colorCode
else
for points in geometries
nodes.push @_addBoundaries false,
id: feature.id
# id: feature.id
layer: name
style: style
properties: feature.properties
label: label
sort: sort
points: points
color: colorCode

View File

@ -25,6 +25,9 @@ catch
module.exports = class TileSource
cache: {}
cacheSize: 16
cached: []
modes:
MBTiles: 1
VectorTile: 2
@ -67,6 +70,10 @@ module.exports = class TileSource
if cached = @cache[[z,x,y].join("-")]
return Promise.resolve cached
if @cached.length > @cacheSize
for tile in @cached.splice 0, Math.abs(@cacheSize-@cached.length)
delete @cache[tile]
switch @mode
when @modes.MBTiles then @_getMBTile 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
_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
_initPersistence: ->

View File

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

View File

@ -55,5 +55,13 @@ utils =
digits: (number, 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