diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b4a2d2..c876a59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.6.2 + +* Make sure line simplification matches on opposite sides of a tile boundary + ## 1.6.1 * Use multiple threads for line simplification and polygon cleaning diff --git a/geometry.cc b/geometry.cc index 1b71b97..5fcd759 100644 --- a/geometry.cc +++ b/geometry.cc @@ -750,8 +750,46 @@ static void douglas_peucker(drawvec &geom, int start, int n, double e) { } } +// 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(drawvec &geom, long long extent) { + drawvec out; + + for (unsigned 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)) { + double x1 = geom[i - 1].x; + double y1 = geom[i - 1].y; + + double x2 = geom[i - 0].x; + double 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.push_back(draw(VT_LINETO, x1, y1)); + out[out.size() - 1].necessary = 1; + } + if (x2 != geom[i - 0].x || y2 != geom[i - 0].y) { + out.push_back(draw(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 detail) { int res = 1 << (32 - detail - z); + long long area = 0xFFFFFFFF; + if (z != 0) { + area = 1LL << (32 - z); + } unsigned i; for (i = 0; i < geom.size(); i++) { @@ -764,6 +802,8 @@ drawvec simplify_lines(drawvec &geom, int z, int detail) { } } + geom = impose_tile_boundaries(geom, area); + for (i = 0; i < geom.size(); i++) { if (geom[i].op == VT_MOVETO) { unsigned j; diff --git a/tile.cc b/tile.cc index 88312ba..670435a 100644 --- a/tile.cc +++ b/tile.cc @@ -493,7 +493,7 @@ void *partial_feature_worker(void *v) { for (unsigned i = a->task; i < (*partials).size(); i += a->tasks) { drawvec geom = (*partials)[i].geom; - (*partials)[i].geom.clear(); // avoid keeping two copies in memory + (*partials)[i].geom.clear(); // avoid keeping two copies in memory signed char t = (*partials)[i].t; int z = (*partials)[i].z; int line_detail = (*partials)[i].line_detail; @@ -501,7 +501,7 @@ void *partial_feature_worker(void *v) { char *additional = (*partials)[i].additional; if ((t == VT_LINE || t == VT_POLYGON) && !prevent['s' & 0xFF]) { - if (1 /* !reduced */) { // XXX why did this not simplify if reduced? + if (1 /* !reduced */) { // XXX why did this not simplify if reduced? if (t == VT_LINE) { geom = remove_noop(geom, t, 32 - z - line_detail); } @@ -845,7 +845,7 @@ long long write_tile(char **geoms, char *metabase, char *stringpool, int z, unsi // This is serial because decode_meta() unifies duplicates for (unsigned i = 0; i < partials.size(); i++) { drawvec geom = partials[i].geom; - partials[i].geom.clear(); // avoid keeping two copies in memory + partials[i].geom.clear(); // avoid keeping two copies in memory long long layer = partials[i].layer; char *meta = partials[i].meta; signed char t = partials[i].t; diff --git a/version.h b/version.h index 0a102b7..b4252d0 100644 --- a/version.h +++ b/version.h @@ -1 +1 @@ -#define VERSION "tippecanoe v1.6.1\n" +#define VERSION "tippecanoe v1.6.2\n"