mapbox-gl
Version:
A WebGL interactive maps library
129 lines (107 loc) • 4.57 kB
JavaScript
'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.
* @private
*/
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);
}