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
+63 -1
View File
@@ -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;