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:
Claude
2026-09-23 17:39:48 +00:00
parent 4f2621186a
commit d3a98170bf
11 changed files with 750 additions and 52 deletions
+69 -19
View File
@@ -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;
}
}
}