Try a little harder to find an optimal label point

This commit is contained in:
Erica Fischer
2022-10-06 13:05:38 -07:00
parent ffd07befce
commit c34348218f
+57 -31
View File
@@ -1368,19 +1368,19 @@ drawvec stairstep(drawvec &geom, int z, int detail) {
// https://github.com/Turfjs/turf/blob/master/packages/turf-center-of-mass/index.ts // https://github.com/Turfjs/turf/blob/master/packages/turf-center-of-mass/index.ts
// //
// The MIT License (MIT) // The MIT License (MIT)
// //
// Copyright (c) 2019 Morgan Herlocker // Copyright (c) 2019 Morgan Herlocker
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy of // Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in // this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to // the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so, // the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions: // subject to the following conditions:
// //
// The above copyright notice and this permission notice shall be included in all // The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software. // copies or substantial portions of the Software.
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
@@ -1451,30 +1451,43 @@ struct polygon_label {
} }
}; };
bool acceptable(const drawvec &dv, size_t start, size_t count, long long x, long long y, long long xmin, long long ymin, long long xmax, long long ymax) { double label_goodness(const drawvec &dv, size_t start, size_t count, long long x, long long y) {
if (!pnpoly(dv, start, count, x, y)) { if (!pnpoly(dv, start, count, x, y)) {
return false; return 0; // outside the polygon is as bad as it gets
} }
// minimal acceptable distance from border: double closest = INFINITY; // square of closest distance to the border
double min = ceil(sqrt((xmax - xmin) * (xmax - xmin) + (ymax - ymin) * (ymax - ymin)) * 0.05);
double squaremin = min * min;
for (size_t i = start; i < start + count; i++) { for (size_t i = start; i < start + count; i++) {
double dx = dv[i].x - x; double dx = dv[i].x - x;
double dy = dv[i].y - y; double dy = dv[i].y - y;
double squared = dx * dx + dy * dy; double squared = dx * dx + dy * dy;
if (squared < squaremin) { if (squared < closest) {
printf("within but too close to the border\n"); closest = squared;
return false;
} }
} }
return true; return sqrt(closest);
} }
// Generate a label point for a polygon feature, currently at the centroid of its largest-area // Generate a label point for a polygon feature.
// outer ring within the tile. //
// A good label point will be near the center of the feature and far from any border.
//
// Polylabel is supposed to be able to do this optimally, but can be quite slow
// and sometimes still produces some odd results.
//
// The centroid is often off-center because edges with many curves will be
// weighted higher than edges with straight lines.
//
// Turf's center-of-mass algorithm generally does a good job, but can sometimes
// find a point that is outside the bounds of the polygon or quite close to the edge.
//
// So prefer the center of mass, but if it produces something too close to the border
// or outside the polygon, try a series of gridded points within the feature's bounding box
// until something works well, or if nothing does after several iterations, use the
// least-bad option.
drawvec polygon_to_anchor(const drawvec &geom) { drawvec polygon_to_anchor(const drawvec &geom) {
std::vector<polygon_label> labels; std::vector<polygon_label> labels;
drawvec dv; drawvec dv;
@@ -1495,6 +1508,7 @@ drawvec polygon_to_anchor(const drawvec &geom) {
double area = get_area(geom, i, j); double area = get_area(geom, i, j);
if (area > 0) { // don't generate anchors for holes if (area > 0) { // don't generate anchors for holes
// Calculate centroid and bounding box.
// i + 1 to exclude the first point, which is duplicated as the last // i + 1 to exclude the first point, which is duplicated as the last
for (size_t k = i + 1; k < j; k++) { for (size_t k = i + 1; k < j; k++) {
xsum += geom[k].x; xsum += geom[k].x;
@@ -1508,35 +1522,47 @@ drawvec polygon_to_anchor(const drawvec &geom) {
} }
if (count > 0) { if (count > 0) {
draw d(VT_MOVETO, xsum / count, ysum / count); draw centroid(VT_MOVETO, xsum / count, ysum / count);
d = centerOfMass(geom, i, j, d); draw d = centerOfMass(geom, i, j, centroid);
if (!acceptable(geom, i, j - i - 1, d.x, d.y, xmin, ymin, xmax, ymax)) { double radius = sqrt(area / M_PI);
// Center of mass is not actually within the polygon, double goodness_threshold = radius / 5;
// so just start trying to subdivide the bounding box
// until something works. double goodness = label_goodness(geom, i, j - i - 1, d.x, d.y);
if (goodness < goodness_threshold) {
// Label is too close to the border or outside it,
// so try some other possible points
for (long long sub = 2; for (long long sub = 2;
sub < 32 && (xmax - xmin) > 2 * sub && (ymax - ymin) > 2 * sub; sub < 32 && (xmax - xmin) > 2 * sub && (ymax - ymin) > 2 * sub;
sub *= 2) { sub *= 2) {
for (long long x = 1; x < sub; x++) { for (long long x = 1; x < sub; x++) {
for (long long y = 1; y < sub; y++) { for (long long y = 1; y < sub; y++) {
printf("try %lld,%lld / %lld\n", x, y, sub);
draw maybe(VT_MOVETO, draw maybe(VT_MOVETO,
xmin + x * (xmax - xmin) / sub, xmin + x * (xmax - xmin) / sub,
ymin + y * (ymax - ymin) / sub); ymin + y * (ymax - ymin) / sub);
if (acceptable(geom, i, j - 1, maybe.x, maybe.y, xmin, ymin, xmax, ymax)) {
double maybe_goodness = label_goodness(geom, i, j - i, maybe.x, maybe.y);
if (maybe_goodness > goodness) {
// better than the previous
d = maybe; d = maybe;
printf("ok!\n"); goodness = maybe_goodness;
goto found;
} }
} }
} }
if (goodness > goodness_threshold) {
break;
}
}
// There is nothing really good. Is the centroid maybe better?
// If not, we're stuck with whatever the best we found was.
if (label_goodness(geom, i, j - i, centroid.x, centroid.y) > goodness) {
d = centroid;
} }
printf("fail! %lld,%lld %lld,%lld\n", xmin, ymin, xmax, ymax);
} }
found:
polygon_label pl; polygon_label pl;
pl.size = area; pl.size = area;
pl.point = d; pl.point = d;