#include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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 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 p; p.push_back(std::move(lr)); mp.push_back(std::move(p)); } } i = j - 1; } } mapbox::geometry::multi_polygon 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 chop_polygon(std::vector &geoms) { while (1) { bool again = false; std::vector 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 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 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 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 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 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; }