Files
Erica Fischer c5f2f0da34 More work on plumbing attribute accumulation through (#263)
* Plumb bounding boxes through potential intersections

* Quick bbox reject for bins that can't possibly intersect

* Inching toward attribute accumulation in megatile handling

* Some sort of test for how all these things interact with each other.

Automatic numeric attribute accumulation does *not* apply to attributes
that have an explicit attribute accumulator set, because the order of
operations is too messy and weird

* More sketching

* More sketching

* Actually do some accumulation

* Put all that behind an --accumulate-numeric flag

* Use the same attribute accumulation logic in binning as in megatiles

* Fix backwards conditional

* Add means, but somehow I have some counts of 0

* Handle aggregated attributes with no base attribute in the feature

* Checkpoint before I break everything

* Found a flaw, now to debug

* Fix a typo that broke accumulation

* Add binning tests

* Make sure IDs make it through on the bins

* Fix count/mean accumulation

* Make the numeric accumulation prefix configurable

* Make sure the accumulate test still works with a different prefix

* Forgot to update this test

* More testing to make sure cluster sizes make it all the way through

* Fix neglected --accumulate-attribute when binning

* Mark unexercised attribute accumulation cases as "can't happen"

* Factor out numeric preservation

* Attrs with the accumulation prefix are just preserved, not accumulated

* Test behavior of prefixed attributes

* Plumbing for exclude and exclude-prefix

* Implement and test attribute prefix stripping in overzoom

* Update version and changelog

* For debugging, make an attribute list of source feature IDs

* Revert "For debugging, make an attribute list of source feature IDs"

This reverts commit 65fc99c9d1.
2024-09-20 15:30:29 -07:00

1030 lines
26 KiB
C++

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <cstdio>
#include <unistd.h>
#include <cmath>
#include <limits.h>
#include <sqlite3.h>
#include <mapbox/geometry/point.hpp>
#include <mapbox/geometry/multi_polygon.hpp>
#include <mapbox/geometry/snap_rounding.hpp>
#include "geometry.hpp"
#include "projection.hpp"
#include "serial.hpp"
#include "main.hpp"
#include "options.hpp"
#include "errors.hpp"
drawvec decode_geometry(const char **meta, int z, unsigned tx, unsigned ty, long long *bbox, unsigned initial_x, unsigned initial_y) {
drawvec out;
bbox[0] = LLONG_MAX;
bbox[1] = LLONG_MAX;
bbox[2] = LLONG_MIN;
bbox[3] = LLONG_MIN;
long long wx = initial_x, wy = initial_y;
while (1) {
draw d;
deserialize_byte(meta, &d.op);
if (d.op == VT_END) {
break;
}
if (d.op == VT_MOVETO || d.op == VT_LINETO) {
long long dx, dy;
deserialize_long_long(meta, &dx);
deserialize_long_long(meta, &dy);
wx += dx * (1 << geometry_scale);
wy += dy * (1 << geometry_scale);
long long wwx = wx;
long long wwy = wy;
if (z != 0) {
wwx -= tx << (32 - z);
wwy -= ty << (32 - z);
}
bbox[0] = std::min(wwx, bbox[0]);
bbox[1] = std::min(wwy, bbox[1]);
bbox[2] = std::max(wwx, bbox[2]);
bbox[3] = std::max(wwy, bbox[3]);
d.x = wwx;
d.y = wwy;
}
out.push_back(d);
}
return out;
}
void check_polygon(drawvec &geom) {
geom = remove_noop(geom, VT_POLYGON, 0);
mapbox::geometry::multi_polygon<long long> mp;
for (size_t i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_MOVETO) {
size_t j;
for (j = i + 1; j < geom.size(); j++) {
if (geom[j].op != VT_LINETO) {
break;
}
}
if (j >= i + 4) {
mapbox::geometry::linear_ring<long long> lr;
for (size_t k = i; k < j; k++) {
lr.push_back({geom[k].x, geom[k].y});
}
if (lr.size() >= 3) {
mapbox::geometry::polygon<long long> p;
p.push_back(std::move(lr));
mp.push_back(std::move(p));
}
}
i = j - 1;
}
}
mapbox::geometry::multi_polygon<long long> mp2 = mapbox::geometry::snap_round(mp, true, true);
if (mp != mp2) {
fprintf(stderr, "Internal error: self-intersecting polygon\n");
}
size_t outer_start = -1;
size_t outer_len = 0;
for (size_t i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_MOVETO) {
size_t j;
for (j = i + 1; j < geom.size(); j++) {
if (geom[j].op != VT_LINETO) {
break;
}
}
double area = get_area(geom, i, j);
if (area > 0) {
outer_start = i;
outer_len = j - i;
} else {
for (size_t k = i; k < j; k++) {
if (!pnpoly(geom, outer_start, outer_len, geom[k].x, geom[k].y)) {
bool on_edge = false;
for (size_t l = outer_start; l < outer_start + outer_len; l++) {
if (geom[k].x == geom[l].x || geom[k].y == geom[l].y) {
on_edge = true;
break;
}
}
if (!on_edge) {
fprintf(stderr, "%lld,%lld at %lld not in outer ring (%lld to %lld)\n", (long long) geom[k].x, (long long) geom[k].y, (long long) k, (long long) outer_start, (long long) (outer_start + outer_len));
}
}
}
}
}
}
}
int quick_check(const long long *bbox, int z, long long buffer) {
long long min = 0;
long long area = 1LL << (32 - z);
// bbox entirely within the tile proper
if (bbox[0] > min && bbox[1] > min && bbox[2] < area && bbox[3] < area) {
return 1;
}
min -= buffer * area / 256;
area += buffer * area / 256;
// bbox entirely within the tile, including its buffer
if (bbox[0] > min && bbox[1] > min && bbox[2] < area && bbox[3] < area) {
return 3;
}
// bbox entirely outside the tile
if (bbox[0] > area || bbox[1] > area) {
return 0;
}
if (bbox[2] < min || bbox[3] < min) {
return 0;
}
// some overlap of edge
return 2;
}
bool point_within_tile(long long x, long long y, int z) {
// No adjustment for buffer, because the point must be
// strictly within the tile to appear exactly once
long long area = 1LL << (32 - z);
return x >= 0 && y >= 0 && x < area && y < area;
}
// If any line segment crosses a tile boundary, add a node there
// that cannot be simplified away, to prevent the edge of any
// feature from jumping abruptly at the tile boundary.
drawvec impose_tile_boundaries(const drawvec &geom, long long extent) {
drawvec out;
for (size_t i = 0; i < geom.size(); i++) {
if (i > 0 && geom[i].op == VT_LINETO && (geom[i - 1].op == VT_MOVETO || geom[i - 1].op == VT_LINETO)) {
long long x1 = geom[i - 1].x;
long long y1 = geom[i - 1].y;
long long x2 = geom[i - 0].x;
long long y2 = geom[i - 0].y;
int c = clip(&x1, &y1, &x2, &y2, 0, 0, extent, extent);
if (c > 1) { // clipped
if (x1 != geom[i - 1].x || y1 != geom[i - 1].y) {
out.emplace_back(VT_LINETO, x1, y1);
out[out.size() - 1].necessary = 1;
}
if (x2 != geom[i - 0].x || y2 != geom[i - 0].y) {
out.emplace_back(VT_LINETO, x2, y2);
out[out.size() - 1].necessary = 1;
}
}
}
out.push_back(geom[i]);
}
return out;
}
drawvec simplify_lines(drawvec &geom, int z, int tx, int ty, int detail, bool mark_tile_bounds, double simplification, size_t retain, drawvec const &shared_nodes, struct node *shared_nodes_map, size_t nodepos, std::string const &shared_nodes_bloom) {
int res = 1 << (32 - detail - z);
long long area = 1LL << (32 - z);
for (size_t i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_MOVETO) {
geom[i].necessary = 1;
} else if (geom[i].op == VT_LINETO) {
geom[i].necessary = 0;
// if this is actually the endpoint, not an intermediate point,
// it will be marked as necessary below
} else {
geom[i].necessary = 1;
}
if (prevent[P_SIMPLIFY_SHARED_NODES]) {
// This is kind of weird, because we have two lists of shared nodes to look through:
// * the drawvec, which is nodes that were introduced during clipping to the tile edge,
// and which are in local tile coordinates
// * the shared_nodes_map, which was made globally before tiling began, and which
// is in global quadkey coordinates.
// To look through the latter, we need to offset and encode the coordinates
// of the feature we are simplifying.
auto pt = std::lower_bound(shared_nodes.begin(), shared_nodes.end(), geom[i]);
if (pt != shared_nodes.end() && *pt == geom[i]) {
geom[i].necessary = true;
}
if (nodepos > 0) {
// offset to global
draw d = geom[i];
if (z != 0) {
d.x += tx * (1LL << (32 - z));
d.y += ty * (1LL << (32 - z));
}
struct node n;
n.index = encode_vertex((unsigned) d.x, (unsigned) d.y);
size_t bloom_ix = n.index % (shared_nodes_bloom.size() * 8);
unsigned char bloom_mask = 1 << (bloom_ix & 7);
bloom_ix >>= 3;
if (shared_nodes_bloom[bloom_ix] & bloom_mask) {
if (bsearch(&n, shared_nodes_map, nodepos / sizeof(node), sizeof(node), nodecmp) != NULL) {
geom[i].necessary = true;
}
}
}
}
}
if (mark_tile_bounds) {
geom = impose_tile_boundaries(geom, area);
}
for (size_t i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_MOVETO) {
size_t j;
for (j = i + 1; j < geom.size(); j++) {
if (geom[j].op != VT_LINETO) {
break;
}
}
geom[i].necessary = 1;
geom[j - 1].necessary = 1;
// empirical mapping from douglas-peucker simplifications
// to visvalingam simplifications that yield similar
// output sizes
double sim = simplification * (0.1596 * z + 0.878);
double scale = (res * sim) * (res * sim);
scale = exp(1.002 * log(scale) + 0.3043);
if (j - i > 1) {
if (additional[A_VISVALINGAM]) {
visvalingam(geom, i, j, scale, retain);
} else {
douglas_peucker(geom, i, j - i, res * simplification, 2, retain, prevent[P_SIMPLIFY_SHARED_NODES]);
}
}
i = j - 1;
}
}
size_t out = 0;
for (size_t i = 0; i < geom.size(); i++) {
if (geom[i].necessary) {
geom[out++] = geom[i];
}
}
geom.resize(out);
return geom;
}
drawvec reorder_lines(const drawvec &geom) {
// Only reorder simple linestrings with a single moveto
if (geom.size() == 0) {
return geom;
}
for (size_t i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_MOVETO) {
if (i != 0) {
// moveto is not at the start, so it is not simple
return geom;
}
} else if (geom[i].op == VT_LINETO) {
if (i == 0) {
// lineto is at the start: can't happen
return geom;
}
} else {
// something other than moveto or lineto: can't happen
return geom;
}
}
// Reorder anything that goes up and to the left
// instead of down and to the right
// so that it will coalesce better
unsigned long long l1 = encode_index(geom[0].x, geom[0].y);
unsigned long long l2 = encode_index(geom[geom.size() - 1].x, geom[geom.size() - 1].y);
if (l1 > l2) {
drawvec out;
for (size_t i = 0; i < geom.size(); i++) {
out.push_back(geom[geom.size() - 1 - i]);
}
out[0].op = VT_MOVETO;
if (out.size() > 1) {
out[out.size() - 1].op = VT_LINETO;
}
return out;
}
return geom;
}
#if 0
std::vector<drawvec> chop_polygon(std::vector<drawvec> &geoms) {
while (1) {
bool again = false;
std::vector<drawvec> out;
for (size_t i = 0; i < geoms.size(); i++) {
if (geoms[i].size() > 700) {
static bool warned = false;
if (!warned) {
fprintf(stderr, "Warning: splitting up polygon with more than 700 sides\n");
warned = true;
}
long long midx = 0, midy = 0, count = 0;
long long maxx = LLONG_MIN, maxy = LLONG_MIN, minx = LLONG_MAX, miny = LLONG_MAX;
for (size_t j = 0; j < geoms[i].size(); j++) {
if (geoms[i][j].op == VT_MOVETO || geoms[i][j].op == VT_LINETO) {
midx += geoms[i][j].x;
midy += geoms[i][j].y;
count++;
if (geoms[i][j].x > maxx) {
maxx = geoms[i][j].x;
}
if (geoms[i][j].y > maxy) {
maxy = geoms[i][j].y;
}
if (geoms[i][j].x < minx) {
minx = geoms[i][j].x;
}
if (geoms[i][j].y < miny) {
miny = geoms[i][j].y;
}
}
}
midx /= count;
midy /= count;
drawvec c1, c2;
if (maxy - miny > maxx - minx) {
c1 = simple_clip_poly(geoms[i], minx, miny, maxx, midy, prevent[P_SIMPLIFY_EDGE_NODES]);
c2 = simple_clip_poly(geoms[i], minx, midy, maxx, maxy, prevent[P_SIMPLIFY_EDGE_NODES]);
} else {
c1 = simple_clip_poly(geoms[i], minx, miny, midx, maxy, prevent[P_SIMPLIFY_EDGE_NODES]);
c2 = simple_clip_poly(geoms[i], midx, miny, maxx, maxy, prevent[P_SIMPLIFY_EDGE_NODES]);
}
if (c1.size() >= geoms[i].size()) {
fprintf(stderr, "Subdividing complex polygon failed\n");
} else {
out.push_back(c1);
}
if (c2.size() >= geoms[i].size()) {
fprintf(stderr, "Subdividing complex polygon failed\n");
} else {
out.push_back(c2);
}
again = true;
} else {
out.push_back(geoms[i]);
}
}
if (!again) {
return out;
}
geoms = out;
}
}
#endif
drawvec stairstep(drawvec &geom, int z, int detail) {
drawvec out;
double scale = 1 << (32 - detail - z);
for (size_t i = 0; i < geom.size(); i++) {
geom[i].x = std::round(geom[i].x / scale);
geom[i].y = std::round(geom[i].y / scale);
}
for (size_t i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_MOVETO) {
out.push_back(geom[i]);
} else if (out.size() > 0) {
long long x0 = out[out.size() - 1].x;
long long y0 = out[out.size() - 1].y;
long long x1 = geom[i].x;
long long y1 = geom[i].y;
bool swap = false;
if (y0 < y1) {
swap = true;
std::swap(x0, x1);
std::swap(y0, y1);
}
long long xx = x0, yy = y0;
long long dx = std::abs(x1 - x0);
long long sx = (x0 < x1) ? 1 : -1;
long long dy = std::abs(y1 - y0);
long long sy = (y0 < y1) ? 1 : -1;
long long err = ((dx > dy) ? dx : -dy) / 2;
int last = -1;
drawvec tmp;
tmp.push_back(draw(VT_LINETO, xx, yy));
while (xx != x1 || yy != y1) {
long long e2 = err;
if (e2 > -dx) {
err -= dy;
xx += sx;
if (last == 1) {
tmp[tmp.size() - 1] = draw(VT_LINETO, xx, yy);
} else {
tmp.push_back(draw(VT_LINETO, xx, yy));
}
last = 1;
}
if (e2 < dy) {
err += dx;
yy += sy;
if (last == 2) {
tmp[tmp.size() - 1] = draw(VT_LINETO, xx, yy);
} else {
tmp.push_back(draw(VT_LINETO, xx, yy));
}
last = 2;
}
}
if (swap) {
for (size_t j = tmp.size(); j > 0; j--) {
out.push_back(tmp[j - 1]);
}
} else {
for (size_t j = 0; j < tmp.size(); j++) {
out.push_back(tmp[j]);
}
}
// out.push_back(draw(VT_LINETO, xx, yy));
} else {
fprintf(stderr, "Can't happen: stairstepping lineto with no moveto\n");
exit(EXIT_IMPOSSIBLE);
}
}
for (size_t i = 0; i < out.size(); i++) {
out[i].x *= 1 << (32 - detail - z);
out[i].y *= 1 << (32 - detail - z);
}
return out;
}
// https://github.com/Turfjs/turf/blob/master/packages/turf-center-of-mass/index.ts
//
// The MIT License (MIT)
//
// Copyright (c) 2019 Morgan Herlocker
//
// 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
// the Software without restriction, including without limitation the rights to
// 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,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
draw centerOfMass(const drawvec &dv, size_t start, size_t end, draw centre) {
std::vector<draw> coords;
for (size_t i = start; i < end; i++) {
coords.push_back(dv[i]);
}
// First, we neutralize the feature (set it around coordinates [0,0]) to prevent rounding errors
// We take any point to translate all the points around 0
draw translation = centre;
double sx = 0;
double sy = 0;
double sArea = 0;
draw pi, pj;
double xi, xj, yi, yj, a;
std::vector<draw> neutralizedPoints;
for (size_t i = 0; i < coords.size(); i++) {
neutralizedPoints.push_back(draw(coords[i].op, coords[i].x - translation.x, coords[i].y - translation.y));
}
for (size_t i = 0; i < coords.size() - 1; i++) {
// pi is the current point
pi = neutralizedPoints[i];
xi = pi.x;
yi = pi.y;
// pj is the next point (pi+1)
pj = neutralizedPoints[i + 1];
xj = pj.x;
yj = pj.y;
// a is the common factor to compute the signed area and the final coordinates
a = xi * yj - xj * yi;
// sArea is the sum used to compute the signed area
sArea += a;
// sx and sy are the sums used to compute the final coordinates
sx += (xi + xj) * a;
sy += (yi + yj) * a;
}
// Shape has no area: fallback on turf.centroid
if (sArea == 0) {
return centre;
} else {
// Compute the signed area, and factorize 1/6A
double area = sArea * 0.5;
double areaFactor = 1 / (6 * area);
// Compute the final coordinates, adding back the values that have been neutralized
return draw(VT_MOVETO, translation.x + areaFactor * sx, translation.y + areaFactor * sy);
}
}
draw center_of_mass_mp(const drawvec &dv) {
double ringx = 0, ringy = 0;
size_t ringcount = 0;
for (size_t i = 0; i < dv.size(); i++) {
if (dv[i].op == VT_MOVETO) {
double xsum = dv[i].x, ysum = dv[i].y;
ssize_t count = 1;
size_t j;
for (j = i + 1; j < dv.size(); j++) {
if (dv[j].op != VT_LINETO) {
break;
} else {
xsum += dv[j].x;
ysum += dv[j].y;
count++;
}
}
double area = get_area(dv, i, j);
draw centroid(VT_MOVETO, std::llround(xsum / count), std::llround(ysum / count));
draw center = centerOfMass(dv, i, j, centroid);
ringx += center.x * area;
ringy += center.y * area;
ringcount += area;
i = j - 1;
}
}
draw center(VT_MOVETO, ringx / ringcount, ringy / ringcount);
return center;
}
double label_goodness(const drawvec &dv, long long x, long long y) {
int nesting = 0;
for (size_t i = 0; i < dv.size(); i++) {
if (dv[i].op == VT_MOVETO) {
size_t j;
for (j = i + 1; j < dv.size(); j++) {
if (dv[j].op != VT_LINETO) {
break;
}
}
// if it's inside the ring, and it's an outer ring,
// we are nested more; if it's an inner ring, we are
// nested less.
if (pnpoly(dv, i, j - i, x, y)) {
if (get_area(dv, i, j) >= 0) {
nesting++;
} else {
nesting--;
}
}
i = j - 1;
}
}
if (nesting < 1) {
return 0; // outside the polygon is as bad as it gets
}
double closest = INFINITY; // closest distance to the border
for (size_t i = 0; i < dv.size(); i++) {
double dx = dv[i].x - x;
double dy = dv[i].y - y;
double dist = sqrt(dx * dx + dy * dy);
if (dist < closest) {
closest = dist;
}
if (i > 0 && dv[i].op == VT_LINETO) {
dist = distance_from_line(x, y, dv[i - 1].x, dv[i - 1].y, dv[i].x, dv[i].y);
if (dist < closest) {
closest = dist;
}
}
}
return closest;
}
struct sorty {
long long x;
long long y;
};
struct sorty_sorter {
int kind;
sorty_sorter(int k)
: kind(k){};
bool operator()(const sorty &a, const sorty &b) const {
long long xa, ya, xb, yb;
if (kind == 0) { // Y first
xa = a.x;
ya = a.y;
xb = b.x;
yb = b.y;
} else if (kind == 1) { // X first
xa = a.y;
ya = a.x;
xb = b.y;
yb = b.x;
} else if (kind == 2) { // diagonal
xa = a.x + a.y;
ya = a.x - a.y;
xb = b.x + b.y;
yb = b.x - b.y;
} else { // other diagonal
xa = a.x - a.y;
ya = a.x + a.y;
xb = b.x - b.y;
yb = b.x + b.y;
}
if (ya < yb) {
return true;
} else if (ya == yb && xa < xb) {
return true;
} else {
return false;
}
};
};
struct candidate {
long long x;
long long y;
double dist;
bool operator<(const candidate &c) const {
// largest distance sorts first
return dist > c.dist;
};
};
// Generate a label point for a polygon feature.
//
// 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) {
size_t start = 0, end = 0;
size_t best_area = 0;
std::vector<sorty> points;
// find the largest outer ring, which will be the best thing
// to label if we can do it.
for (size_t i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_MOVETO) {
size_t j;
for (j = i + 1; j < geom.size(); j++) {
if (geom[j].op != VT_LINETO) {
break;
}
sorty sy;
sy.x = geom[j].x;
sy.y = geom[j].y;
points.push_back(sy);
}
double area = get_area(geom, i, j);
if (area > best_area) {
start = i;
end = j;
best_area = area;
}
i = j - 1;
}
}
// If there are no outer rings, don't generate a label point
if (best_area > 0) {
long long xsum = 0;
long long ysum = 0;
size_t count = 0;
long long xmin = LLONG_MAX, ymin = LLONG_MAX, xmax = LLONG_MIN, ymax = LLONG_MIN;
// Calculate centroid and bounding box of biggest ring.
// start + 1 to exclude the first point, which is duplicated as the last
for (size_t k = start + 1; k < end; k++) {
xsum += geom[k].x;
ysum += geom[k].y;
count++;
xmin = std::min(xmin, geom[k].x);
ymin = std::min(ymin, geom[k].y);
xmax = std::max(xmax, geom[k].x);
ymax = std::max(ymax, geom[k].y);
}
if (count > 0) {
// We want label points that are at least a moderate distance from
// the edge of the feature. The threshold for what is too close
// is derived from the area of the feature.
double radius = sqrt(best_area / M_PI);
double goodness_threshold = radius / 5;
// First choice: Turf's center of mass.
draw centroid(VT_MOVETO, xsum / count, ysum / count);
draw d = centerOfMass(geom, start, end, centroid);
double goodness = label_goodness(geom, d.x, d.y);
const char *kind = "mass";
if (goodness < goodness_threshold) {
// Label is too close to the border or outside it,
// so try some other possible points. Sort the vertices
// both by Y and X coordinate and then by diagonals,
// and walk through each set
// in sorted order. Adjacent pairs of coordinates should
// tend to bounce back and forth between rings, so the
// midpoint of each pair will hopefully be somewhere in the
// interior of the polygon.
std::vector<candidate> candidates;
for (size_t pass = 0; pass < 4; pass++) {
std::stable_sort(points.begin(), points.end(), sorty_sorter(pass));
for (size_t i = 1; i < points.size(); i++) {
double dx = points[i].x - points[i - 1].x;
double dy = points[i].y - points[i - 1].y;
double dist = sqrt(dx * dx + dy * dy);
if (dist > 2 * goodness_threshold) {
candidate c;
c.x = (points[i].x + points[i - 1].x) / 2;
c.y = (points[i].y + points[i - 1].y) / 2;
c.dist = dist;
candidates.push_back(c);
}
}
}
// Now sort the accumulate list of segment midpoints by the lengths
// of the segments. Starting from the longest
// segment, if we find one whose midpoint is inside the polygon and
// far enough from any edge to be good enough, stop looking.
std::stable_sort(candidates.begin(), candidates.end());
// only check the top 50 stride midpoints, since this list can be quite large
for (size_t i = 0; i < candidates.size() && i < 50; i++) {
double maybe_goodness = label_goodness(geom, candidates[i].x, candidates[i].y);
if (maybe_goodness > goodness) {
d.x = candidates[i].x;
d.y = candidates[i].y;
goodness = maybe_goodness;
kind = "diagonal";
if (goodness > goodness_threshold) {
break;
}
}
}
}
// We may still not have anything decent, so the next thing to look at
// is points from gridding the bounding box of the largest ring.
if (goodness < goodness_threshold) {
for (long long sub = 2;
sub < 32 && (xmax - xmin) > 2 * sub && (ymax - ymin) > 2 * sub;
sub *= 2) {
for (long long x = 1; x < sub; x++) {
for (long long y = 1; y < sub; y++) {
draw maybe(VT_MOVETO,
xmin + x * (xmax - xmin) / sub,
ymin + y * (ymax - ymin) / sub);
double maybe_goodness = label_goodness(geom, maybe.x, maybe.y);
if (maybe_goodness > goodness) {
// better than the previous
d = maybe;
goodness = maybe_goodness;
kind = "grid";
}
}
}
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.
double maybe_goodness = label_goodness(geom, centroid.x, centroid.y);
if (maybe_goodness > goodness) {
d = centroid;
goodness = maybe_goodness;
kind = "centroid";
}
if (goodness <= 0) {
double lon, lat;
tile2lonlat(d.x, d.y, 32, &lon, &lat);
static std::atomic<long long> warned(0);
if (warned++ < 10) {
fprintf(stderr, "could not find good label point: %s %f,%f\n", kind, lat, lon);
}
}
}
drawvec dv;
dv.push_back(d);
return dv;
}
}
return drawvec();
}
drawvec checkerboard_anchors(drawvec const &geom, int tx, int ty, int z, unsigned long long label_point) {
drawvec out;
// anchor point in world coordinates
unsigned wx, wy;
decode_index(label_point, &wx, &wy);
// upper left of tile in world coordinates
long long tx1 = 0, ty1 = 0;
// lower right of tile in world coordinates;
long long tx2 = 1LL << 32; // , ty2 = 1LL << 32;
if (z != 0) {
tx1 = (long long) tx << (32 - z);
ty1 = (long long) ty << (32 - z);
tx2 = (long long) (tx + 1) << (32 - z);
// ty2 = (long long) (ty + 1) << (32 - z);
}
// upper left of feature in world coordinates
long long bx1 = LLONG_MAX, by1 = LLONG_MAX;
// lower right of feature in world coordinates;
long long bx2 = LLONG_MIN, by2 = LLONG_MIN;
for (auto const &g : geom) {
bx1 = std::min(bx1, g.x + tx1);
by1 = std::min(by1, g.y + ty1);
bx2 = std::max(bx2, g.x + tx1);
by2 = std::max(by2, g.y + ty1);
}
if (bx1 > bx2 || by1 > by2) {
return out;
}
// labels repeat every 0.3 tiles at z0
double spiral_dist = 0.3;
if (z > 0) {
// only every ~6 tiles by the time we get to z15
spiral_dist = spiral_dist * exp(log(z) * 1.2);
}
const long long label_spacing = spiral_dist * (tx2 - tx1);
long long x1 = floor(std::min(bx1 - wx, bx2 - wx) / label_spacing);
long long x2 = ceil(std::max(bx1 - wx, bx2 - wx) / label_spacing);
long long y1 = floor(std::min(by1 - wy, by2 - wy) / label_spacing - 0.5);
long long y2 = ceil(std::max(by1 - wy, by2 - wy) / label_spacing);
for (long long lx = x1; lx <= x2; lx++) {
for (long long ly = y1; ly <= y2; ly++) {
long long x = lx * label_spacing + wx;
long long y = ly * label_spacing + wy;
if (((unsigned long long) lx & 1) == 1) {
y += label_spacing / 2;
}
if (x < bx1 || x > bx2 || y < by1 || y > by2) {
continue;
}
// If it's the central label, it's the best we've got,
// so accept it in any case. If it's from the outer spiral,
// don't use it if it's too close to a border.
if (lx == 0 && ly == 0) {
out.push_back(draw(VT_MOVETO, x - tx1, y - ty1));
break;
} else {
double tilesize = 1LL << (32 - z);
double goodness_threshold = tilesize / 100;
if (label_goodness(geom, x - tx1, y - ty1) > goodness_threshold) {
out.push_back(draw(VT_MOVETO, x - tx1, y - ty1));
break;
}
}
}
}
return out;
}