mirror of
https://github.com/rastapasta/mapscii.git
synced 2025-04-01 09:06:16 +02:00
Merge pull request #19 from rastapasta/stringwidth
Support for wide characters (Chinese/Japanese/...), cache, speed and general optimization
This commit is contained in:
commit
82c05d6932
18
README.md
18
README.md
@ -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:
|
||||||
|
@ -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",
|
||||||
|
@ -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) ->
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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: ->
|
||||||
|
@ -33,3 +33,5 @@ module.exports =
|
|||||||
output: process.stdout
|
output: process.stdout
|
||||||
|
|
||||||
headless: false
|
headless: false
|
||||||
|
|
||||||
|
delimeter: "\n\r"
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user