📐 implementing inner exclusion of filled polygons

This commit is contained in:
Michael Straßburger 2016-09-21 01:44:51 +02:00
parent 50a597fa1d
commit 87340c1d3c
3 changed files with 84 additions and 50 deletions

View File

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

View File

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

View File

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