mirror of
https://github.com/rastapasta/mapscii.git
synced 2024-11-22 08:03:07 +01:00
📐 implementing inner exclusion of filled polygons
This commit is contained in:
parent
50a597fa1d
commit
87340c1d3c
@ -37,6 +37,7 @@ No web browser around? No worries - discover the planet in your console!
|
|||||||
* [`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
|
||||||
|
|
||||||
#### Juggling the vectors and numbers
|
#### Juggling the vectors and numbers
|
||||||
|
* [`pnltri`](https://github.com/jahting/pnltri.js) for polygon triangulation to draw them filled
|
||||||
* [`rbush`](https://github.com/mourner/rbush) for 2D spatial indexing based label and mouse collision detection
|
* [`rbush`](https://github.com/mourner/rbush) for 2D spatial indexing based label and mouse collision detection
|
||||||
* [`sphericalmercator`](https://github.com/mapbox/node-sphericalmercator) for [EPSG:3857](http://spatialreference.org/ref/sr-org/6864/) <> [WGS84](http://spatialreference.org/ref/epsg/wgs-84/) conversions
|
* [`sphericalmercator`](https://github.com/mapbox/node-sphericalmercator) for [EPSG:3857](http://spatialreference.org/ref/sr-org/6864/) <> [WGS84](http://spatialreference.org/ref/epsg/wgs-84/) conversions
|
||||||
|
|
||||||
|
@ -7,6 +7,9 @@ module.exports = class LabelBuffer
|
|||||||
constructor: (@width, @height) ->
|
constructor: (@width, @height) ->
|
||||||
@tree = rbush()
|
@tree = rbush()
|
||||||
|
|
||||||
|
clear: ->
|
||||||
|
@tree.clear()
|
||||||
|
|
||||||
project: (x, y) ->
|
project: (x, y) ->
|
||||||
[Math.floor(x/2), Math.floor(y/4)]
|
[Math.floor(x/2), Math.floor(y/4)]
|
||||||
|
|
||||||
|
128
termap.coffee
128
termap.coffee
@ -26,7 +26,9 @@ utils =
|
|||||||
class Termap
|
class Termap
|
||||||
config:
|
config:
|
||||||
zoomStep: 0.5
|
zoomStep: 0.5
|
||||||
drawOrder: ["admin", "water", "landuse", "building", "road", "poi_label", "housenum_label"]
|
|
||||||
|
# landuse
|
||||||
|
drawOrder: ["admin", "water", "building", "road", "poi_label", "housenum_label"]
|
||||||
|
|
||||||
icons:
|
icons:
|
||||||
car: "🚗"
|
car: "🚗"
|
||||||
@ -116,8 +118,11 @@ class Termap
|
|||||||
@height = Math.ceil(process.stdout.rows/4)*4*4
|
@height = Math.ceil(process.stdout.rows/4)*4*4
|
||||||
@canvas = new Canvas @width, @height
|
@canvas = new Canvas @width, @height
|
||||||
|
|
||||||
|
unless @lastDrawAt
|
||||||
@zoom = Math.log(4096/@width)/Math.LN2
|
@zoom = Math.log(4096/@width)/Math.LN2
|
||||||
|
|
||||||
|
@labelBuffer = new LabelBuffer()
|
||||||
|
|
||||||
_onResize: (cb) ->
|
_onResize: (cb) ->
|
||||||
process.stdout.on 'resize', cb
|
process.stdout.on 'resize', cb
|
||||||
|
|
||||||
@ -189,59 +194,17 @@ class Termap
|
|||||||
_draw: ->
|
_draw: ->
|
||||||
return if @isDrawing
|
return if @isDrawing
|
||||||
@isDrawing = true
|
@isDrawing = true
|
||||||
|
|
||||||
@lastDrawAt = Date.now()
|
@lastDrawAt = Date.now()
|
||||||
|
|
||||||
@canvas.clearRect 0, 0, @width, @height
|
@canvas.clearRect 0, 0, @width, @height
|
||||||
#@_write @canvas._canvas.frame()
|
|
||||||
|
|
||||||
@canvas.save()
|
@canvas.save()
|
||||||
|
|
||||||
@canvas.translate @view[0], @view[1]
|
@canvas.translate @view[0], @view[1]
|
||||||
|
|
||||||
scale = Math.pow 2, @zoom
|
@labelBuffer.clear()
|
||||||
|
|
||||||
drawn = []
|
drawn = @_drawLayers()
|
||||||
labelBuffer = new LabelBuffer()
|
|
||||||
|
|
||||||
for layer in @config.drawOrder
|
|
||||||
continue unless @features?[layer]
|
|
||||||
|
|
||||||
if @config.layers[layer].minZoom and @zoom > @config.layers[layer].minZoom
|
|
||||||
continue
|
|
||||||
|
|
||||||
@canvas.strokeStyle = @canvas.fillStyle = @config.layers[layer].color
|
|
||||||
|
|
||||||
for feature in @features[layer]
|
|
||||||
for points in feature.points
|
|
||||||
|
|
||||||
visible = false
|
|
||||||
points = for point in points
|
|
||||||
p = x: point.x/scale, y: point.y/scale
|
|
||||||
if not visible and @_isOnScreen p
|
|
||||||
visible = true
|
|
||||||
p
|
|
||||||
continue unless visible
|
|
||||||
|
|
||||||
wasDrawn = false
|
|
||||||
switch feature.type
|
|
||||||
when "line"
|
|
||||||
@_drawWithLines points
|
|
||||||
|
|
||||||
when "polygon"
|
|
||||||
unless points.length > 3 and @_drawWithTriangles points
|
|
||||||
@_drawWithLines points
|
|
||||||
wasDrawn = true
|
|
||||||
|
|
||||||
when "point"
|
|
||||||
text = feature.properties.house_num or @config.icons[feature.properties.maki] or "◉"
|
|
||||||
|
|
||||||
for point in points
|
|
||||||
if labelBuffer.writeIfPossible text, point.x, point.y
|
|
||||||
@canvas.fillText text, point.x, point.y
|
|
||||||
wasDrawn = true
|
|
||||||
|
|
||||||
if wasDrawn
|
|
||||||
drawn.push feature
|
|
||||||
|
|
||||||
@canvas.restore()
|
@canvas.restore()
|
||||||
|
|
||||||
@ -250,15 +213,82 @@ class Termap
|
|||||||
|
|
||||||
@isDrawing = false
|
@isDrawing = false
|
||||||
|
|
||||||
|
_drawLayers: ->
|
||||||
|
drawn = []
|
||||||
|
for layer in @config.drawOrder
|
||||||
|
scale = Math.pow 2, @zoom
|
||||||
|
continue unless @features?[layer]
|
||||||
|
|
||||||
|
if @config.layers[layer].minZoom and @zoom > @config.layers[layer].minZoom
|
||||||
|
continue
|
||||||
|
|
||||||
|
@canvas.strokeStyle = @canvas.fillStyle = @config.layers[layer].color
|
||||||
|
|
||||||
|
for feature in @features[layer]
|
||||||
|
if @_drawFeature feature, scale
|
||||||
|
drawn.push feature
|
||||||
|
|
||||||
|
drawn
|
||||||
|
|
||||||
|
_drawFeature: (feature, scale) ->
|
||||||
|
toDraw = []
|
||||||
|
for idx, points of feature.points
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
projectedPoints = for point in points
|
||||||
|
projectedPoint =
|
||||||
|
x: point.x/scale
|
||||||
|
y: point.y/scale
|
||||||
|
|
||||||
|
visible = true if not visible and @_isOnScreen projectedPoint
|
||||||
|
projectedPoint
|
||||||
|
|
||||||
|
if idx is 0 and not visible
|
||||||
|
return false
|
||||||
|
|
||||||
|
continue unless visible
|
||||||
|
toDraw.push projectedPoints
|
||||||
|
|
||||||
|
switch feature.type
|
||||||
|
when "line"
|
||||||
|
@_drawWithLines points for points in toDraw
|
||||||
|
true
|
||||||
|
|
||||||
|
when "polygon"
|
||||||
|
unless @_drawWithTriangles toDraw
|
||||||
|
@_drawWithLines points for points in toDraw
|
||||||
|
true
|
||||||
|
|
||||||
|
when "point"
|
||||||
|
text = feature.properties.house_num or @config.icons[feature.properties.maki] or "◉"
|
||||||
|
|
||||||
|
wasDrawn = false
|
||||||
|
# TODO: check in definition if points can actually own multiple geometries
|
||||||
|
for points in toDraw
|
||||||
|
for point in points
|
||||||
|
if @labelBuffer.writeIfPossible text, point.x, point.y
|
||||||
|
@canvas.fillText text, point.x, point.y
|
||||||
|
wasDrawn = true
|
||||||
|
|
||||||
|
wasDrawn
|
||||||
|
|
||||||
_drawWithTriangles: (points) ->
|
_drawWithTriangles: (points) ->
|
||||||
try
|
try
|
||||||
triangles = triangulator.triangulate_polygon [points]
|
triangles = triangulator.triangulate_polygon points
|
||||||
catch
|
catch
|
||||||
return false
|
return false
|
||||||
|
|
||||||
for triangle in triangles
|
return false unless triangles.length
|
||||||
@canvas.fillTriangle points[triangle[0]], points[triangle[1]], points[triangle[2]]
|
|
||||||
|
|
||||||
|
# TODO: triangles are returned as vertex references to a flattened input.
|
||||||
|
# optimize it!
|
||||||
|
|
||||||
|
arr = points.reduce (a, b) -> a.concat b
|
||||||
|
for triangle in triangles
|
||||||
|
try
|
||||||
|
@canvas.fillTriangle arr[triangle[0]], arr[triangle[1]], arr[triangle[2]]
|
||||||
|
catch
|
||||||
|
return false
|
||||||
true
|
true
|
||||||
|
|
||||||
_drawWithLines: (points) ->
|
_drawWithLines: (points) ->
|
||||||
|
Loading…
Reference in New Issue
Block a user