From 1e2813ed65cdd932097550eb7ed60f9476277926 Mon Sep 17 00:00:00 2001 From: Erica Fischer Date: Thu, 7 Dec 2023 11:25:06 -0800 Subject: [PATCH] Generalize overzooming functions to multiple tiles --- CHANGELOG.md | 4 + README.md | 7 +- clip.cpp | 254 +++++++++++++++++++++++++++-------------------- geometry.hpp | 18 +++- man/tippecanoe.1 | 11 +- mvt.cpp | 2 +- mvt.hpp | 2 +- overzoom.cpp | 108 ++++++++++++-------- tile-join.cpp | 11 +- version.hpp | 2 +- 10 files changed, 263 insertions(+), 156 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 130e3e94..e194c65b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.38.0 + +* `tippecanoe-overzoom` can now merge multiple tiles when overzooming them + # 2.37.1 * Reduce maximum memory used for vertex sorting diff --git a/README.md b/README.md index 6a1c8557..6d746f4b 100644 --- a/README.md +++ b/README.md @@ -984,10 +984,15 @@ to turn those into moderate detail tiles at high zoom levels, for the benefit of renderers that cannot internally overzoom high-resolution tiles without losing some of the precision. Running: - tippecanoe-overzoom -o out.mvt.gz inz/inx/iny outz/outx/outy in.mvt.gz + tippecanoe-overzoom -o out.mvt.gz in.mvt.gz inz/inx/iny outz/outx/outy reads tile `inz/inx/iny` of `in.mvt.gz` and produces tile `outz/outx/outy` of `out.mvt.gz`. + tippecanoe-overzoom -o out.mvt.gz -t outz/outx/outy in.mvt.gz inz/inx/iny in2.mvt.gz in2z/in2x/in2y in3.mvt.gz in3z/in3x/in3y + +reads tile `inz/inx/iny` of `in.mvt.gz`, tile `in2z/in2x/in2y` of `in2.mvt.gz`, and tile `in3z/in3x/in3y` of `in3.mvt.gz`, +and produces tile `outz/outx/outy` of `out.mvt.gz` from them. + ### Options * `-b` *buffer*: Set the tile buffer in the output tile (default 5) diff --git a/clip.cpp b/clip.cpp index 512ab2fd..31a3898c 100644 --- a/clip.cpp +++ b/clip.cpp @@ -752,143 +752,172 @@ static std::vector> clip_poly1(std::vector tiles, int nz, int nx, int ny, int detail, int buffer, std::set const &keep, bool do_compress, std::vector> *next_overzoomed_tiles) { - mvt_tile tile; + std::vector decoded; - try { - bool was_compressed; - if (!tile.decode(s, was_compressed)) { - fprintf(stderr, "Couldn't parse tile %d/%u/%u\n", oz, ox, oy); - exit(EXIT_MVT); + for (auto const &t : tiles) { + mvt_tile tile; + + try { + bool was_compressed; + if (!tile.decode(t.tile, was_compressed)) { + fprintf(stderr, "Couldn't parse tile %d/%u/%u\n", t.z, t.x, t.y); + exit(EXIT_MVT); + } + } catch (std::exception const &e) { + fprintf(stderr, "PBF decoding error in tile %d/%u/%u\n", t.z, t.x, t.y); + exit(EXIT_PROTOBUF); } - } catch (std::exception const &e) { - fprintf(stderr, "PBF decoding error in tile %d/%u/%u\n", oz, ox, oy); - exit(EXIT_PROTOBUF); + + source_tile out; + out.tile = tile; + out.z = t.z; + out.x = t.x; + out.y = t.y; + + decoded.push_back(out); } - return overzoom(tile, oz, ox, oy, nz, nx, ny, detail, buffer, keep, do_compress, next_overzoomed_tiles); + return overzoom(decoded, nz, nx, ny, detail, buffer, keep, do_compress, next_overzoomed_tiles); } -std::string overzoom(mvt_tile tile, int oz, int ox, int oy, int nz, int nx, int ny, +std::string overzoom(std::vector tiles, int nz, int nx, int ny, int detail, int buffer, std::set const &keep, bool do_compress, std::vector> *next_overzoomed_tiles) { mvt_tile outtile; - for (auto const &layer : tile.layers) { - mvt_layer outlayer = mvt_layer(); + for (auto const &tile : tiles) { + for (auto const &layer : tile.tile.layers) { + mvt_layer *outlayer = NULL; - int det = detail; - if (det <= 0) { - det = std::round(log(layer.extent) / log(2)); - } + int det = detail; + if (det <= 0) { + det = std::round(log(layer.extent) / log(2)); + } - outlayer.name = layer.name; - outlayer.version = layer.version; - outlayer.extent = 1LL << det; - - for (auto const &feature : layer.features) { - mvt_feature outfeature; - drawvec geom; - int t = feature.type; - - // Convert feature geometry to world coordinates - - long long tilesize = 1LL << (32 - oz); // source tile size in world coordinates - draw ring_closure(0, 0, 0); - - for (auto const &g : feature.geometry) { - if (g.op == mvt_closepath) { - geom.push_back(ring_closure); - } else { - geom.emplace_back(g.op, - g.x * tilesize / layer.extent + ox * tilesize, - g.y * tilesize / layer.extent + oy * tilesize); - - if (g.op == mvt_moveto) { - ring_closure = geom.back(); - ring_closure.op = mvt_lineto; - } + for (size_t i = 0; i < outtile.layers.size(); i++) { + if (outtile.layers[i].name == layer.name) { + outlayer = &outtile.layers[i]; } } - // Now offset from world coordinates to output tile coordinates, - // but retain world scale, because that is what tippecanoe clipping expects + if (outlayer == NULL) { + mvt_layer newlayer = mvt_layer(); - long long outtilesize = 1LL << (32 - nz); // destination tile size in world coordinates - for (auto &g : geom) { - g.x -= nx * outtilesize; - g.y -= ny * outtilesize; + newlayer.name = layer.name; + newlayer.version = layer.version; + newlayer.extent = 1LL << det; + + outtile.layers.push_back(newlayer); + outlayer = &outtile.layers.back(); } - // Clip to output tile + for (auto const &feature : layer.features) { + mvt_feature outfeature; + drawvec geom; + int t = feature.type; - long long xmin = LLONG_MAX; - long long ymin = LLONG_MAX; - long long xmax = LLONG_MIN; - long long ymax = LLONG_MIN; + // Convert feature geometry to world coordinates - for (auto const &g : geom) { - xmin = std::min(xmin, g.x); - ymin = std::min(ymin, g.y); - xmax = std::max(xmax, g.x); - ymax = std::max(ymax, g.y); - } + long long tilesize = 1LL << (32 - tile.z); // source tile size in world coordinates + draw ring_closure(0, 0, 0); - long long b = outtilesize * buffer / 256; - if (xmax < -b || ymax < -b || xmin > outtilesize + b || ymin > outtilesize + b) { - continue; - } + for (auto const &g : feature.geometry) { + if (g.op == mvt_closepath) { + geom.push_back(ring_closure); + } else { + geom.emplace_back(g.op, + g.x * tilesize / layer.extent + tile.x * tilesize, + g.y * tilesize / layer.extent + tile.y * tilesize); - if (t == VT_LINE) { - geom = clip_lines(geom, nz, buffer); - } else if (t == VT_POLYGON) { - drawvec dv; - geom = simple_clip_poly(geom, nz, buffer, dv, false); - } else if (t == VT_POINT) { - geom = clip_point(geom, nz, buffer); - } - - // Scale to output tile extent - - to_tile_scale(geom, nz, det); - - // Clean geometries - - geom = remove_noop(geom, t, 0); - if (t == VT_POLYGON) { - geom = clean_or_clip_poly(geom, 0, 0, false, false); - geom = close_poly(geom); - } - - // Add geometry to output feature - - outfeature.type = t; - for (auto const &g : geom) { - outfeature.geometry.emplace_back(g.op, g.x, g.y); - } - - // ID and attributes, if it didn't get clipped away - - if (outfeature.geometry.size() > 0) { - if (feature.has_id) { - outfeature.has_id = true; - outfeature.id = feature.id; - } - - for (size_t i = 0; i + 1 < feature.tags.size(); i += 2) { - if (keep.size() == 0 || keep.find(layer.keys[feature.tags[i]]) != keep.end()) { - outlayer.tag(outfeature, layer.keys[feature.tags[i]], layer.values[feature.tags[i + 1]]); + if (g.op == mvt_moveto) { + ring_closure = geom.back(); + ring_closure.op = mvt_lineto; + } } } - outlayer.features.push_back(outfeature); + // Now offset from world coordinates to output tile coordinates, + // but retain world scale, because that is what tippecanoe clipping expects + + long long outtilesize = 1LL << (32 - nz); // destination tile size in world coordinates + for (auto &g : geom) { + g.x -= nx * outtilesize; + g.y -= ny * outtilesize; + } + + // Clip to output tile + + long long xmin = LLONG_MAX; + long long ymin = LLONG_MAX; + long long xmax = LLONG_MIN; + long long ymax = LLONG_MIN; + + for (auto const &g : geom) { + xmin = std::min(xmin, g.x); + ymin = std::min(ymin, g.y); + xmax = std::max(xmax, g.x); + ymax = std::max(ymax, g.y); + } + + long long b = outtilesize * buffer / 256; + if (xmax < -b || ymax < -b || xmin > outtilesize + b || ymin > outtilesize + b) { + continue; + } + + if (t == VT_LINE) { + geom = clip_lines(geom, nz, buffer); + } else if (t == VT_POLYGON) { + drawvec dv; + geom = simple_clip_poly(geom, nz, buffer, dv, false); + } else if (t == VT_POINT) { + geom = clip_point(geom, nz, buffer); + } + + // Scale to output tile extent + + to_tile_scale(geom, nz, det); + + // Clean geometries + + geom = remove_noop(geom, t, 0); + if (t == VT_POLYGON) { + geom = clean_or_clip_poly(geom, 0, 0, false, false); + geom = close_poly(geom); + } + + // Add geometry to output feature + + outfeature.type = t; + for (auto const &g : geom) { + outfeature.geometry.emplace_back(g.op, g.x, g.y); + } + + // ID and attributes, if it didn't get clipped away + + if (outfeature.geometry.size() > 0) { + if (feature.has_id) { + outfeature.has_id = true; + outfeature.id = feature.id; + } + + for (size_t i = 0; i + 1 < feature.tags.size(); i += 2) { + if (keep.size() == 0 || keep.find(layer.keys[feature.tags[i]]) != keep.end()) { + outlayer->tag(outfeature, layer.keys[feature.tags[i]], layer.values[feature.tags[i + 1]]); + } + } + + outlayer->features.push_back(outfeature); + } } } + } - if (outlayer.features.size() > 0) { - outtile.layers.push_back(outlayer); + for (ssize_t i = outtile.layers.size() - 1; i >= 0; i--) { + if (outtile.layers[i].features.size() == 0) { + outtile.layers.erase(outtile.layers.begin() + i); } } @@ -903,7 +932,16 @@ std::string overzoom(mvt_tile tile, int oz, int ox, int oy, int nz, int nx, int if (outtile.layers.size() > 0) { for (size_t x = 0; x < 2; x++) { for (size_t y = 0; y < 2; y++) { - std::string child = overzoom(outtile, nz, nx, ny, + source_tile st; + st.tile = outtile; + st.z = nz; + st.x = nx; + st.y = ny; + + std::vector sts; + sts.push_back(st); + + std::string child = overzoom(sts, nz + 1, nx * 2 + x, ny * 2 + y, detail, buffer, keep, false, NULL); if (child.size() > 0) { diff --git a/geometry.hpp b/geometry.hpp index 1cbaa8ef..47ddb000 100644 --- a/geometry.hpp +++ b/geometry.hpp @@ -98,11 +98,25 @@ void visvalingam(drawvec &ls, size_t start, size_t end, double threshold, size_t int pnpoly(const drawvec &vert, size_t start, size_t nvert, long long testx, long long testy); double distance_from_line(long long point_x, long long point_y, long long segA_x, long long segA_y, long long segB_x, long long segB_y); -std::string overzoom(mvt_tile tile, int oz, int ox, int oy, int nz, int nx, int ny, +struct input_tile { + std::string tile; + int z; + int x; + int y; +}; + +struct source_tile { + mvt_tile tile; + int z; + int x; + int y; +}; + +std::string overzoom(std::vector tiles, int nz, int nx, int ny, int detail, int buffer, std::set const &keep, bool do_compress, std::vector> *next_overzoomed_tiles); -std::string overzoom(std::string s, int oz, int ox, int oy, int nz, int nx, int ny, +std::string overzoom(std::vector tiles, int nz, int nx, int ny, int detail, int buffer, std::set const &keep, bool do_compress, std::vector> *next_overzoomed_tiles); diff --git a/man/tippecanoe.1 b/man/tippecanoe.1 index a1abaaf2..906e5c62 100644 --- a/man/tippecanoe.1 +++ b/man/tippecanoe.1 @@ -1247,11 +1247,20 @@ some of the precision. Running: .PP .RS .nf -tippecanoe\-overzoom \-o out.mvt.gz inz/inx/iny outz/outx/outy in.mvt.gz +tippecanoe\-overzoom \-o out.mvt.gz in.mvt.gz inz/inx/iny outz/outx/outy .fi .RE .PP reads tile \fB\fCinz/inx/iny\fR of \fB\fCin.mvt.gz\fR and produces tile \fB\fCoutz/outx/outy\fR of \fB\fCout.mvt.gz\fR\&. +.PP +.RS +.nf +tippecanoe\-overzoom \-o out.mvt.gz \-t outz/outx/outy in.mvt.gz inz/inx/iny in2.mvt.gz in2z/in2x/in2y in3.mvt.gz in3z/in3x/in3y +.fi +.RE +.PP +reads tile \fB\fCinz/inx/iny\fR of \fB\fCin.mvt.gz\fR, tile \fB\fCin2z/in2x/in2y\fR of \fB\fCin2.mvt.gz\fR, and tile \fB\fCin3z/in3x/in3y\fR of \fB\fCin3.mvt.gz\fR, +and produces tile \fB\fCoutz/outx/outy\fR of \fB\fCout.mvt.gz\fR from them. .SS Options .RS .IP \(bu 2 diff --git a/mvt.cpp b/mvt.cpp index 4c21716e..b882fc43 100644 --- a/mvt.cpp +++ b/mvt.cpp @@ -109,7 +109,7 @@ int compress(std::string const &input, std::string &output, bool gz) { return 0; } -bool mvt_tile::decode(std::string &message, bool &was_compressed) { +bool mvt_tile::decode(const std::string &message, bool &was_compressed) { layers.clear(); std::string src; diff --git a/mvt.hpp b/mvt.hpp index 696e2535..ae4cda76 100644 --- a/mvt.hpp +++ b/mvt.hpp @@ -110,7 +110,7 @@ struct mvt_tile { std::vector layers{}; std::string encode(); - bool decode(std::string &message, bool &was_compressed); + bool decode(const std::string &message, bool &was_compressed); }; bool is_compressed(std::string const &data); diff --git a/overzoom.cpp b/overzoom.cpp index 1a630413..d365cca3 100644 --- a/overzoom.cpp +++ b/overzoom.cpp @@ -18,12 +18,24 @@ std::set keep; void usage(char **argv) { fprintf(stderr, "Usage: %s -o newtile.pbf.gz tile.pbf.gz oz/ox/oy nz/nx/ny\n", argv[0]); fprintf(stderr, "to create tile nz/nx/ny from tile oz/ox/oy\n"); + fprintf(stderr, "Usage: %s -o newtile.pbf.gz -t nz/nx/ny tile.pbf.gz oz/ox/oy tile2.pbf.gz oz2/ox2/oy2\n", argv[0]); + fprintf(stderr, "to create tile nz/nx/ny from tiles oz/ox/oy and oz2/ox2/oy2\n"); exit(EXIT_FAILURE); } +struct source { + std::string file; + int z; + int x; + int y; +}; + int main(int argc, char **argv) { int i; const char *outfile = NULL; + const char *outtile = NULL; + + std::vector sources; while ((i = getopt(argc, argv, "y:o:d:b:")) != -1) { switch (i) { @@ -43,57 +55,73 @@ int main(int argc, char **argv) { buffer = atoi(optarg); break; + case 't': + outtile = optarg; + break; + default: usage(argv); } } - if (argc - optind != 3) { - usage(argv); - } - if (outfile == NULL) { usage(argv); } - const char *infile = argv[optind + 0]; + if (outtile == NULL) { + if (argc - optind != 3) { + usage(argv); + } - int oz, ox, oy; - if (sscanf(argv[optind + 1], "%d/%d/%d", &oz, &ox, &oy) != 3) { - fprintf(stderr, "%s: not in z/x/y form\n", argv[optind + 1]); - usage(argv); + const char *infile = argv[optind + 0]; + + int oz, ox, oy; + if (sscanf(argv[optind + 1], "%d/%d/%d", &oz, &ox, &oy) != 3) { + fprintf(stderr, "%s: not in z/x/y form\n", argv[optind + 1]); + usage(argv); + } + + int nz, nx, ny; + if (sscanf(argv[optind + 2], "%d/%d/%d", &nz, &nx, &ny) != 3) { + fprintf(stderr, "%s: not in z/x/y form\n", argv[optind + 2]); + usage(argv); + } + + std::string tile; + char buf[1000]; + int len; + + FILE *f = fopen(infile, "rb"); + if (f == NULL) { + perror(infile); + exit(EXIT_FAILURE); + } + + while ((len = fread(buf, sizeof(char), 1000, f)) > 0) { + tile.append(std::string(buf, len)); + } + fclose(f); + + f = fopen(outfile, "wb"); + if (f == NULL) { + perror(outfile); + exit(EXIT_FAILURE); + } + + input_tile it; + it.tile = tile; + it.z = oz; + it.x = ox; + it.y = oy; + + std::vector its; + its.push_back(it); + + std::string out = overzoom(its, nz, nx, ny, detail, buffer, keep, true, NULL); + fwrite(out.c_str(), sizeof(char), out.size(), f); + fclose(f); + } else { } - int nz, nx, ny; - if (sscanf(argv[optind + 2], "%d/%d/%d", &nz, &nx, &ny) != 3) { - fprintf(stderr, "%s: not in z/x/y form\n", argv[optind + 2]); - usage(argv); - } - - std::string tile; - char buf[1000]; - int len; - - FILE *f = fopen(infile, "rb"); - if (f == NULL) { - perror(infile); - exit(EXIT_FAILURE); - } - - while ((len = fread(buf, sizeof(char), 1000, f)) > 0) { - tile.append(std::string(buf, len)); - } - fclose(f); - - f = fopen(outfile, "wb"); - if (f == NULL) { - perror(outfile); - exit(EXIT_FAILURE); - } - - std::string out = overzoom(tile, oz, ox, oy, nz, nx, ny, detail, buffer, keep, true, NULL); - fwrite(out.c_str(), sizeof(char), out.size(), f); - fclose(f); - return 0; } diff --git a/tile-join.cpp b/tile-join.cpp index 6e2f9319..025b8de9 100644 --- a/tile-join.cpp +++ b/tile-join.cpp @@ -782,7 +782,16 @@ struct tileset_reader { } if (source.layers.size() != 0) { - std::string ret = overzoom(source, parent_tile.z, parent_tile.x, parent_tile.y, tile.z, tile.x, tile.y, -1, buffer, std::set(), false, &next_overzoomed_tiles); + source_tile st; + st.tile = source; + st.z = parent_tile.z; + st.x = parent_tile.x; + st.y = parent_tile.y; + + std::vector sts; + sts.push_back(st); + + std::string ret = overzoom(sts, tile.z, tile.x, tile.y, -1, buffer, std::set(), false, &next_overzoomed_tiles); return ret; } diff --git a/version.hpp b/version.hpp index 1fbbe1a5..709cd9a7 100644 --- a/version.hpp +++ b/version.hpp @@ -1,6 +1,6 @@ #ifndef VERSION_HPP #define VERSION_HPP -#define VERSION "v2.37.1" +#define VERSION "v2.38.0" #endif