mirror of
https://github.com/rastapasta/mapscii.git
synced 2024-11-28 19:13:42 +01:00
🌲 using r-tree spatial indexing to declutter the labels
This commit is contained in:
parent
8f3de4a159
commit
014eab2b70
@ -30,6 +30,7 @@ Discover the world in your console!
|
|||||||
* [`node-mbtiles`](https://github.com/mapbox/node-mbtiles) for MBTiles parsing
|
* [`node-mbtiles`](https://github.com/mapbox/node-mbtiles) for MBTiles parsing
|
||||||
* [`pbf`](https://github.com/mapbox/pbf) for Protobuf decoding
|
* [`pbf`](https://github.com/mapbox/pbf) for Protobuf decoding
|
||||||
* [`vector-tile-js`](https://github.com/mapbox/vector-tile-js) for [VectorTile](https://github.com/mapbox/vector-tile-spec/tree/master/2.1) parsing
|
* [`vector-tile-js`](https://github.com/mapbox/vector-tile-js) for [VectorTile](https://github.com/mapbox/vector-tile-spec/tree/master/2.1) parsing
|
||||||
|
* [`rbush`](https://github.com/mourner/rbush) for 2D spatial indexing
|
||||||
* [`sphericalmercator`](https://github.com/mapbox/node-sphericalmercator) for EPSG:3857 <> WGS84 conversions
|
* [`sphericalmercator`](https://github.com/mapbox/node-sphericalmercator) for EPSG:3857 <> WGS84 conversions
|
||||||
|
|
||||||
## Wishlist
|
## Wishlist
|
||||||
@ -41,8 +42,11 @@ Discover the world in your console!
|
|||||||
* [ ] mapping of view to tiles to show
|
* [ ] mapping of view to tiles to show
|
||||||
* [ ] label drawing
|
* [ ] label drawing
|
||||||
* [x] support for point labels
|
* [x] support for point labels
|
||||||
* [ ] dynamic decluttering of labels
|
* [x] dynamic decluttering of labels
|
||||||
|
* [ ] centering text labels
|
||||||
* [ ] lat/lng-center + zoom based viewport
|
* [ ] lat/lng-center + zoom based viewport
|
||||||
|
* [ ] bbox awareness
|
||||||
|
* [ ] zoom -> scale calculation
|
||||||
* [ ] TileSource class (abstracting URL, mbtiles, single vector tile source)
|
* [ ] TileSource class (abstracting URL, mbtiles, single vector tile source)
|
||||||
* [ ] tile request system
|
* [ ] tile request system
|
||||||
* [ ] from local mbtiles
|
* [ ] from local mbtiles
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
"drawille-canvas-blessed-contrib": "^0.1.3",
|
"drawille-canvas-blessed-contrib": "^0.1.3",
|
||||||
"keypress": "^0.2.1",
|
"keypress": "^0.2.1",
|
||||||
"pbf": "^3.0.0",
|
"pbf": "^3.0.0",
|
||||||
|
"rbush": "^2.0.1",
|
||||||
"sphericalmercator": "^1.0.5",
|
"sphericalmercator": "^1.0.5",
|
||||||
"term-mouse": "^0.1.1",
|
"term-mouse": "^0.1.1",
|
||||||
"vector-tile": "^1.3.0"
|
"vector-tile": "^1.3.0"
|
||||||
|
27
src/LabelBuffer.coffee
Normal file
27
src/LabelBuffer.coffee
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
rbush = require 'rbush'
|
||||||
|
|
||||||
|
module.exports = class LabelBuffer
|
||||||
|
tree: null
|
||||||
|
margin: 1
|
||||||
|
|
||||||
|
constructor: (@width, @height) ->
|
||||||
|
@tree = rbush()
|
||||||
|
|
||||||
|
project: (x, y) ->
|
||||||
|
[Math.floor(x/2), Math.floor(y/4)]
|
||||||
|
|
||||||
|
writeIfPossible: (text, x, y) ->
|
||||||
|
point = @project x, y
|
||||||
|
|
||||||
|
return false unless @_hasSpace text, point[0], point[1]
|
||||||
|
@tree.insert @_calculateArea text, point[0], point[1]
|
||||||
|
true
|
||||||
|
|
||||||
|
_hasSpace: (text, x, y) ->
|
||||||
|
not @tree.collides @_calculateArea text, x, y
|
||||||
|
|
||||||
|
_calculateArea: (text, x, y) ->
|
||||||
|
minX: x-@margin
|
||||||
|
minY: y-@margin
|
||||||
|
maxX: x+@margin+text.length
|
||||||
|
maxY: y+@margin
|
@ -6,6 +6,7 @@ fs = require 'fs'
|
|||||||
zlib = require 'zlib'
|
zlib = require 'zlib'
|
||||||
TermMouse = require 'term-mouse'
|
TermMouse = require 'term-mouse'
|
||||||
mercator = new (require('sphericalmercator'))()
|
mercator = new (require('sphericalmercator'))()
|
||||||
|
LabelBuffer = require __dirname+'/src/LabelBuffer'
|
||||||
|
|
||||||
utils =
|
utils =
|
||||||
deg2rad: (angle) ->
|
deg2rad: (angle) ->
|
||||||
@ -16,12 +17,10 @@ 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)
|
||||||
|
|
||||||
metersPerPixel: (zoom, lat = 0) ->
|
metersPerPixel: (zoom, lat = 0) ->
|
||||||
utils.rad2deg(40075017*Math.cos(utils.deg2rad(lat))/Math.pow(2, zoom+8))
|
utils.rad2deg(40075017*Math.cos(utils.deg2rad(lat))/Math.pow(2, zoom+8))
|
||||||
|
|
||||||
console.log utils.metersPerPixel(16, 180)
|
|
||||||
process.exit 0
|
|
||||||
|
|
||||||
|
|
||||||
class Termap
|
class Termap
|
||||||
config:
|
config:
|
||||||
@ -84,7 +83,7 @@ class Termap
|
|||||||
lat: 49.019855
|
lat: 49.019855
|
||||||
lng: 12.096956
|
lng: 12.096956
|
||||||
|
|
||||||
zoom: 0
|
zoom: 2
|
||||||
view: [-400, -80]
|
view: [-400, -80]
|
||||||
|
|
||||||
scale: 4
|
scale: 4
|
||||||
@ -198,6 +197,9 @@ class Termap
|
|||||||
|
|
||||||
scale = Math.pow 2, @zoom
|
scale = Math.pow 2, @zoom
|
||||||
|
|
||||||
|
drawn = []
|
||||||
|
labelBuffer = new LabelBuffer()
|
||||||
|
|
||||||
for layer in @config.drawOrder
|
for layer in @config.drawOrder
|
||||||
continue unless @features?[layer]
|
continue unless @features?[layer]
|
||||||
|
|
||||||
@ -212,25 +214,30 @@ class Termap
|
|||||||
visible = false
|
visible = false
|
||||||
points = for point in points
|
points = for point in points
|
||||||
p = [point.x/scale, point.y/scale]
|
p = [point.x/scale, point.y/scale]
|
||||||
if not visible and
|
if not visible and @_isOnScreen p
|
||||||
p[0]+@view[0]>=4 and
|
|
||||||
p[0]+@view[0]<@width-4 and
|
|
||||||
p[1]+@view[1]>=0 and
|
|
||||||
p[1]+@view[1]<@height
|
|
||||||
visible = true
|
visible = true
|
||||||
p
|
p
|
||||||
continue unless visible
|
continue unless visible
|
||||||
|
|
||||||
|
wasDrawn = false
|
||||||
switch feature.type
|
switch feature.type
|
||||||
when "polygon", "line"
|
when "polygon", "line"
|
||||||
@canvas.beginPath()
|
@canvas.beginPath()
|
||||||
@canvas.moveTo points.shift()...
|
@canvas.moveTo points.shift()...
|
||||||
@canvas.lineTo point... for point in points
|
@canvas.lineTo point... for point in points
|
||||||
@canvas.stroke()
|
@canvas.stroke()
|
||||||
|
wasDrawn = true
|
||||||
|
|
||||||
when "point"
|
when "point"
|
||||||
text = feature.properties.house_num or @config.icons[feature.properties.maki] or "◉"
|
text = feature.properties.house_num or @config.icons[feature.properties.maki] or "◉"
|
||||||
@canvas.fillText text, point... for point in points
|
|
||||||
|
for point in points
|
||||||
|
if labelBuffer.writeIfPossible text, point...
|
||||||
|
@canvas.fillText text, point...
|
||||||
|
wasDrawn = true
|
||||||
|
|
||||||
|
if wasDrawn
|
||||||
|
drawn.push feature
|
||||||
|
|
||||||
@canvas.restore()
|
@canvas.restore()
|
||||||
|
|
||||||
@ -239,6 +246,12 @@ class Termap
|
|||||||
|
|
||||||
@isDrawing = false
|
@isDrawing = false
|
||||||
|
|
||||||
|
_isOnScreen: (point) ->
|
||||||
|
point[0]+@view[0]>=4 and
|
||||||
|
point[0]+@view[0]<@width-4 and
|
||||||
|
point[1]+@view[1]>=0 and
|
||||||
|
point[1]+@view[1]<@height
|
||||||
|
|
||||||
_write: (text) ->
|
_write: (text) ->
|
||||||
process.stdout.write text
|
process.stdout.write text
|
||||||
|
|
||||||
|
BIN
tiles/europe.pbf.gz
Normal file
BIN
tiles/europe.pbf.gz
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user