Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gravitate to Centroid #63

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 28 additions & 12 deletions polylabel.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ if (Queue.default) Queue = Queue.default; // temporary webpack fix
module.exports = polylabel;
module.exports.default = polylabel;

function polylabel(polygon, precision, debug) {
function polylabel(polygon, precision, debug, centroidWeight) {
precision = precision || 1.0;
centroidWeight = centroidWeight || 0;

// find the bounding box of the outer ring
var minX, minY, maxX, maxY;
Expand All @@ -34,19 +35,26 @@ function polylabel(polygon, precision, debug) {
// a priority queue of cells in order of their "potential" (max distance to polygon)
var cellQueue = new Queue(undefined, compareMax);

var centroidCell = getCentroidCell(polygon);

// take centroid as the first best guess
var bestCell = centroidCell;

// 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, polygon));
cellQueue.push(new Cell(x + h, y + h, h, polygon, centroidCell));
}
}

// take centroid as the first best guess
var bestCell = getCentroidCell(polygon);
// the fitness function to be maximized
function fitness(cell) {
return cell.d - cell.distanceToCentroid * centroidWeight;
}

// special case for rectangular polygons
var bboxCell = new Cell(minX + width / 2, minY + height / 2, 0, polygon);
if (bboxCell.d > bestCell.d) bestCell = bboxCell;
var bboxCell = new Cell(minX + width / 2, minY + height / 2, 0, polygon, centroidCell);
if (fitness(bboxCell) > fitness(bestCell)) bestCell = bboxCell;

var numProbes = cellQueue.length;

Expand All @@ -55,7 +63,7 @@ function polylabel(polygon, precision, debug) {
var cell = cellQueue.pop();

// update the best cell if we found a better one
if (cell.d > bestCell.d) {
if (fitness(cell) > fitness(bestCell)) {
bestCell = cell;
if (debug) console.log('found best %d after %d probes', Math.round(1e4 * cell.d) / 1e4, numProbes);
}
Expand All @@ -65,10 +73,10 @@ function polylabel(polygon, precision, debug) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bit is sort of a mystery to me if (cell.max - bestCell.d <= precision) continue;.

I wonder if the bestCell.d should be replaced here by fitness(bestCell). I don't understand all the logic behind max, so I left this as-is. Seems to work fine like this.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test corresponds to point 4 of the algorithm.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it helps, my interpretation of that line is that cell.max provides the best possible value for distance from the polygon edge, and if the bestCell.d is at least within precision of cell.max then a better solution does not exist within the selected cell, and we can move on through the priority queue.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
// do not drill down further if there's no chance of a better solution
if ((cell.max - cell.distanceToCentroid * centroidWeight) - fitness(bestCell) <= precision) continue;

Here's my suggestion to fix a potentially code-breaking issue of this line.
Given that cell.max represents the best possible result from a new cell to be compared, it should be equally adjusted as in the fitness(cell) function (subtracting the weighted cell.distanceToCentroid from the result to obtain the final "fitness".

Not making this change might result in an infinite loop, typically when thecentroidWeightis much larger than the precision. e.g. polylabel(polygon, 0.000001, false, 0.01);

// split the cell into four cells
h = cell.h / 2;
cellQueue.push(new Cell(cell.x - h, cell.y - h, h, polygon));
cellQueue.push(new Cell(cell.x + h, cell.y - h, h, polygon));
cellQueue.push(new Cell(cell.x - h, cell.y + h, h, polygon));
cellQueue.push(new Cell(cell.x + h, cell.y + h, h, polygon));
cellQueue.push(new Cell(cell.x - h, cell.y - h, h, polygon, centroidCell));
cellQueue.push(new Cell(cell.x + h, cell.y - h, h, polygon, centroidCell));
cellQueue.push(new Cell(cell.x - h, cell.y + h, h, polygon, centroidCell));
cellQueue.push(new Cell(cell.x + h, cell.y + h, h, polygon, centroidCell));
numProbes += 4;
}

Expand All @@ -86,14 +94,22 @@ function compareMax(a, b) {
return b.max - a.max;
}

function Cell(x, y, h, polygon) {
function Cell(x, y, h, polygon, centroidCell) {
this.x = x; // cell center x
this.y = y; // cell center y
this.h = h; // half the cell size
this.d = pointToPolygonDist(x, y, polygon); // distance from cell center to polygon
this.distanceToCentroid = centroidCell ? pointToPointDist(this, centroidCell) : 0;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: The only case where centroidCell is not provided is when generating the centroid cell itself, so distance should be 0.

this.max = this.d + this.h * Math.SQRT2; // max distance to polygon within a cell
}

// distance between two cells
function pointToPointDist(cellA, cellB) {
var dx = cellB.x - cellA.x;
var dy = cellB.y - cellA.y;
return Math.sqrt(dx * dx + dy * dy);
}

// signed distance from point to polygon outline (negative if point is outside)
function pointToPolygonDist(x, y, polygon) {
var inside = false;
Expand Down