-
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
Updated mapbox-gl-test-suite sha
- Loading branch information
Showing
7 changed files
with
295 additions
and
21 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,150 @@ | ||
'use strict'; | ||
|
||
var isClosedPolygon = require('./util').isClosedPolygon; | ||
|
||
function pointToLineDistance(x, y, x1, y1, x2, y2) { | ||
var C = x2 - x1; | ||
var D = y2 - y1; | ||
var lenSq = C * C + D * D; | ||
var param = -1; | ||
if (lenSq !== 0) { | ||
var A = x - x1; | ||
var B = y - y1; | ||
var dot = A * C + B * D; | ||
param = dot / lenSq; | ||
} | ||
var xx, yy; | ||
if (param < 0) { | ||
xx = x1; | ||
yy = y1; | ||
} else if (param > 1) { | ||
xx = x2; | ||
yy = y2; | ||
} else { | ||
xx = x1 + param * C; | ||
yy = y1 + param * D; | ||
} | ||
var dx = x - xx; | ||
var dy = y - yy; | ||
// return Math.sqrt(dx * dx + dy * dy); | ||
return (dx * dx + dy * dy); | ||
} | ||
|
||
function pointToPerimeterDistance(x, y, points) { | ||
var d, p1, p2, minDistance = Number.MAX_VALUE; | ||
var len = points.length; | ||
|
||
for (var i = -1, l = len, j = l - 1; ++i < l; j = i) { | ||
p1 = points[i]; | ||
p2 = points[j]; | ||
d = pointToLineDistance(x, y, p1.x, p1.y, p2.x, p2.y); | ||
if (d < minDistance) { | ||
minDistance = d; | ||
} | ||
} | ||
return minDistance; | ||
} | ||
|
||
function isInside(x, y, points) { | ||
var c = false; | ||
for (var i = -1, l = points.length, j = l - 1; ++i < l; j = i) { | ||
var pi = points[i]; | ||
var pj = points[j]; | ||
if (((pi.y <= y && y < pj.y) || (pj.y <= y && y < pi.y)) && | ||
(x < (pj.x - pi.x) * (y - pi.y) / (pj.y - pi.y) + pi.x)) | ||
c = !c; | ||
} | ||
return c; | ||
} | ||
|
||
function poleScan(xMin, yMin, xMax, yMax, points, holes, allpoints) { | ||
var px, py, pd, maxDistance = 0; | ||
// If this is too small then we might miss large areas of the model | ||
// If it is too big then we waste cpu searching | ||
var splits = 24; | ||
// This loop is to prevent some theoretical geometries from failing completely | ||
while (maxDistance === 0) { | ||
// Sample a grid of points over the shape, starting at the north western edge | ||
var yStep = ((yMax - yMin) / splits); | ||
var xStep = ((xMax - xMin) / splits); | ||
for (var y = yMax; y >= yMin; y -= yStep) { | ||
for (var x = xMin; x <= xMax; x += xStep) { | ||
// Validate this point is inside the polygon | ||
if (isInside(x, y, points)) { | ||
if (holes) { | ||
var inHole = false; | ||
// And it isn't inside a hole | ||
for (var i = 0; i < holes.length && !inHole; i++) { | ||
var hole = holes[i]; | ||
inHole = isInside(x, y, hole); | ||
} | ||
if (inHole) | ||
continue; | ||
} | ||
// Work out the distance to the closest perimiter | ||
pd = pointToPerimeterDistance(x, y, allpoints); | ||
if (pd > maxDistance) { | ||
maxDistance = pd; | ||
px = x; | ||
py = y; | ||
} | ||
} | ||
} | ||
} | ||
// If we didn't find any points then split the polygon up more. | ||
splits = splits * 2; | ||
} | ||
return { | ||
x: px, | ||
y: py | ||
}; | ||
} | ||
|
||
/** | ||
* Finds an approximation of a polygon's Pole Of Inaccessibiliy https://en.wikipedia.org/wiki/Pole_of_inaccessibility | ||
* Note that for perfect rectangles the pole found ISN'T in the center (though the center is a valid POI) | ||
* | ||
* @param {Array<Point>} points the outer ring of the polygon | ||
* @param {Array<Array<Point>>} [holes] List of interior holes | ||
* @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 | ||
* | ||
* @returns {Point} Pole of Inaccessibiliy. | ||
*/ | ||
module.exports = function (points, holes, precision) { | ||
if (!isClosedPolygon(points)) { | ||
return points[0]; | ||
} | ||
|
||
if (precision === undefined) { | ||
precision = 1; | ||
} | ||
var xMin, yMin, xMax, yMax; | ||
var p = points[0]; | ||
xMin = xMax = p.x; | ||
yMin = yMax = p.y; | ||
for (var i = 1; i < points.length; i++) { | ||
p = points[i]; | ||
if (xMin > p.x) xMin = p.x; | ||
if (xMax < p.x) xMax = p.x; | ||
if (yMin > p.y) yMin = p.y; | ||
if (yMax < p.y) yMax = p.y; | ||
} | ||
|
||
var allpoints = holes ? points.concat.apply(points, holes) : points; | ||
var lp = poleScan(xMin, yMin, xMax, yMax, points, holes, allpoints); | ||
if (precision > 0) { | ||
var r = ((xMax - xMin) * (yMax - yMin)); | ||
var dx, dy; | ||
while (r > precision) { | ||
lp = poleScan(xMin, yMin, xMax, yMax, points, holes, allpoints); | ||
dx = (xMax - xMin) / 24; | ||
dy = (yMax - yMin) / 24; | ||
xMin = lp.x - dx; | ||
xMax = lp.x + dx; | ||
yMin = lp.y - dy; | ||
yMax = lp.y + dy; | ||
r = dx * dy; | ||
} | ||
} | ||
return lp; | ||
}; |
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,41 @@ | ||
'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 point = [ | ||
new Point(0, 0) | ||
]; | ||
var line = [ | ||
new Point(10, 10), | ||
new Point(0, 0) | ||
]; | ||
var emptyPolygon = [ | ||
new Point(20, 20), | ||
new Point(0, 0), | ||
new Point(10, 10), | ||
new Point(20, 20) | ||
]; | ||
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, 7), | ||
new Point(6, 1), | ||
new Point(2, 1) | ||
]; | ||
t.deepEqual(poleOfInaccessibility(point), point[0]); | ||
t.deepEqual(poleOfInaccessibility(line), line[0]); | ||
t.deepEqual(poleOfInaccessibility(emptyPolygon), emptyPolygon[0]); | ||
t.deepEqual(poleOfInaccessibility(closedRing), new Point(7.083333333333335, 2.9166666666666665)); | ||
t.deepEqual(poleOfInaccessibility(closedRing, [closedRingHole]), new Point(7.916666666666669, 5)); | ||
|
||
t.end(); | ||
}); |