From c2d6760639c93332a20566dc20c3a7a8eb8dbac8 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Sat, 28 Oct 2023 11:41:21 -0700 Subject: [PATCH] Fix the check for continuous polygons --- geometry.cpp | 60 +++++++++++++++++++++++++++++++++------------------- geometry.hpp | 3 ++- serial.cpp | 3 +++ serial.hpp | 1 + tile.cpp | 33 +++++++++++++++++++++++------ 5 files changed, 70 insertions(+), 30 deletions(-) diff --git a/geometry.cpp b/geometry.cpp index 037707d65..fae975920 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -1427,7 +1427,7 @@ drawvec checkerboard_anchors(drawvec const &geom, int tx, int ty, int z, unsigne return out; } -bool is_continous_poly(drawvec const &geom, size_t i, size_t j, int z, int tx, int ty, struct node *shared_nodes_map, size_t nodepos) { +bool is_continuous_ring(drawvec const &geom, size_t i, size_t j, int z, int tx, int ty, struct node *shared_nodes_map, size_t nodepos) { size_t count = 0; // j - 1 to avoid double-counting the duplicate last node @@ -1435,6 +1435,7 @@ bool is_continous_poly(drawvec const &geom, size_t i, size_t j, int z, int tx, i if (is_shared_node(geom[i], z, tx, ty, shared_nodes_map, nodepos)) { count++; + // every ring will have three shared nodes; the fourth indicates a shared vertex if (count >= 4) { return true; } @@ -1444,7 +1445,26 @@ bool is_continous_poly(drawvec const &geom, size_t i, size_t j, int z, int tx, i return false; } -drawvec buffer_poly(drawvec const &geom, double buffer, int z, int tx, int ty, struct node *shared_nodes_map, size_t nodepos) { +bool is_continuous_poly(drawvec const &geom, int z, int tx, int ty, struct node *shared_nodes_map, size_t nodepos) { + 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 (is_continuous_ring(geom, i, j, z, tx, ty, shared_nodes_map, nodepos)) { + return true; + } + } + } + + return false; +} + +drawvec buffer_poly(drawvec const &geom, double buffer) { drawvec out = geom; if (buffer != 0) { @@ -1457,32 +1477,28 @@ drawvec buffer_poly(drawvec const &geom, double buffer, int z, int tx, int ty, s } } - if (!is_continous_poly(geom, i, j, z, tx, ty, shared_nodes_map, nodepos)) { - for (size_t k = i; k < j - 1; k++) { - draw p0 = geom[(k + 0 - i) % (j - i - 1) + i]; - draw p1 = geom[(k + 1 - i) % (j - i - 1) + i]; - draw p2 = geom[(k + 2 - i) % (j - i - 1) + i]; - - double a10 = atan2(p1.y - p0.y, p1.x - p0.x); - double a21 = atan2(p2.y - p1.y, p2.x - p1.x); + for (size_t k = i; k < j - 1; k++) { + draw p0 = geom[(k + 0 - i) % (j - i - 1) + i]; + draw p1 = geom[(k + 1 - i) % (j - i - 1) + i]; + draw p2 = geom[(k + 2 - i) % (j - i - 1) + i]; - double dx = cos(a10 - 90 * M_PI / 180) + cos(a21 - 90 * M_PI / 180); - double dy = sin(a10 - 90 * M_PI / 180) + sin(a21 - 90 * M_PI / 180); + double a10 = atan2(p1.y - p0.y, p1.x - p0.x); + double a21 = atan2(p2.y - p1.y, p2.x - p1.x); - // the angle halfway between the angles - // perpendicular to a0->a1 (a10) and a1->a2 (a21) - double a2 = atan2(dy, dx); + double dx = cos(a10 - 90 * M_PI / 180) + cos(a21 - 90 * M_PI / 180); + double dy = sin(a10 - 90 * M_PI / 180) + sin(a21 - 90 * M_PI / 180); - out[(k + 1 - i) % (j - i - 1) + i].x = std::round(p1.x + buffer * cos(a2)); - out[(k + 1 - i) % (j - i - 1) + i].y = std::round(p1.y + buffer * sin(a2)); - } + // the angle halfway between the angles + // perpendicular to a0->a1 (a10) and a1->a2 (a21) + double a2 = atan2(dy, dx); - out[j - 1].x = out[i].x; - out[j - 1].y = out[i].y; - } else { - printf("skipping continuous ring %zu to %zu\n", i, j); + out[(k + 1 - i) % (j - i - 1) + i].x = std::round(p1.x + buffer * cos(a2)); + out[(k + 1 - i) % (j - i - 1) + i].y = std::round(p1.y + buffer * sin(a2)); } + out[j - 1].x = out[i].x; + out[j - 1].y = out[i].y; + i = j - 1; } } diff --git a/geometry.hpp b/geometry.hpp index 53bc46094..001912e10 100644 --- a/geometry.hpp +++ b/geometry.hpp @@ -104,6 +104,7 @@ std::string overzoom(mvt_tile tile, int oz, int ox, int oy, int nz, int nx, int std::string overzoom(std::string s, int oz, int ox, int oy, int nz, int nx, int ny, int detail, int buffer, std::set const &keep, bool do_compress); -drawvec buffer_poly(drawvec const &geom, double buffer, int z, int tx, int ty, struct node *shared_nodes_map, size_t nodepos); +drawvec buffer_poly(drawvec const &geom, double buffer); +bool is_continuous_poly(drawvec const &geom, int z, int tx, int ty, struct node *shared_nodes_map, size_t nodepos); #endif diff --git a/serial.cpp b/serial.cpp index a40cc462d..24ae3a826 100644 --- a/serial.cpp +++ b/serial.cpp @@ -221,6 +221,7 @@ std::string serialize_feature(serial_feature *sf, long long wx, long long wy) { serialize_int(s, sf->segment); write_geometry(sf->geometry, s, wx, wy); + serialize_byte(s, sf->continuous); if (sf->index != 0) { serialize_ulong_long(s, sf->index); @@ -276,8 +277,10 @@ serial_feature deserialize_feature(std::string &geoms, unsigned z, unsigned tx, sf.index = 0; sf.label_point = 0; sf.extent = 0; + sf.continuous = -1; // unknown sf.geometry = decode_geometry(&cp, z, tx, ty, sf.bbox, initial_x[sf.segment], initial_y[sf.segment]); + deserialize_byte(&cp, &sf.continuous); if (sf.layer & (1 << FLAG_INDEX)) { deserialize_ulong_long(&cp, &sf.index); diff --git a/serial.hpp b/serial.hpp index a465bfd67..81d9da1ac 100644 --- a/serial.hpp +++ b/serial.hpp @@ -43,6 +43,7 @@ struct serial_feature { signed char t = 0; signed char feature_minzoom = 0; + signed char continuous = -1; // is this a continuous polygon? bool has_id = false; unsigned long long id = 0; diff --git a/tile.cpp b/tile.cpp index f42dc3059..23ea6a197 100644 --- a/tile.cpp +++ b/tile.cpp @@ -331,7 +331,7 @@ struct ordercmp { } } ordercmp; -void rewrite(drawvec &geom, int z, int nextzoom, int maxzoom, long long *bbox, unsigned tx, unsigned ty, int buffer, int *within, std::atomic *geompos, compressor **geomfile, const char *fname, signed char t, int layer, signed char feature_minzoom, int child_shards, int max_zoom_increment, long long seq, int tippecanoe_minzoom, int tippecanoe_maxzoom, int segment, unsigned *initial_x, unsigned *initial_y, std::vector &metakeys, std::vector &metavals, bool has_id, unsigned long long id, unsigned long long index, unsigned long long label_point, long long extent) { +void rewrite(drawvec &geom, int z, int nextzoom, int maxzoom, long long *bbox, unsigned tx, unsigned ty, int buffer, int *within, std::atomic *geompos, compressor **geomfile, const char *fname, signed char t, int layer, signed char feature_minzoom, int child_shards, int max_zoom_increment, long long seq, int tippecanoe_minzoom, int tippecanoe_maxzoom, int segment, unsigned *initial_x, unsigned *initial_y, std::vector &metakeys, std::vector &metavals, bool has_id, unsigned long long id, unsigned long long index, unsigned long long label_point, long long extent, signed char continuous) { if (geom.size() > 0 && (nextzoom <= maxzoom || additional[A_EXTEND_ZOOMS] || extend_zooms_max > 0)) { int xo, yo; int span = 1 << (nextzoom - z); @@ -424,6 +424,7 @@ void rewrite(drawvec &geom, int z, int nextzoom, int maxzoom, long long *bbox, u sf.label_point = label_point; sf.extent = extent; sf.feature_minzoom = feature_minzoom; + sf.continuous = continuous; for (size_t i = 0; i < metakeys.size(); i++) { sf.keys.push_back(metakeys[i]); @@ -474,6 +475,7 @@ struct partial { long long clustered = 0; std::set need_tilestats; std::map attribute_accum_state; + int continuous; }; struct partial_arg { @@ -613,8 +615,8 @@ void *partial_feature_worker(void *v) { drawvec geom = (*partials)[i].geoms[0]; - if (additional[A_BUFFER_POLYGONS_OUTWARD] && t == VT_POLYGON) { - geom = buffer_poly(geom, (1LL << (32 - z - out_detail)) * sqrt(2), z, (*partials)[i].tx, (*partials)[i].ty, a->shared_nodes_map, a->nodepos); + if (additional[A_BUFFER_POLYGONS_OUTWARD] && t == VT_POLYGON && !((*partials)[i].continuous)) { + geom = buffer_poly(geom, (1LL << (32 - z - out_detail)) * sqrt(2)); } to_tile_scale(geom, z, out_detail); @@ -1461,7 +1463,7 @@ void remove_attributes(serial_feature &sf, std::set const &exclude_ } } -serial_feature next_feature(decompressor *geoms, std::atomic *geompos_in, int z, unsigned tx, unsigned ty, unsigned *initial_x, unsigned *initial_y, long long *original_features, long long *unclipped_features, int nextzoom, int maxzoom, int minzoom, int max_zoom_increment, size_t pass, std::atomic *along, long long alongminus, int buffer, int *within, compressor **geomfile, std::atomic *geompos, std::atomic *oprogress, double todo, const char *fname, int child_shards, struct json_object *filter, const char *stringpool, long long *pool_off, std::vector> *layer_unmaps, bool first_time, bool compressed) { +serial_feature next_feature(decompressor *geoms, std::atomic *geompos_in, int z, unsigned tx, unsigned ty, unsigned *initial_x, unsigned *initial_y, long long *original_features, long long *unclipped_features, int nextzoom, int maxzoom, int minzoom, int max_zoom_increment, size_t pass, std::atomic *along, long long alongminus, int buffer, int *within, compressor **geomfile, std::atomic *geompos, std::atomic *oprogress, double todo, const char *fname, int child_shards, struct json_object *filter, const char *stringpool, long long *pool_off, std::vector> *layer_unmaps, bool first_time, bool compressed, struct node *shared_nodes_map, size_t nodepos) { while (1) { serial_feature sf; std::string s; @@ -1512,9 +1514,21 @@ serial_feature next_feature(decompressor *geoms, std::atomic *geompos (*unclipped_features)++; } + if (sf.t == VT_POLYGON && sf.continuous < 0) { + // figure out whether the polygon is continuous the first time it is examined, + // since we don't have access to all of the shared nodes to determine this + // when the feature is first being ingested + + if (prevent[P_SIMPLIFY_SHARED_NODES]) { + sf.continuous = is_continuous_poly(sf.geometry, z, tx, ty, shared_nodes_map, nodepos); + } else { + sf.continuous = false; + } + } + if (first_time && pass == 0) { /* only write out the next zoom once, even if we retry */ if (sf.tippecanoe_maxzoom == -1 || sf.tippecanoe_maxzoom >= nextzoom) { - rewrite(sf.geometry, z, nextzoom, maxzoom, sf.bbox, tx, ty, buffer, within, geompos, geomfile, fname, sf.t, sf.layer, sf.feature_minzoom, child_shards, max_zoom_increment, sf.seq, sf.tippecanoe_minzoom, sf.tippecanoe_maxzoom, sf.segment, initial_x, initial_y, sf.keys, sf.values, sf.has_id, sf.id, sf.index, sf.label_point, sf.extent); + rewrite(sf.geometry, z, nextzoom, maxzoom, sf.bbox, tx, ty, buffer, within, geompos, geomfile, fname, sf.t, sf.layer, sf.feature_minzoom, child_shards, max_zoom_increment, sf.seq, sf.tippecanoe_minzoom, sf.tippecanoe_maxzoom, sf.segment, initial_x, initial_y, sf.keys, sf.values, sf.has_id, sf.id, sf.index, sf.label_point, sf.extent, sf.continuous); } } @@ -1646,6 +1660,8 @@ struct run_prefilter_args { struct json_object *filter = NULL; bool first_time = false; bool compressed = false; + struct node *shared_nodes_map; + size_t nodepos; }; void *run_prefilter(void *v) { @@ -1653,7 +1669,7 @@ void *run_prefilter(void *v) { json_writer state(rpa->prefilter_fp); while (1) { - serial_feature sf = next_feature(rpa->geoms, rpa->geompos_in, rpa->z, rpa->tx, rpa->ty, rpa->initial_x, rpa->initial_y, rpa->original_features, rpa->unclipped_features, rpa->nextzoom, rpa->maxzoom, rpa->minzoom, rpa->max_zoom_increment, rpa->pass, rpa->along, rpa->alongminus, rpa->buffer, rpa->within, rpa->geomfile, rpa->geompos, rpa->oprogress, rpa->todo, rpa->fname, rpa->child_shards, rpa->filter, rpa->stringpool, rpa->pool_off, rpa->layer_unmaps, rpa->first_time, rpa->compressed); + serial_feature sf = next_feature(rpa->geoms, rpa->geompos_in, rpa->z, rpa->tx, rpa->ty, rpa->initial_x, rpa->initial_y, rpa->original_features, rpa->unclipped_features, rpa->nextzoom, rpa->maxzoom, rpa->minzoom, rpa->max_zoom_increment, rpa->pass, rpa->along, rpa->alongminus, rpa->buffer, rpa->within, rpa->geomfile, rpa->geompos, rpa->oprogress, rpa->todo, rpa->fname, rpa->child_shards, rpa->filter, rpa->stringpool, rpa->pool_off, rpa->layer_unmaps, rpa->first_time, rpa->compressed, rpa->shared_nodes_map, rpa->nodepos); if (sf.t < 0) { break; } @@ -2047,6 +2063,8 @@ long long write_tile(decompressor *geoms, std::atomic *geompos_in, ch rpa.filter = filter; rpa.first_time = first_time; rpa.compressed = compressed_input; + rpa.shared_nodes_map = shared_nodes_map; + rpa.nodepos = nodepos; if (pthread_create(&prefilter_writer, NULL, run_prefilter, &rpa) != 0) { perror("pthread_create (prefilter writer)"); @@ -2066,7 +2084,7 @@ long long write_tile(decompressor *geoms, std::atomic *geompos_in, ch ssize_t which_partial = -1; if (prefilter == NULL) { - sf = next_feature(geoms, geompos_in, z, tx, ty, initial_x, initial_y, &original_features, &unclipped_features, nextzoom, maxzoom, minzoom, max_zoom_increment, pass, along, alongminus, buffer, within, geomfile, geompos, &oprogress, todo, fname, child_shards, filter, stringpool, pool_off, layer_unmaps, first_time, compressed_input); + sf = next_feature(geoms, geompos_in, z, tx, ty, initial_x, initial_y, &original_features, &unclipped_features, nextzoom, maxzoom, minzoom, max_zoom_increment, pass, along, alongminus, buffer, within, geomfile, geompos, &oprogress, todo, fname, child_shards, filter, stringpool, pool_off, layer_unmaps, first_time, compressed_input, shared_nodes_map, nodepos); } else { sf = parse_feature(prefilter_jp, z, tx, ty, layermaps, tiling_seg, layer_unmaps, postfilter != NULL); } @@ -2271,6 +2289,7 @@ long long write_tile(decompressor *geoms, std::atomic *geompos_in, ch p.renamed = -1; p.extent = sf.extent; p.clustered = 0; + p.continuous = sf.continuous; if (line_detail == detail && extra_detail >= 0 && z == maxzoom) { p.extra_detail = extra_detail;