-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Issue #1231 Use polygon centroid for 'point' symbol placements
uses a copy of @mourner mapbox/polylabel to find it
- Loading branch information
Showing
7 changed files
with
253 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
'use strict'; | ||
var Queue = require('tinyqueue'); | ||
var Point = require('point-geometry'); | ||
var distToSegmentSquared = require('./intersection_tests').distToSegmentSquared; | ||
|
||
/** | ||
* Finds an approximation of a polygon's Pole Of Inaccessibiliy https://en.wikipedia.org/wiki/Pole_of_inaccessibility | ||
* This is a copy of http://github.com/mapbox/polylabel adapted to use Points | ||
* | ||
* @param {Array<Array<Point>>} List of polygon rings first item in array is the outer ring followed optionally by the list of holes, should be an element of the result of util/classify_rings | ||
* @param {number} [precision=1] Specified in input coordinate units. If 0 returns after first run, if > 0 repeatedly narrows the search space until the radius of the area searched for the best pole is less than precision | ||
* @param {bool} [debug=false] Print some statistics to the console during execution | ||
* | ||
* @returns {Point} Pole of Inaccessibiliy. | ||
*/ | ||
module.exports = function (polygonRings, precision, debug) { | ||
precision = precision || 1.0; | ||
|
||
// find the bounding box of the outer ring | ||
var minX, minY, maxX, maxY; | ||
var outerRing = polygonRings[0]; | ||
for (var i = 0; i < outerRing.length; i++) { | ||
var p = outerRing[i]; | ||
if (!i || p.x < minX) minX = p.x; | ||
if (!i || p.y < minY) minY = p.y; | ||
if (!i || p.x > maxX) maxX = p.x; | ||
if (!i || p.y > maxY) maxY = p.y; | ||
} | ||
|
||
var width = maxX - minX; | ||
var height = maxY - minY; | ||
var cellSize = Math.min(width, height); | ||
var h = cellSize / 2; | ||
|
||
// a priority queue of cells in order of their "potential" (max distance to polygon) | ||
var cellQueue = new Queue(null, compareMax); | ||
|
||
// cover polygon with initial cells | ||
for (var x = minX; x < maxX; x += cellSize) { | ||
for (var y = minY; y < maxY; y += cellSize) { | ||
cellQueue.push(new Cell(x + h, y + h, h, polygonRings)); | ||
} | ||
} | ||
|
||
// take centroid as the first best guess | ||
var bestCell = getCentroidCell(polygonRings); | ||
var numProbes = cellQueue.length; | ||
|
||
while (cellQueue.length) { | ||
// pick the most promising cell from the queue | ||
var cell = cellQueue.pop(); | ||
|
||
// update the best cell if we found a better one | ||
if (cell.d > bestCell.d) { | ||
bestCell = cell; | ||
if (debug) console.log('found best %d after %d probes', Math.round(1e4 * cell.d) / 1e4, numProbes); | ||
} | ||
|
||
// do not drill down further if there's no chance of a better solution | ||
if (cell.max - bestCell.d <= precision) continue; | ||
|
||
// split the cell into four cells | ||
h = cell.h / 2; | ||
cellQueue.push(new Cell(cell.p.x - h, cell.p.y - h, h, polygonRings)); | ||
cellQueue.push(new Cell(cell.p.x + h, cell.p.y - h, h, polygonRings)); | ||
cellQueue.push(new Cell(cell.p.x - h, cell.p.y + h, h, polygonRings)); | ||
cellQueue.push(new Cell(cell.p.x + h, cell.p.y + h, h, polygonRings)); | ||
numProbes += 4; | ||
} | ||
|
||
if (debug) { | ||
console.log('num probes: ' + numProbes); | ||
console.log('best distance: ' + bestCell.d); | ||
} | ||
|
||
return bestCell.p; | ||
}; | ||
|
||
function compareMax(a, b) { | ||
return b.max - a.max; | ||
} | ||
|
||
function Cell(x, y, h, polygon) { | ||
this.p = new Point(x, y); | ||
this.h = h; // half the cell size | ||
this.d = pointToPolygonDist(this.p, polygon); // distance from cell center to polygon | ||
this.max = this.d + this.h * Math.SQRT2; // max distance to polygon within a cell | ||
} | ||
|
||
// signed distance from point to polygon outline (negative if point is outside) | ||
function pointToPolygonDist(p, polygon) { | ||
var inside = false; | ||
var minDistSq = Infinity; | ||
|
||
for (var k = 0; k < polygon.length; k++) { | ||
var ring = polygon[k]; | ||
|
||
for (var i = 0, len = ring.length, j = len - 1; i < len; j = i++) { | ||
var a = ring[i]; | ||
var b = ring[j]; | ||
|
||
if ((a.y > p.y !== b.y > p.y) && | ||
(p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x)) inside = !inside; | ||
|
||
minDistSq = Math.min(minDistSq, distToSegmentSquared(p, a, b)); | ||
} | ||
} | ||
|
||
return (inside ? 1 : -1) * Math.sqrt(minDistSq); | ||
} | ||
|
||
// get polygon centroid | ||
function getCentroidCell(polygon) { | ||
var area = 0; | ||
var x = 0; | ||
var y = 0; | ||
var points = polygon[0]; | ||
for (var i = 0, len = points.length, j = len - 1; i < len; j = i++) { | ||
var a = points[i]; | ||
var b = points[j]; | ||
var f = a.x * b.y - b.x * a.y; | ||
x += (a.x + b.x) * f; | ||
y += (a.y + b.y) * f; | ||
area += f * 3; | ||
} | ||
return new Cell(x / area, y / area, 0, polygon); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
'use strict'; | ||
|
||
var test = require('tap').test; | ||
var Point = require('point-geometry'); | ||
var poleOfInaccessibility = require('../../../js/util/polygon_poi'); | ||
|
||
test('polygon_poi', function(t) { | ||
|
||
var closedRing = [ | ||
new Point(0, 0), | ||
new Point(10, 10), | ||
new Point(10, 0), | ||
new Point(0, 0) | ||
]; | ||
var closedRingHole = [ | ||
new Point(2, 1), | ||
new Point(6, 6), | ||
new Point(6, 1), | ||
new Point(2, 1) | ||
]; | ||
t.deepEqual(poleOfInaccessibility([closedRing], 0.1), new Point(7.0703125, 2.9296875)); | ||
t.deepEqual(poleOfInaccessibility([closedRing, closedRingHole], 0.1), new Point(7.96875, 2.03125)); | ||
|
||
t.end(); | ||
}); |