Merge pull request #19 from rastapasta/stringwidth

Support for wide characters (Chinese/Japanese/...), cache, speed and general optimization
pull/21/head
Michael Straßburger 2017-05-10 14:40:56 +02:00 zatwierdzone przez GitHub
commit 82c05d6932
10 zmienionych plików z 106 dodań i 51 usunięć

Wyświetl plik

@ -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:

Wyświetl plik

@ -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",

Wyświetl plik

@ -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
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] if currentColor isnt colorCode = @_termColor @foregroundBuffer[idx], @backgroundBuffer[idx]
output.push currentColor = colorCode output.push currentColor = colorCode
output.push if @charBuffer[idx] output.push if char = @charBuffer[idx]
@charBuffer[idx] skip += stringWidth(char)-1
if skip+x >= @width/2
''
else else
char
else
if not skip
String.fromCharCode 0x2800+@pixelBuffer[idx] String.fromCharCode 0x2800+@pixelBuffer[idx]
else
skip--
''
output.push @termReset+delimeter output.push @termReset+config.delimeter
output.join '' output.join ''
setChar: (char, x, y, color) -> setChar: (char, x, y, color) ->

Wyświetl plik

@ -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

Wyświetl plik

@ -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"
if config.quitCallback
config.quitCallback()
else
process.exit 0 process.exit 0
when "w" then @zoomy = 1
when "s" then @zoomy = -1
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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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: ->

Wyświetl plik

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

Wyświetl plik

@ -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