diff --git a/README.md b/README.md index 438d3b0..0af58aa 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/package.json b/package.json index a00fb0d..ba16cea 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/BrailleBuffer.coffee b/src/BrailleBuffer.coffee index f46f41b..489fa79 100644 --- a/src/BrailleBuffer.coffee +++ b/src/BrailleBuffer.coffee @@ -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 - if currentColor isnt colorCode = @_termColor @foregroundBuffer[idx], @backgroundBuffer[idx] - output.push currentColor = colorCode + for x in [0...@width/2] + idx = y*@width/2 + x - output.push if @charBuffer[idx] - @charBuffer[idx] - else - String.fromCharCode 0x2800+@pixelBuffer[idx] + if idx and not x + output.push config.delimeter - 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 '' setChar: (char, x, y, color) -> diff --git a/src/LabelBuffer.coffee b/src/LabelBuffer.coffee index fec3a1f..cd07a36 100644 --- a/src/LabelBuffer.coffee +++ b/src/LabelBuffer.coffee @@ -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 diff --git a/src/Mapscii.coffee b/src/Mapscii.coffee index ef1ef24..fedcfa8 100644 --- a/src/Mapscii.coffee +++ b/src/Mapscii.coffee @@ -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" - process.exit 0 - - when "w" then @zoomy = 1 - when "s" then @zoomy = -1 + if config.quitCallback + config.quitCallback() + else + process.exit 0 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 diff --git a/src/Renderer.coffee b/src/Renderer.coffee index 51da171..209ed83 100644 --- a/src/Renderer.coffee +++ b/src/Renderer.coffee @@ -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 diff --git a/src/Tile.coffee b/src/Tile.coffee index 1702fb9..45f5631 100644 --- a/src/Tile.coffee +++ b/src/Tile.coffee @@ -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 diff --git a/src/TileSource.coffee b/src/TileSource.coffee index b30485f..dd0e100 100644 --- a/src/TileSource.coffee +++ b/src/TileSource.coffee @@ -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: -> diff --git a/src/config.coffee b/src/config.coffee index 6cd6bc9..45fd8cc 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -33,3 +33,5 @@ module.exports = output: process.stdout headless: false + + delimeter: "\n\r" \ No newline at end of file diff --git a/src/utils.coffee b/src/utils.coffee index bf6fdfc..57f08e0 100644 --- a/src/utils.coffee +++ b/src/utils.coffee @@ -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