mirror of
https://github.com/felt/tippecanoe.git
synced 2026-10-02 16:35:40 +02:00
Look up shared nodes once per vertex instead of once per tile
With --no-simplification-of-shared-nodes, simplify_lines() used to offset every vertex of every feature to world coordinates and check it against the Bloom filter and the global sorted list of shared nodes, in every tile at every zoom level. Whether a vertex is a shared node depends only on its world coordinates, so now it is found once, after the list of shared nodes has been made and before the geometry is sorted, by a parallel pass over each reader's geometry that marks each vertex in place, in the upper bits of its serialized operation byte. Decoding puts that state into a new field of draw (which still fits in 16 bytes), and it is carried through clipping, the copies across the antimeridian at z0, and the geometry written for the next zoom level. Polygon cleaning of coalesced features restores the state of any output vertex with the same coordinates as an input vertex. Vertices whose state is still unknown, because they were created by clipping or polygon cleaning or came back from a prefilter, are still looked up in the global list when they are simplified, so the output is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P2nBqZisxNQfmEmon3vE9v
This commit is contained in:
@@ -1,3 +1,14 @@
|
||||
# 2.83.0
|
||||
|
||||
* Speed up `--no-simplification-of-shared-nodes` by checking each vertex
|
||||
against the global list of shared nodes only once, before tiling begins,
|
||||
instead of again in every tile at every zoom level. Each vertex now carries
|
||||
whether it is a shared node, in the upper bits of its serialized operation
|
||||
byte and in a new field of `draw`, through clipping and into the geometry
|
||||
for the next zoom level. Only vertices that are created during tiling,
|
||||
by clipping or by polygon cleaning, or that come back from a prefilter,
|
||||
still need to be looked up. Output is unchanged.
|
||||
|
||||
# 2.82.0
|
||||
|
||||
* Fix corruption of a JSON array when a non-final element was removed from it.
|
||||
|
||||
@@ -16,10 +16,30 @@
|
||||
#include "projection.hpp"
|
||||
#include "read_json.hpp"
|
||||
|
||||
static std::vector<std::pair<double, double>> clip_poly1(std::vector<std::pair<double, double>> &geom,
|
||||
long long minx, long long miny, long long maxx, long long maxy,
|
||||
long long ax, long long ay, long long bx, long long by, drawvec &edge_nodes,
|
||||
bool prevent_simplify_shared_nodes);
|
||||
// A vertex being clipped, which remembers whether it is a shared node.
|
||||
// Vertices created at the intersections with the clipping edges don't know.
|
||||
struct clip_point_d {
|
||||
double first;
|
||||
double second;
|
||||
signed char node = NODE_UNKNOWN;
|
||||
|
||||
clip_point_d()
|
||||
: first(0), second(0) {
|
||||
}
|
||||
|
||||
clip_point_d(double x, double y)
|
||||
: first(x), second(y) {
|
||||
}
|
||||
|
||||
clip_point_d(double x, double y, signed char n)
|
||||
: first(x), second(y), node(n) {
|
||||
}
|
||||
};
|
||||
|
||||
static std::vector<clip_point_d> clip_poly1(std::vector<clip_point_d> &geom,
|
||||
long long minx, long long miny, long long maxx, long long maxy,
|
||||
long long ax, long long ay, long long bx, long long by, drawvec &edge_nodes,
|
||||
bool prevent_simplify_shared_nodes);
|
||||
|
||||
drawvec simple_clip_poly(drawvec &geom, long long minx, long long miny, long long maxx, long long maxy,
|
||||
long long ax, long long ay, long long bx, long long by, drawvec &edge_nodes, bool prevent_simplify_shared_nodes) {
|
||||
@@ -37,11 +57,11 @@ drawvec simple_clip_poly(drawvec &geom, long long minx, long long miny, long lon
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<double, double>> tmp;
|
||||
std::vector<clip_point_d> tmp;
|
||||
for (size_t k = i; k < j; k++) {
|
||||
double x = geom[k].x;
|
||||
double y = geom[k].y;
|
||||
tmp.emplace_back(x, y);
|
||||
tmp.emplace_back(x, y, geom[k].node);
|
||||
}
|
||||
tmp = clip_poly1(tmp, minx, miny, maxx, maxy, ax, ay, bx, by, edge_nodes, prevent_simplify_shared_nodes);
|
||||
if (tmp.size() > 0) {
|
||||
@@ -52,9 +72,9 @@ drawvec simple_clip_poly(drawvec &geom, long long minx, long long miny, long lon
|
||||
}
|
||||
for (size_t k = 0; k < tmp.size(); k++) {
|
||||
if (k == 0) {
|
||||
out.push_back(draw(VT_MOVETO, std::round(tmp[k].first), std::round(tmp[k].second)));
|
||||
out.push_back(draw(VT_MOVETO, std::round(tmp[k].first), std::round(tmp[k].second), tmp[k].node));
|
||||
} else {
|
||||
out.push_back(draw(VT_LINETO, std::round(tmp[k].first), std::round(tmp[k].second)));
|
||||
out.push_back(draw(VT_LINETO, std::round(tmp[k].first), std::round(tmp[k].second), tmp[k].node));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,13 +146,14 @@ drawvec clip_lines(drawvec &geom, long long minx, long long miny, long long maxx
|
||||
int c = clip(&x1, &y1, &x2, &y2, minx, miny, maxx, maxy);
|
||||
|
||||
if (c > 1) { // clipped
|
||||
out.push_back(draw(VT_MOVETO, x1, y1));
|
||||
out.push_back(draw(VT_LINETO, x2, y2));
|
||||
out.push_back(draw(VT_MOVETO, geom[i].x, geom[i].y));
|
||||
// endpoints that weren't moved by clipping keep their node state
|
||||
out.push_back(draw(VT_MOVETO, x1, y1, (x1 == geom[i - 1].x && y1 == geom[i - 1].y) ? geom[i - 1].node : NODE_UNKNOWN));
|
||||
out.push_back(draw(VT_LINETO, x2, y2, (x2 == geom[i].x && y2 == geom[i].y) ? geom[i].node : NODE_UNKNOWN));
|
||||
out.push_back(draw(VT_MOVETO, geom[i].x, geom[i].y, geom[i].node));
|
||||
} else if (c == 1) { // unchanged
|
||||
out.push_back(geom[i]);
|
||||
} else { // clipped away entirely
|
||||
out.push_back(draw(VT_MOVETO, geom[i].x, geom[i].y));
|
||||
out.push_back(draw(VT_MOVETO, geom[i].x, geom[i].y, geom[i].node));
|
||||
}
|
||||
} else {
|
||||
out.push_back(geom[i]);
|
||||
@@ -739,7 +760,7 @@ drawvec close_poly(drawvec &geom) {
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool inside(std::pair<double, double> d, int edge, long long minx, long long miny, long long maxx, long long maxy) {
|
||||
static bool inside(clip_point_d const &d, int edge, long long minx, long long miny, long long maxx, long long maxy) {
|
||||
switch (edge) {
|
||||
case 0: // top
|
||||
return d.second > miny;
|
||||
@@ -758,41 +779,56 @@ static bool inside(std::pair<double, double> d, int edge, long long minx, long l
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static std::pair<double, double> intersect(std::pair<double, double> a, std::pair<double, double> b, int edge, long long minx, long long miny, long long maxx, long long maxy) {
|
||||
static clip_point_d intersect1(clip_point_d const &a, clip_point_d const &b, int edge, long long minx, long long miny, long long maxx, long long maxy) {
|
||||
switch (edge) {
|
||||
case 0: // top
|
||||
return std::pair<double, double>((a.first + (double) (b.first - a.first) * (miny - a.second) / (b.second - a.second)), miny);
|
||||
return clip_point_d((a.first + (double) (b.first - a.first) * (miny - a.second) / (b.second - a.second)), miny);
|
||||
|
||||
case 1: // right
|
||||
return std::pair<double, double>(maxx, (a.second + (double) (b.second - a.second) * (maxx - a.first) / (b.first - a.first)));
|
||||
return clip_point_d(maxx, (a.second + (double) (b.second - a.second) * (maxx - a.first) / (b.first - a.first)));
|
||||
|
||||
case 2: // bottom
|
||||
return std::pair<double, double>((a.first + (double) (b.first - a.first) * (maxy - a.second) / (b.second - a.second)), maxy);
|
||||
return clip_point_d((a.first + (double) (b.first - a.first) * (maxy - a.second) / (b.second - a.second)), maxy);
|
||||
|
||||
case 3: // left
|
||||
return std::pair<double, double>(minx, (a.second + (double) (b.second - a.second) * (minx - a.first) / (b.first - a.first)));
|
||||
return clip_point_d(minx, (a.second + (double) (b.second - a.second) * (minx - a.first) / (b.first - a.first)));
|
||||
}
|
||||
|
||||
fprintf(stderr, "internal error intersecting\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// If the intersection is at one of the endpoints of the segment,
|
||||
// it is the same vertex, and has the same node state.
|
||||
static clip_point_d intersect(clip_point_d const &a, clip_point_d const &b, int edge, long long minx, long long miny, long long maxx, long long maxy) {
|
||||
clip_point_d p = intersect1(a, b, edge, minx, miny, maxx, maxy);
|
||||
double x = std::round(p.first), y = std::round(p.second);
|
||||
|
||||
if (x == a.first && y == a.second) {
|
||||
p.node = a.node;
|
||||
} else if (x == b.first && y == b.second) {
|
||||
p.node = b.node;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// http://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm
|
||||
static std::vector<std::pair<double, double>> clip_poly1(std::vector<std::pair<double, double>> &geom,
|
||||
long long minx, long long miny, long long maxx, long long maxy,
|
||||
long long ax, long long ay, long long bx, long long by, drawvec &edge_nodes,
|
||||
bool prevent_simplify_shared_nodes) {
|
||||
std::vector<std::pair<double, double>> out = geom;
|
||||
static std::vector<clip_point_d> clip_poly1(std::vector<clip_point_d> &geom,
|
||||
long long minx, long long miny, long long maxx, long long maxy,
|
||||
long long ax, long long ay, long long bx, long long by, drawvec &edge_nodes,
|
||||
bool prevent_simplify_shared_nodes) {
|
||||
std::vector<clip_point_d> out = geom;
|
||||
|
||||
for (int edge = 0; edge < 4; edge++) {
|
||||
if (out.size() > 0) {
|
||||
std::vector<std::pair<double, double>> in = out;
|
||||
std::vector<clip_point_d> in = out;
|
||||
out.resize(0);
|
||||
|
||||
std::pair<double, double> S = in[in.size() - 1];
|
||||
clip_point_d S = in[in.size() - 1];
|
||||
|
||||
for (size_t e = 0; e < in.size(); e++) {
|
||||
std::pair<double, double> E = in[e];
|
||||
clip_point_d E = in[e];
|
||||
|
||||
if (!inside(S, edge, minx, miny, maxx, maxy)) {
|
||||
// was outside the buffer
|
||||
|
||||
+69
-19
@@ -37,6 +37,9 @@ drawvec decode_geometry(const char **meta, int z, unsigned tx, unsigned ty, long
|
||||
break;
|
||||
}
|
||||
|
||||
d.node = (d.op >> NODE_SHIFT) & NODE_MASK;
|
||||
d.op &= OP_MASK;
|
||||
|
||||
if (d.op == VT_MOVETO || d.op == VT_LINETO) {
|
||||
long long dx, dy;
|
||||
|
||||
@@ -216,6 +219,49 @@ drawvec impose_tile_boundaries(const drawvec &geom, long long extent) {
|
||||
return out;
|
||||
}
|
||||
|
||||
// Is the vertex at world coordinates wx, wy one of the nodes in the global list of shared nodes?
|
||||
bool is_shared_node(long long wx, long long wy, struct node const *shared_nodes_map, size_t nodepos, std::string const &shared_nodes_bloom) {
|
||||
struct node n;
|
||||
n.index = encode_vertex((unsigned) wx, (unsigned) wy);
|
||||
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) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Operations like polygon cleaning that construct new geometries don't know about
|
||||
// the node states of the vertices. But since the node state is a function only
|
||||
// of the vertex's coordinates, any vertex in the output that has the same coordinates
|
||||
// as a vertex in the input must have the same node state as that vertex did.
|
||||
void restore_node_states(drawvec const &from, drawvec &to) {
|
||||
drawvec known;
|
||||
for (auto const &d : from) {
|
||||
if ((d.op == VT_MOVETO || d.op == VT_LINETO) && d.node != NODE_UNKNOWN) {
|
||||
known.push_back(d);
|
||||
}
|
||||
}
|
||||
if (known.size() == 0) {
|
||||
return;
|
||||
}
|
||||
std::sort(known.begin(), known.end());
|
||||
|
||||
for (auto &d : to) {
|
||||
if ((d.op == VT_MOVETO || d.op == VT_LINETO) && d.node == NODE_UNKNOWN) {
|
||||
auto pt = std::lower_bound(known.begin(), known.end(), d);
|
||||
if (pt != known.end() && *pt == d) {
|
||||
d.node = pt->node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -236,33 +282,37 @@ drawvec simplify_lines(drawvec &geom, int z, int tx, int ty, int detail, bool ma
|
||||
// * 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.
|
||||
// is in global coordinates.
|
||||
//
|
||||
// The vertices from the original geometry were already checked against the
|
||||
// shared_nodes_map before tiling began, and carry the result of that check
|
||||
// in their node state, so it is only vertices that have been created since then,
|
||||
// by clipping or polygon cleaning, that need to be looked up in it here.
|
||||
// To look through it, we need to offset the coordinates to global.
|
||||
//
|
||||
// If the shared_nodes_map is NULL, the caller doesn't want the global
|
||||
// shared nodes to be preserved, so the node states are ignored too.
|
||||
|
||||
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));
|
||||
if (shared_nodes_map != NULL && nodepos > 0) {
|
||||
if (geom[i].node == NODE_UNKNOWN) {
|
||||
// offset to global
|
||||
long long wx = geom[i].x;
|
||||
long long wy = geom[i].y;
|
||||
if (z != 0) {
|
||||
wx += tx * (1LL << (32 - z));
|
||||
wy += ty * (1LL << (32 - z));
|
||||
}
|
||||
|
||||
geom[i].node = is_shared_node(wx, wy, shared_nodes_map, nodepos, shared_nodes_bloom) ? NODE_SHARED : NODE_NOT_SHARED;
|
||||
}
|
||||
|
||||
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 (geom[i].node == NODE_SHARED) {
|
||||
geom[i].necessary = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+33
-2
@@ -20,6 +20,22 @@
|
||||
#define VT_LINETO 2
|
||||
#define VT_CLOSEPATH 7
|
||||
|
||||
// Whether a vertex is one of the shared nodes that --no-simplification-of-shared-nodes
|
||||
// must not simplify away. This is a function only of the vertex's world coordinates,
|
||||
// so it is found once, before tiling, from the global list of shared nodes, and then
|
||||
// carried along with the vertex, instead of being looked up again for each tile.
|
||||
// Vertices that are created during tiling, by clipping or polygon cleaning,
|
||||
// start out NODE_UNKNOWN and are looked up in the global list if they are simplified.
|
||||
#define NODE_UNKNOWN 0
|
||||
#define NODE_NOT_SHARED 1
|
||||
#define NODE_SHARED 2
|
||||
|
||||
// In serialized geometry, the node state is stored in the upper bits
|
||||
// of the byte that holds each vertex's operation.
|
||||
#define NODE_SHIFT 4
|
||||
#define NODE_MASK 3
|
||||
#define OP_MASK 0x0F
|
||||
|
||||
// The bitfield is to make sizeof(draw) be 16 instead of 24
|
||||
// at the cost, apparently, of a 0.7% increase in running time
|
||||
// for packing and unpacking.
|
||||
@@ -28,19 +44,30 @@ struct draw {
|
||||
signed char op;
|
||||
long long y : 40;
|
||||
signed char necessary;
|
||||
signed char node;
|
||||
|
||||
draw(int nop, long long nx, long long ny)
|
||||
: x(nx),
|
||||
op(nop),
|
||||
y(ny),
|
||||
necessary(0) {
|
||||
necessary(0),
|
||||
node(NODE_UNKNOWN) {
|
||||
}
|
||||
|
||||
draw(int nop, long long nx, long long ny, signed char nnode)
|
||||
: x(nx),
|
||||
op(nop),
|
||||
y(ny),
|
||||
necessary(0),
|
||||
node(nnode) {
|
||||
}
|
||||
|
||||
draw()
|
||||
: x(0),
|
||||
op(0),
|
||||
y(0),
|
||||
necessary(0) {
|
||||
necessary(0),
|
||||
node(NODE_UNKNOWN) {
|
||||
}
|
||||
|
||||
bool operator<(draw const &s) const {
|
||||
@@ -64,6 +91,8 @@ struct draw {
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(draw) == 16, "draw should still fit in 16 bytes");
|
||||
|
||||
typedef std::vector<draw> drawvec;
|
||||
struct serial_feature;
|
||||
|
||||
@@ -81,6 +110,8 @@ drawvec stairstep(drawvec &geom, int z, int detail);
|
||||
bool point_within_tile(long long x, long long y, int z);
|
||||
int quick_check(const long long *bbox, int z, long long buffer);
|
||||
void douglas_peucker(drawvec &geom, int start, int n, double e, size_t kept, size_t retain, bool prevent_simplify_shared_nodes);
|
||||
bool is_shared_node(long long wx, long long wy, struct node const *shared_nodes_map, size_t nodepos, std::string const &shared_nodes_bloom);
|
||||
void restore_node_states(drawvec const &from, drawvec &to);
|
||||
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);
|
||||
drawvec reorder_lines(const drawvec &geom);
|
||||
drawvec fix_polygon(const drawvec &geom, bool use_winding, bool reverse_winding);
|
||||
|
||||
@@ -1233,6 +1233,71 @@ int vertexcmp(const void *void1, const void *void2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct mark_shared_nodes_arg {
|
||||
int geomfd;
|
||||
long long geomsize;
|
||||
unsigned *initial_x;
|
||||
unsigned *initial_y;
|
||||
node const *shared_nodes_map;
|
||||
size_t nodepos;
|
||||
std::string const *shared_nodes_bloom;
|
||||
};
|
||||
|
||||
// Go through the geometry that one reader serialized, marking each vertex
|
||||
// with whether it is one of the shared nodes. The features are read in
|
||||
// chunks and written back in place, since marking doesn't change their size.
|
||||
static void *run_mark_shared_nodes(void *v) {
|
||||
mark_shared_nodes_arg *a = (mark_shared_nodes_arg *) v;
|
||||
|
||||
std::string buf;
|
||||
buf.resize(16 * 1024 * 1024);
|
||||
long long pos = 0;
|
||||
|
||||
while (pos < a->geomsize) {
|
||||
long long want = std::min((long long) buf.size(), a->geomsize - pos);
|
||||
if (pread(a->geomfd, &buf[0], want, pos) != want) {
|
||||
fprintf(stderr, "pread(geom): %s\n", strerror(errno));
|
||||
exit(EXIT_READ);
|
||||
}
|
||||
|
||||
// Mark each feature that is entirely within this chunk
|
||||
long long used = 0;
|
||||
while (used < want) {
|
||||
// a feature length is at most 10 bytes of varint
|
||||
if (want - used < 10 && pos + want < a->geomsize) {
|
||||
break;
|
||||
}
|
||||
|
||||
const char *cp = buf.c_str() + used;
|
||||
long long len;
|
||||
deserialize_long_long(&cp, &len);
|
||||
long long flen = (cp - buf.c_str()) - used;
|
||||
|
||||
if (used + flen + len > want) {
|
||||
break;
|
||||
}
|
||||
|
||||
mark_shared_nodes(&buf[0] + used + flen, len, a->initial_x, a->initial_y, a->shared_nodes_map, a->nodepos, *a->shared_nodes_bloom);
|
||||
used += flen + len;
|
||||
}
|
||||
|
||||
if (used == 0) {
|
||||
// a single feature is bigger than the buffer
|
||||
buf.resize(buf.size() * 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pwrite(a->geomfd, buf.c_str(), used, pos) != used) {
|
||||
fprintf(stderr, "pwrite(geom): %s\n", strerror(errno));
|
||||
exit(EXIT_WRITE);
|
||||
}
|
||||
|
||||
pos += used;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
double round_droprate(double r) {
|
||||
return std::round(r * 100000.0) / 100000.0;
|
||||
}
|
||||
@@ -2167,6 +2232,41 @@ std::pair<int, metadata> read_input(std::vector<source> &sources, char *fname, i
|
||||
fclose(node_out);
|
||||
}
|
||||
|
||||
// Mark the vertices of each feature with whether they are shared nodes,
|
||||
// so that tiling doesn't need to look them up again for every tile.
|
||||
|
||||
if (nodepos > 0) {
|
||||
if (!quiet) {
|
||||
fprintf(stderr, "Marking nodes \r");
|
||||
}
|
||||
|
||||
std::vector<pthread_t> pthreads(CPUS);
|
||||
std::vector<mark_shared_nodes_arg> args(CPUS);
|
||||
|
||||
for (size_t i = 0; i < CPUS; i++) {
|
||||
args[i].geomfd = readers[i].geomfd;
|
||||
args[i].geomsize = readers[i].geompos;
|
||||
args[i].initial_x = initial_x.data();
|
||||
args[i].initial_y = initial_y.data();
|
||||
args[i].shared_nodes_map = shared_nodes_map;
|
||||
args[i].nodepos = nodepos;
|
||||
args[i].shared_nodes_bloom = &shared_nodes_bloom;
|
||||
|
||||
if (thread_create(&pthreads[i], NULL, run_mark_shared_nodes, &args[i]) != 0) {
|
||||
perror("pthread_create");
|
||||
exit(EXIT_PTHREAD);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < CPUS; i++) {
|
||||
void *retval;
|
||||
|
||||
if (pthread_join(pthreads[i], &retval) != 0) {
|
||||
perror("pthread_join mark nodes");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!quiet) {
|
||||
fprintf(stderr, "Merging index \r");
|
||||
}
|
||||
|
||||
+63
-1
@@ -157,7 +157,7 @@ void deserialize_byte(const char **f, signed char *n) {
|
||||
static void write_geometry(drawvec const &dv, std::string &out, long long wx, long long wy) {
|
||||
for (size_t i = 0; i < dv.size(); i++) {
|
||||
if (dv[i].op == VT_MOVETO || dv[i].op == VT_LINETO) {
|
||||
serialize_byte(out, dv[i].op);
|
||||
serialize_byte(out, dv[i].op | (dv[i].node << NODE_SHIFT));
|
||||
serialize_long_long(out, dv[i].x - wx);
|
||||
serialize_long_long(out, dv[i].y - wy);
|
||||
wx = dv[i].x;
|
||||
@@ -301,6 +301,68 @@ serial_feature deserialize_feature(std::string const &geoms, unsigned z, unsigne
|
||||
return sf;
|
||||
}
|
||||
|
||||
// Before tiling, mark each vertex of a serialized feature with whether
|
||||
// it is one of the global shared nodes. This is done in place, by setting
|
||||
// the node state in the upper bits of the byte that holds each vertex's operation,
|
||||
// so the feature's length and position don't change.
|
||||
void mark_shared_nodes(char *feature, size_t len, unsigned *initial_x, unsigned *initial_y, struct node const *shared_nodes_map, size_t nodepos, std::string const &shared_nodes_bloom) {
|
||||
const char *cp = feature;
|
||||
|
||||
signed char t;
|
||||
deserialize_byte(&cp, &t);
|
||||
if (t != VT_LINE && t != VT_POLYGON) {
|
||||
return;
|
||||
}
|
||||
|
||||
long long layer, seq;
|
||||
deserialize_long_long(&cp, &layer);
|
||||
deserialize_long_long(&cp, &seq);
|
||||
|
||||
if (layer & (1 << FLAG_MINZOOM)) {
|
||||
int minzoom;
|
||||
deserialize_int(&cp, &minzoom);
|
||||
}
|
||||
if (layer & (1 << FLAG_MAXZOOM)) {
|
||||
int maxzoom;
|
||||
deserialize_int(&cp, &maxzoom);
|
||||
}
|
||||
if (layer & (1 << FLAG_ID)) {
|
||||
unsigned long long id;
|
||||
deserialize_ulong_long(&cp, &id);
|
||||
}
|
||||
|
||||
int segment;
|
||||
deserialize_int(&cp, &segment);
|
||||
|
||||
// the same accumulation as in decode_geometry()
|
||||
long long wx = initial_x[segment], wy = initial_y[segment];
|
||||
|
||||
while (cp < feature + len) {
|
||||
char *opp = feature + (cp - feature);
|
||||
signed char op;
|
||||
deserialize_byte(&cp, &op);
|
||||
if (op == VT_END) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (op == VT_MOVETO || op == VT_LINETO) {
|
||||
long long dx, dy;
|
||||
|
||||
deserialize_long_long(&cp, &dx);
|
||||
deserialize_long_long(&cp, &dy);
|
||||
|
||||
wx += dx * (1 << geometry_scale);
|
||||
wy += dy * (1 << geometry_scale);
|
||||
|
||||
signed char node = is_shared_node(wx, wy, shared_nodes_map, nodepos, shared_nodes_bloom) ? NODE_SHARED : NODE_NOT_SHARED;
|
||||
*opp = op | (node << NODE_SHIFT);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Internal error: no end of geometry marking shared nodes\n");
|
||||
exit(EXIT_IMPOSSIBLE);
|
||||
}
|
||||
|
||||
static long long scale_geometry(struct serialization_state *sst, long long *bbox, drawvec &geom) {
|
||||
long long offset = 0;
|
||||
long long prev = 0;
|
||||
|
||||
@@ -321,6 +321,7 @@ struct node {
|
||||
};
|
||||
|
||||
int nodecmp(const void *void1, const void *void2);
|
||||
void mark_shared_nodes(char *feature, size_t len, unsigned *initial_x, unsigned *initial_y, struct node const *shared_nodes_map, size_t nodepos, std::string const &shared_nodes_bloom);
|
||||
|
||||
int serialize_feature(struct serialization_state *sst, serial_feature &sf, std::string const &layername);
|
||||
void coerce_value(std::string const &key, int &vt, std::string &val, std::unordered_map<std::string, int> const *attribute_types);
|
||||
|
||||
File diff suppressed because one or more lines are too long
+217
File diff suppressed because one or more lines are too long
@@ -476,7 +476,7 @@ static void rewrite(serial_feature const &osf, int z, int nextzoom, int maxzoom,
|
||||
|
||||
drawvec geom2;
|
||||
for (auto const &g : osf.geometry) {
|
||||
geom2.emplace_back(g.op, SHIFT_RIGHT(g.x + sx), SHIFT_RIGHT(g.y + sy));
|
||||
geom2.emplace_back(g.op, SHIFT_RIGHT(g.x + sx), SHIFT_RIGHT(g.y + sy), g.node);
|
||||
}
|
||||
|
||||
for (xo = bbox2[0]; xo <= bbox2[2]; xo++) {
|
||||
@@ -631,7 +631,9 @@ static double simplify_feature(serial_feature *p, drawvec const &shared_nodes, n
|
||||
// unioned exactly
|
||||
//
|
||||
// don't try to scale up because these are still world coordinates
|
||||
drawvec before = geom;
|
||||
coalesce_polygon(geom, false);
|
||||
restore_node_states(before, geom);
|
||||
}
|
||||
|
||||
// continues to simplify to line_detail even if we have extra detail
|
||||
@@ -979,13 +981,13 @@ static bool clip_to_tile(serial_feature &sf, int z, long long buffer) {
|
||||
|
||||
if (sf.bbox[0] <= (1LL << 32) * buffer / 256) {
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
sf.geometry.push_back(draw(sf.geometry[i].op, sf.geometry[i].x + (1LL << 32), sf.geometry[i].y));
|
||||
sf.geometry.push_back(draw(sf.geometry[i].op, sf.geometry[i].x + (1LL << 32), sf.geometry[i].y, sf.geometry[i].node));
|
||||
}
|
||||
}
|
||||
|
||||
if (sf.bbox[2] >= (1LL << 32) - ((1LL << 32) * buffer / 256)) {
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
sf.geometry.push_back(draw(sf.geometry[i].op, sf.geometry[i].x - (1LL << 32), sf.geometry[i].y));
|
||||
sf.geometry.push_back(draw(sf.geometry[i].op, sf.geometry[i].x - (1LL << 32), sf.geometry[i].y, sf.geometry[i].node));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2295,6 +2297,7 @@ long long write_tile(decompressor *geoms, std::atomic<long long> *geompos_in, ch
|
||||
|
||||
// don't scale up because this is still world coordinates
|
||||
coalesce_polygon(to_clean, false);
|
||||
restore_node_states(features[simplified_geometry_through]->geometry, to_clean);
|
||||
features[simplified_geometry_through]->geometry = std::move(to_clean);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
#ifndef VERSION_HPP
|
||||
#define VERSION_HPP
|
||||
|
||||
#define VERSION "v2.82.0"
|
||||
#define VERSION "v2.83.0"
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user