mirror of
https://github.com/felt/tippecanoe.git
synced 2026-10-02 08:25:40 +02:00
1402 lines
38 KiB
C++
1402 lines
38 KiB
C++
#include <iostream>
|
|
#include <fstream>
|
|
#include <string>
|
|
#include <stack>
|
|
#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"
|
|
#include "projection.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", geom[k].x, geom[k].y, (long long) k, (long long) outer_start, (long long) (outer_start + outer_len));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
double get_perimeter(const drawvec &geom, size_t i, size_t j) {
|
|
double perimeter = 0;
|
|
|
|
for (size_t k = i; k + 1 < j; k++) {
|
|
double dx = geom[k].x - geom[k + 1].x;
|
|
double dy = geom[k].y - geom[k + 1].y;
|
|
perimeter += sqrt(dx * dx + dy * dy);
|
|
}
|
|
|
|
return perimeter;
|
|
}
|
|
|
|
drawvec reduce_tiny_poly(drawvec const &geom, int z, int detail, bool *still_needs_simplification, bool *reduced_away, double *accum_area, serial_feature *this_feature, serial_feature *tiny_feature) {
|
|
drawvec out;
|
|
const double pixel = (1LL << (32 - detail - z)) * (double) tiny_polygon_size;
|
|
bool includes_real = false;
|
|
bool includes_dust = false;
|
|
|
|
bool included_last_outer = false;
|
|
*still_needs_simplification = false;
|
|
*reduced_away = false;
|
|
|
|
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);
|
|
double schwartzberg = 1;
|
|
|
|
if (area != 0) {
|
|
// polygon compactness measure
|
|
// https://fisherzachary.github.io/public/r-output.html
|
|
// Schwartzberg, Joseph E. 1965. “Reapportionment, gerrymanders, and the notion of compactness”.
|
|
// In: Minn. L. Rev. 50, 443.
|
|
|
|
double perimeter = get_perimeter(geom, i, j);
|
|
schwartzberg = 1 / (perimeter / (2 * M_PI * sqrt(std::fabs(area) / M_PI)));
|
|
}
|
|
|
|
// XXX There is an ambiguity here: If the area of a ring is 0 and it is followed by holes,
|
|
// we don't know whether the area-0 ring was a hole too or whether it was the outer ring
|
|
// that these subsequent holes are somehow being subtracted from. I hope that if a polygon
|
|
// was simplified down to nothing, its holes also became nothing.
|
|
|
|
if (area != 0) {
|
|
// These are pixel coordinates, so area > 0 for the outer ring.
|
|
// If the outer ring of a polygon was reduced to a pixel, its
|
|
// inner rings must just have their area de-accumulated rather
|
|
// than being drawn since we don't really know where they are.
|
|
|
|
// i.e., this outer ring is small enough that we are including it
|
|
// in a tiny polygon rather than letting it represent itself,
|
|
// OR it is an inner ring and we haven't output an outer ring for it to be
|
|
// cut out of, so we are just subtracting its area from the tiny polygon
|
|
// rather than trying to deal with it geometrically
|
|
if ((area > 0 && area <= pixel * pixel && schwartzberg > 0.3) ||
|
|
(area < 0 && !included_last_outer)) {
|
|
*accum_area += area;
|
|
*reduced_away = true;
|
|
|
|
if (area > 0 && *accum_area > pixel * pixel) {
|
|
// XXX use centroid;
|
|
|
|
out.emplace_back(VT_MOVETO, geom[i].x - pixel / 2, geom[i].y - pixel / 2);
|
|
out.emplace_back(VT_LINETO, geom[i].x - pixel / 2 + pixel, geom[i].y - pixel / 2);
|
|
out.emplace_back(VT_LINETO, geom[i].x - pixel / 2 + pixel, geom[i].y - pixel / 2 + pixel);
|
|
out.emplace_back(VT_LINETO, geom[i].x - pixel / 2, geom[i].y - pixel / 2 + pixel);
|
|
out.emplace_back(VT_LINETO, geom[i].x - pixel / 2, geom[i].y - pixel / 2);
|
|
includes_dust = true;
|
|
|
|
*accum_area -= pixel * pixel;
|
|
}
|
|
|
|
if (area > 0) {
|
|
included_last_outer = false;
|
|
}
|
|
}
|
|
// i.e., this ring is large enough that it gets to represent itself
|
|
// or it is a tiny hole out of a real polygon, which we are still treating
|
|
// as a real geometry because otherwise we can accumulate enough tiny holes
|
|
// that we will drop the next several outer rings getting back up to 0.
|
|
else {
|
|
for (size_t k = i; k < j && k < geom.size(); k++) {
|
|
out.push_back(geom[k]);
|
|
}
|
|
|
|
// which means that the overall polygon has a real geometry,
|
|
// which means that it gets to be simplified.
|
|
*still_needs_simplification = true;
|
|
includes_real = true;
|
|
|
|
if (area > 0) {
|
|
included_last_outer = true;
|
|
}
|
|
}
|
|
} else {
|
|
// area is 0: doesn't count as either having been reduced away,
|
|
// since it was probably just degenerate from having been clipped,
|
|
// or as needing simplification, since it produces no output.
|
|
}
|
|
|
|
i = j - 1;
|
|
} else {
|
|
fprintf(stderr, "how did we get here with %d in %d?\n", geom[i].op, (int) geom.size());
|
|
|
|
for (size_t n = 0; n < geom.size(); n++) {
|
|
fprintf(stderr, "%d/%lld/%lld ", geom[n].op, geom[n].x, geom[n].y);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
|
|
out.push_back(geom[i]);
|
|
includes_real = true;
|
|
}
|
|
}
|
|
|
|
if (!includes_real) {
|
|
if (includes_dust) {
|
|
// this geometry is just dust, so if there is another feature that
|
|
// contributed to the dust that is larger than this feature,
|
|
// keep its attributes instead of this one that just happened to be
|
|
// the one that hit the threshold of survival.
|
|
|
|
if (tiny_feature->extent > this_feature->extent) {
|
|
*this_feature = *tiny_feature;
|
|
tiny_feature->extent = 0;
|
|
}
|
|
} else {
|
|
// this is a feature that we are throwing away, so hang on to it
|
|
// attributes if it is bigger than the biggest one we threw away so far
|
|
|
|
if (this_feature->extent > tiny_feature->extent) {
|
|
*tiny_feature = *this_feature;
|
|
}
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
double distance_from_line(long long point_x, long long point_y, long long segA_x, long long segA_y, long long segB_x, long long segB_y) {
|
|
long long p2x = segB_x - segA_x;
|
|
long long p2y = segB_y - segA_y;
|
|
double something = p2x * p2x + p2y * p2y;
|
|
double u = (0 == something) ? 0 : ((point_x - segA_x) * p2x + (point_y - segA_y) * p2y) / (something);
|
|
|
|
if (u >= 1) {
|
|
u = 1;
|
|
} else if (u <= 0) {
|
|
u = 0;
|
|
}
|
|
|
|
double x = segA_x + u * p2x;
|
|
double y = segA_y + u * p2y;
|
|
|
|
double dx = x - point_x;
|
|
double dy = y - point_y;
|
|
|
|
double out = std::round(sqrt(dx * dx + dy * dy) * 16.0) / 16.0;
|
|
return out;
|
|
}
|
|
|
|
// https://github.com/Project-OSRM/osrm-backend/blob/733d1384a40f/Algorithms/DouglasePeucker.cpp
|
|
static void douglas_peucker(drawvec &geom, int start, int n, double e, size_t kept, size_t retain) {
|
|
std::stack<int> recursion_stack;
|
|
|
|
if (!geom[start + 0].necessary || !geom[start + n - 1].necessary) {
|
|
fprintf(stderr, "endpoints not marked necessary\n");
|
|
exit(EXIT_IMPOSSIBLE);
|
|
}
|
|
|
|
int prev = 0;
|
|
for (int here = 1; here < n; here++) {
|
|
if (geom[start + here].necessary) {
|
|
recursion_stack.push(prev);
|
|
recursion_stack.push(here);
|
|
prev = here;
|
|
|
|
if (prevent[P_SIMPLIFY_SHARED_NODES]) {
|
|
if (retain > 0) {
|
|
retain--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// These segments are put on the stack from start to end,
|
|
// independent of winding, so note that anything that uses
|
|
// "retain" to force it to keep at least N points will
|
|
// keep a different set of points when wound one way than
|
|
// when wound the other way.
|
|
|
|
while (!recursion_stack.empty()) {
|
|
// pop next element
|
|
int second = recursion_stack.top();
|
|
recursion_stack.pop();
|
|
int first = recursion_stack.top();
|
|
recursion_stack.pop();
|
|
|
|
double max_distance = -1;
|
|
int farthest_element_index;
|
|
|
|
// find index idx of element with max_distance
|
|
int i;
|
|
if (geom[start + first] < geom[start + second]) {
|
|
farthest_element_index = first;
|
|
for (i = first + 1; i < second; i++) {
|
|
double temp_dist = distance_from_line(geom[start + i].x, geom[start + i].y, geom[start + first].x, geom[start + first].y, geom[start + second].x, geom[start + second].y);
|
|
|
|
double distance = std::fabs(temp_dist);
|
|
|
|
if ((distance > e || kept < retain) && (distance > max_distance || (distance == max_distance && geom[start + i] < geom[start + farthest_element_index]))) {
|
|
farthest_element_index = i;
|
|
max_distance = distance;
|
|
}
|
|
}
|
|
} else {
|
|
farthest_element_index = second;
|
|
for (i = second - 1; i > first; i--) {
|
|
double temp_dist = distance_from_line(geom[start + i].x, geom[start + i].y, geom[start + second].x, geom[start + second].y, geom[start + first].x, geom[start + first].y);
|
|
|
|
double distance = std::fabs(temp_dist);
|
|
|
|
if ((distance > e || kept < retain) && (distance > max_distance || (distance == max_distance && geom[start + i] < geom[start + farthest_element_index]))) {
|
|
farthest_element_index = i;
|
|
max_distance = distance;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (max_distance >= 0) {
|
|
// mark idx as necessary
|
|
geom[start + farthest_element_index].necessary = 1;
|
|
kept++;
|
|
|
|
if (geom[start + first] < geom[start + second]) {
|
|
if (1 < farthest_element_index - first) {
|
|
recursion_stack.push(first);
|
|
recursion_stack.push(farthest_element_index);
|
|
}
|
|
if (1 < second - farthest_element_index) {
|
|
recursion_stack.push(farthest_element_index);
|
|
recursion_stack.push(second);
|
|
}
|
|
} else {
|
|
if (1 < second - farthest_element_index) {
|
|
recursion_stack.push(farthest_element_index);
|
|
recursion_stack.push(second);
|
|
}
|
|
if (1 < farthest_element_index - first) {
|
|
recursion_stack.push(first);
|
|
recursion_stack.push(farthest_element_index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
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));
|
|
}
|
|
|
|
// to quadkey
|
|
struct node n;
|
|
n.index = encode_quadkey((unsigned) d.x, (unsigned) d.y);
|
|
|
|
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);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
drawvec fix_polygon(const drawvec &geom) {
|
|
int outer = 1;
|
|
drawvec out;
|
|
|
|
for (size_t i = 0; i < geom.size(); i++) {
|
|
if (geom[i].op == VT_CLOSEPATH) {
|
|
outer = 1;
|
|
} else if (geom[i].op == VT_MOVETO) {
|
|
// Find the end of the ring
|
|
|
|
size_t j;
|
|
for (j = i + 1; j < geom.size(); j++) {
|
|
if (geom[j].op != VT_LINETO) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// A polygon ring must contain at least three points
|
|
// (and really should contain four). If this one does
|
|
// not have any, avoid a division by zero trying to
|
|
// calculate the centroid below.
|
|
if (j - i < 1) {
|
|
i = j - 1;
|
|
outer = 0;
|
|
continue;
|
|
}
|
|
|
|
// Make a temporary copy of the ring.
|
|
// Close it if it isn't closed.
|
|
|
|
drawvec ring;
|
|
for (size_t a = i; a < j; a++) {
|
|
ring.push_back(geom[a]);
|
|
}
|
|
if (j - i != 0 && (ring[0].x != ring[j - i - 1].x || ring[0].y != ring[j - i - 1].y)) {
|
|
ring.push_back(ring[0]);
|
|
}
|
|
|
|
// A polygon ring at this point should contain at least four points.
|
|
// Flesh it out with some vertex copies if it doesn't.
|
|
|
|
while (ring.size() < 4) {
|
|
ring.push_back(ring[0]);
|
|
}
|
|
|
|
// Reverse ring if winding order doesn't match
|
|
// inner/outer expectation
|
|
|
|
bool reverse_ring = false;
|
|
if (prevent[P_USE_SOURCE_POLYGON_WINDING]) {
|
|
// GeoJSON winding is reversed from vector winding
|
|
reverse_ring = true;
|
|
} else if (prevent[P_REVERSE_SOURCE_POLYGON_WINDING]) {
|
|
// GeoJSON winding is reversed from vector winding
|
|
reverse_ring = false;
|
|
} else {
|
|
double area = get_area(ring, 0, ring.size());
|
|
if ((area > 0) != outer) {
|
|
reverse_ring = true;
|
|
}
|
|
}
|
|
|
|
if (reverse_ring) {
|
|
drawvec tmp;
|
|
for (int a = ring.size() - 1; a >= 0; a--) {
|
|
tmp.push_back(ring[a]);
|
|
}
|
|
ring = tmp;
|
|
}
|
|
|
|
// Now we are rotating the ring to make the first/last point
|
|
// one that would be unlikely to be simplified away.
|
|
|
|
// calculate centroid
|
|
// a + 1 < size() because point 0 is duplicated at the end
|
|
long long xtotal = 0;
|
|
long long ytotal = 0;
|
|
long long count = 0;
|
|
for (size_t a = 0; a + 1 < ring.size(); a++) {
|
|
xtotal += ring[a].x;
|
|
ytotal += ring[a].y;
|
|
count++;
|
|
}
|
|
xtotal /= count;
|
|
ytotal /= count;
|
|
|
|
// figure out which point is furthest from the centroid
|
|
long long dist2 = 0;
|
|
long long furthest = 0;
|
|
for (size_t a = 0; a + 1 < ring.size(); a++) {
|
|
// division by 16 because these are z0 coordinates and we need to avoid overflow
|
|
long long xd = (ring[a].x - xtotal) / 16;
|
|
long long yd = (ring[a].y - ytotal) / 16;
|
|
long long d2 = xd * xd + yd * yd;
|
|
if (d2 > dist2 || (d2 == dist2 && ring[a] < ring[furthest])) {
|
|
dist2 = d2;
|
|
furthest = a;
|
|
}
|
|
}
|
|
|
|
// then figure out which point is furthest from *that*,
|
|
// which will hopefully be a good origin point since it should be
|
|
// at a far edge of the shape.
|
|
long long dist2b = 0;
|
|
long long furthestb = 0;
|
|
for (size_t a = 0; a + 1 < ring.size(); a++) {
|
|
// division by 16 because these are z0 coordinates and we need to avoid overflow
|
|
long long xd = (ring[a].x - ring[furthest].x) / 16;
|
|
long long yd = (ring[a].y - ring[furthest].y) / 16;
|
|
long long d2 = xd * xd + yd * yd;
|
|
if (d2 > dist2b || (d2 == dist2b && ring[a] < ring[furthestb])) {
|
|
dist2b = d2;
|
|
furthestb = a;
|
|
}
|
|
}
|
|
|
|
// rotate ring so the furthest point is the duplicated one.
|
|
// the idea is that simplification will then be more efficient,
|
|
// never wasting the start and end points, which are always retained,
|
|
// on a point that has little impact on the shape.
|
|
|
|
// Copy ring into output, fixing the moveto/lineto ops if necessary because of
|
|
// reversal or closing
|
|
|
|
for (size_t a = 0; a < ring.size(); a++) {
|
|
size_t a2 = (a + furthestb) % (ring.size() - 1);
|
|
|
|
if (a == 0) {
|
|
out.push_back(draw(VT_MOVETO, ring[a2].x, ring[a2].y));
|
|
} else {
|
|
out.push_back(draw(VT_LINETO, ring[a2].x, ring[a2].y));
|
|
}
|
|
}
|
|
|
|
// Next ring or polygon begins on the non-lineto that ended this one
|
|
// and is not an outer ring unless there is a terminator first
|
|
|
|
i = j - 1;
|
|
outer = 0;
|
|
} else {
|
|
fprintf(stderr, "Internal error: polygon ring begins with %d, not moveto\n", geom[i].op);
|
|
exit(EXIT_IMPOSSIBLE);
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
#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);
|
|
}
|
|
}
|
|
|
|
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::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::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;
|
|
}
|