Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an option to exclude tiles that are entirely covered by a polygon #808

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ the same layer, enclose them in an `all` expression so they will all be evaluate
* `-b` _pixels_ or `--buffer=`_pixels_: Buffer size where features are duplicated from adjacent tiles. Units are "screen pixels"—1/256th of the tile width or height. (default 5)
* `-pc` or `--no-clipping`: Don't clip features to the size of the tile. If a feature overlaps the tile's bounds or buffer at all, it is included completely. Be careful: this can produce very large tilesets, especially with large polygons.
* `-pD` or `--no-duplication`: As with `--no-clipping`, each feature is included intact instead of cut to tile boundaries. In addition, it is included only in a single tile per zoom level rather than potentially in multiple copies. Clients of the tileset must check adjacent tiles (possibly some distance away) to ensure they have all features.
* `-pF` or `--remove-filled`: Do not generate tiles that consist entirely of polygons that cover the entire extent of the tile. This is primarily useful for preventing the generation of large number of water or land cover tiles.

### Reordering features within each tile

Expand Down
6 changes: 4 additions & 2 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ size_t max_tile_features = 200000;
int cluster_distance = 0;
long justx = -1, justy = -1;
std::string attribute_for_id = "";
int fillzoom = -1;

int prevent[256];
int additional[256];
Expand Down Expand Up @@ -2267,7 +2268,7 @@ int read_input(std::vector<source> &sources, char *fname, int maxzoom, int minzo

std::atomic<unsigned> midx(0);
std::atomic<unsigned> midy(0);
int written = traverse_zooms(fd, size, meta, stringpool, &midx, &midy, maxzoom, minzoom, outdb, outdir, buffer, fname, tmpdir, gamma, full_detail, low_detail, min_detail, meta_off, pool_off, initial_x, initial_y, simplification, layermaps, prefilter, postfilter, attribute_accum, filter);
int written = traverse_zooms(fd, size, meta, stringpool, &midx, &midy, maxzoom, minzoom, outdb, outdir, buffer, fname, tmpdir, gamma, full_detail, low_detail, min_detail, meta_off, pool_off, initial_x, initial_y, simplification, layermaps, prefilter, postfilter, attribute_accum, filter, &fillzoom);

if (maxzoom != written) {
if (written > minzoom) {
Expand Down Expand Up @@ -2329,7 +2330,7 @@ int read_input(std::vector<source> &sources, char *fname, int maxzoom, int minzo
ai->second.maxzoom = maxzoom;
}

mbtiles_write_metadata(outdb, outdir, fname, minzoom, maxzoom, minlat, minlon, maxlat, maxlon, midlat, midlon, forcetable, attribution, merged_lm, true, description, !prevent[P_TILE_STATS], attribute_descriptions, "tippecanoe", commandline);
mbtiles_write_metadata(outdb, outdir, fname, minzoom, maxzoom, fillzoom, minlat, minlon, maxlat, maxlon, midlat, midlon, forcetable, attribution, merged_lm, true, description, !prevent[P_TILE_STATS], attribute_descriptions, "tippecanoe", commandline);

return ret;
}
Expand Down Expand Up @@ -2579,6 +2580,7 @@ int main(int argc, char **argv) {
{"buffer", required_argument, 0, 'b'},
{"no-clipping", no_argument, &prevent[P_CLIPPING], 1},
{"no-duplication", no_argument, &prevent[P_DUPLICATION], 1},
{"remove-filled", no_argument, &prevent[P_FILLED], 1},

{"Reordering features within each tile", 0, 0, 0},
{"preserve-input-order", no_argument, &prevent[P_INPUT_ORDER], 1},
Expand Down
13 changes: 12 additions & 1 deletion mbtiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ void tilestats(std::map<std::string, layermap_entry> const &layermap1, size_t el
state.json_end_hash();
}

void mbtiles_write_metadata(sqlite3 *outdb, const char *outdir, const char *fname, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, int forcetable, const char *attribution, std::map<std::string, layermap_entry> const &layermap, bool vector, const char *description, bool do_tilestats, std::map<std::string, std::string> const &attribute_descriptions, std::string const &program, std::string const &commandline) {
void mbtiles_write_metadata(sqlite3 *outdb, const char *outdir, const char *fname, int minzoom, int maxzoom, int fillzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, int forcetable, const char *attribution, std::map<std::string, layermap_entry> const &layermap, bool vector, const char *description, bool do_tilestats, std::map<std::string, std::string> const &attribute_descriptions, std::string const &program, std::string const &commandline) {
char *sql, *err;

sqlite3 *db = outdb;
Expand Down Expand Up @@ -330,6 +330,17 @@ void mbtiles_write_metadata(sqlite3 *outdb, const char *outdir, const char *fnam
}
sqlite3_free(sql);

if (fillzoom >= 0) {
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('fillzoom', %d);", fillzoom);
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set fillzoom: %s\n", err);
if (!forcetable) {
exit(EXIT_FAILURE);
}
}
sqlite3_free(sql);
}

sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('center', '%f,%f,%d');", midlon, midlat, maxzoom);
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "set center: %s\n", err);
Expand Down
2 changes: 1 addition & 1 deletion mbtiles.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ sqlite3 *mbtiles_open(char *dbname, char **argv, int forcetable);

void mbtiles_write_tile(sqlite3 *outdb, int z, int tx, int ty, const char *data, int size);

void mbtiles_write_metadata(sqlite3 *outdb, const char *outdir, const char *fname, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, int forcetable, const char *attribution, std::map<std::string, layermap_entry> const &layermap, bool vector, const char *description, bool do_tilestats, std::map<std::string, std::string> const &attribute_descriptions, std::string const &program, std::string const &commandline);
void mbtiles_write_metadata(sqlite3 *outdb, const char *outdir, const char *fname, int minzoom, int maxzoom, int fillzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, int forcetable, const char *attribution, std::map<std::string, layermap_entry> const &layermap, bool vector, const char *description, bool do_tilestats, std::map<std::string, std::string> const &attribute_descriptions, std::string const &program, std::string const &commandline);

void mbtiles_close(sqlite3 *outdb, const char *pgm);

Expand Down
1 change: 1 addition & 0 deletions options.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#define P_SIMPLIFY_LOW ((int) 'S')
#define P_SIMPLIFY_SHARED_NODES ((int) 'n')
#define P_FEATURE_LIMIT ((int) 'f')
#define P_FILLED ((int) 'F')
#define P_KILOBYTE_LIMIT ((int) 'k')
#define P_DYNAMIC_DROP ((int) 'd')
#define P_INPUT_ORDER ((int) 'i')
Expand Down
3,930 changes: 3,930 additions & 0 deletions tests/ne_110m_admin_0_countries/out/-z4_-yname_--remove-filled.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tile-join.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1124,7 +1124,7 @@ int main(int argc, char **argv) {
}
}

mbtiles_write_metadata(outdb, out_dir, name.c_str(), st.minzoom, st.maxzoom, st.minlat, st.minlon, st.maxlat, st.maxlon, st.midlat, st.midlon, 0, attribution.size() != 0 ? attribution.c_str() : NULL, layermap, true, description.c_str(), !pg, attribute_descriptions, "tile-join", generator_options);
mbtiles_write_metadata(outdb, out_dir, name.c_str(), st.minzoom, st.maxzoom, -1, st.minlat, st.minlon, st.maxlat, st.maxlon, st.midlat, st.midlon, 0, attribution.size() != 0 ? attribution.c_str() : NULL, layermap, true, description.c_str(), !pg, attribute_descriptions, "tile-join", generator_options);

if (outdb != NULL) {
mbtiles_close(outdb, argv[0]);
Expand Down
85 changes: 80 additions & 5 deletions tile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1215,6 +1215,7 @@ struct write_tile_args {
int wrote_zoom = 0;
size_t tiling_seg = 0;
struct json_object *filter = NULL;
bool removed_filled = false;
};

bool clip_to_tile(serial_feature &sf, int z, long long buffer) {
Expand Down Expand Up @@ -1312,7 +1313,44 @@ void remove_attributes(serial_feature &sf, std::set<std::string> const &exclude_
}
}

serial_feature next_feature(FILE *geoms, std::atomic<long long> *geompos_in, char *metabase, long long *meta_off, 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, size_t passes, std::atomic<long long> *along, long long alongminus, int buffer, int *within, bool *first_time, FILE **geomfile, std::atomic<long long> *geompos, std::atomic<double> *oprogress, double todo, const char *fname, int child_shards, struct json_object *filter, const char *stringpool, long long *pool_off, std::vector<std::vector<std::string>> *layer_unmaps) {
bool is_filled(serial_feature &sf, int z) {
long long extent = 1LL << (32 - z);

if (prevent[P_FILLED]) {
if (sf.t != VT_POLYGON || sf.geometry.size() != 5) {
return false;
}

// It is a polygon with four sides.
// Are they all outside the tile boundary?

for (size_t j = 0; j + 1 < sf.geometry.size(); j++) {
if (sf.geometry[j].x == sf.geometry[j + 1].x && (sf.geometry[j].x < 0 || sf.geometry[j].x > extent)) {
// vertical line, outside the edge of the tile

if (!((sf.geometry[j].y < 0 && sf.geometry[j + 1].y > extent) ||
(sf.geometry[j + 1].y < 0 && sf.geometry[j].y > extent))) {
return false;
}
} else if (sf.geometry[j].y == sf.geometry[j + 1].y && (sf.geometry[j].y < 0 || sf.geometry[j].y > extent)) {
// horizontal line, outside the edge of the tile

if (!((sf.geometry[j].x < 0 && sf.geometry[j + 1].x > extent) ||
(sf.geometry[j + 1].x < 0 && sf.geometry[j].x > extent))) {
return false;
}
} else {
return false;
}
}

return true;
} else {
return false;
}
}

serial_feature next_feature(FILE *geoms, std::atomic<long long> *geompos_in, char *metabase, long long *meta_off, 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, size_t passes, std::atomic<long long> *along, long long alongminus, int buffer, int *within, bool *first_time, FILE **geomfile, std::atomic<long long> *geompos, std::atomic<double> *oprogress, double todo, const char *fname, int child_shards, struct json_object *filter, const char *stringpool, long long *pool_off, std::vector<std::vector<std::string>> *layer_unmaps, std::vector<serial_feature> *rewrite_queue, bool *is_rewriting) {
while (1) {
serial_feature sf = deserialize_feature(geoms, geompos_in, metabase, meta_off, z, tx, ty, initial_x, initial_y);
if (sf.t < 0) {
Expand All @@ -1339,7 +1377,26 @@ serial_feature next_feature(FILE *geoms, std::atomic<long long> *geompos_in, cha

if (*first_time && pass == 1) { /* 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.metapos, 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.extent);
if (!*is_rewriting) {
if (is_filled(sf, z)) {
rewrite_queue->push_back(sf);
} else {
// Found a feature that we need to keep, so also push out all the
// filled features that we weren't sure about before.

*is_rewriting = true;

for (auto &rsf : *rewrite_queue) {
rewrite(rsf.geometry, z, nextzoom, maxzoom, rsf.bbox, tx, ty, buffer, within, geompos, geomfile, fname, rsf.t, rsf.layer, rsf.metapos, rsf.feature_minzoom, child_shards, max_zoom_increment, rsf.seq, rsf.tippecanoe_minzoom, rsf.tippecanoe_maxzoom, rsf.segment, initial_x, initial_y, rsf.keys, rsf.values, rsf.has_id, rsf.id, rsf.index, rsf.extent);
}

rewrite_queue->clear();
}
}

if (*is_rewriting) {
rewrite(sf.geometry, z, nextzoom, maxzoom, sf.bbox, tx, ty, buffer, within, geompos, geomfile, fname, sf.t, sf.layer, sf.metapos, 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.extent);
}
}
}

Expand Down Expand Up @@ -1473,14 +1530,16 @@ struct run_prefilter_args {
long long *pool_off = NULL;
FILE *prefilter_fp = NULL;
struct json_object *filter = NULL;
std::vector<serial_feature> *rewrite_queue;
bool *is_rewriting;
};

void *run_prefilter(void *v) {
run_prefilter_args *rpa = (run_prefilter_args *) v;
json_writer state(rpa->prefilter_fp);

while (1) {
serial_feature sf = next_feature(rpa->geoms, rpa->geompos_in, rpa->metabase, rpa->meta_off, 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->passes, rpa->along, rpa->alongminus, rpa->buffer, rpa->within, rpa->first_time, rpa->geomfile, rpa->geompos, rpa->oprogress, rpa->todo, rpa->fname, rpa->child_shards, rpa->filter, rpa->stringpool, rpa->pool_off, rpa->layer_unmaps);
serial_feature sf = next_feature(rpa->geoms, rpa->geompos_in, rpa->metabase, rpa->meta_off, 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->passes, rpa->along, rpa->alongminus, rpa->buffer, rpa->within, rpa->first_time, rpa->geomfile, rpa->geompos, rpa->oprogress, rpa->todo, rpa->fname, rpa->child_shards, rpa->filter, rpa->stringpool, rpa->pool_off, rpa->layer_unmaps, rpa->rewrite_queue, rpa->is_rewriting);
if (sf.t < 0) {
break;
}
Expand Down Expand Up @@ -1746,6 +1805,9 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
bool has_polygons = false;

bool first_time = true;
std::vector<serial_feature> rewrite_queue;
bool is_rewriting = false;

// This only loops if the tile data didn't fit, in which case the detail
// goes down and the progress indicator goes backward for the next try.
for (line_detail = detail; line_detail >= min_detail || line_detail == detail; line_detail--, oprogress = 0) {
Expand Down Expand Up @@ -1839,6 +1901,8 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
rpa.stringpool = stringpool;
rpa.pool_off = pool_off;
rpa.filter = filter;
rpa.rewrite_queue = &rewrite_queue;
rpa.is_rewriting = &is_rewriting;

if (pthread_create(&prefilter_writer, NULL, run_prefilter, &rpa) != 0) {
perror("pthread_create (prefilter writer)");
Expand All @@ -1858,7 +1922,7 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
ssize_t which_partial = -1;

if (prefilter == NULL) {
sf = next_feature(geoms, geompos_in, metabase, meta_off, z, tx, ty, initial_x, initial_y, &original_features, &unclipped_features, nextzoom, maxzoom, minzoom, max_zoom_increment, pass, passes, along, alongminus, buffer, within, &first_time, geomfile, geompos, &oprogress, todo, fname, child_shards, filter, stringpool, pool_off, layer_unmaps);
sf = next_feature(geoms, geompos_in, metabase, meta_off, z, tx, ty, initial_x, initial_y, &original_features, &unclipped_features, nextzoom, maxzoom, minzoom, max_zoom_increment, pass, passes, along, alongminus, buffer, within, &first_time, geomfile, geompos, &oprogress, todo, fname, child_shards, filter, stringpool, pool_off, layer_unmaps, &rewrite_queue, &is_rewriting);
} else {
sf = parse_feature(prefilter_jp, z, tx, ty, layermaps, tiling_seg, layer_unmaps, postfilter != NULL);
}
Expand Down Expand Up @@ -2085,6 +2149,10 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
}
}

if (first_time && pass == 1 && !is_rewriting && prevent[P_FILLED]) {
arg->removed_filled = true;
}

first_time = false;
bool merge_successful = true;

Expand Down Expand Up @@ -2647,7 +2715,7 @@ void *run_thread(void *vargs) {
return NULL;
}

int traverse_zooms(int *geomfd, off_t *geom_size, char *metabase, char *stringpool, std::atomic<unsigned> *midx, std::atomic<unsigned> *midy, int &maxzoom, int minzoom, sqlite3 *outdb, const char *outdir, int buffer, const char *fname, const char *tmpdir, double gamma, int full_detail, int low_detail, int min_detail, long long *meta_off, long long *pool_off, unsigned *initial_x, unsigned *initial_y, double simplification, std::vector<std::map<std::string, layermap_entry>> &layermaps, const char *prefilter, const char *postfilter, std::map<std::string, attribute_op> const *attribute_accum, struct json_object *filter) {
int traverse_zooms(int *geomfd, off_t *geom_size, char *metabase, char *stringpool, std::atomic<unsigned> *midx, std::atomic<unsigned> *midy, int &maxzoom, int minzoom, sqlite3 *outdb, const char *outdir, int buffer, const char *fname, const char *tmpdir, double gamma, int full_detail, int low_detail, int min_detail, long long *meta_off, long long *pool_off, unsigned *initial_x, unsigned *initial_y, double simplification, std::vector<std::map<std::string, layermap_entry>> &layermaps, const char *prefilter, const char *postfilter, std::map<std::string, attribute_op> const *attribute_accum, struct json_object *filter, int *fillzoom) {
last_progress = 0;

// The existing layermaps are one table per input thread.
Expand Down Expand Up @@ -2844,6 +2912,7 @@ int traverse_zooms(int *geomfd, off_t *geom_size, char *metabase, char *stringpo
args[thread].passes = 2 - start;
args[thread].wrote_zoom = -1;
args[thread].still_dropping = false;
args[thread].removed_filled = false;

if (pthread_create(&pthreads[thread], NULL, run_thread, &args[thread]) != 0) {
perror("pthread_create");
Expand Down Expand Up @@ -2883,6 +2952,12 @@ int traverse_zooms(int *geomfd, off_t *geom_size, char *metabase, char *stringpo
if (additional[A_EXTEND_ZOOMS] && i == maxzoom && args[thread].still_dropping && maxzoom < MAX_ZOOM) {
maxzoom++;
}

if (args[thread].removed_filled && *fillzoom < 0) {
// Removing filled means that there will be tiles missing
// at the *next* zoom, so this zoom is OK to overzoom from
*fillzoom = i;
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion tile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ enum attribute_op {

long long write_tile(char **geom, char *metabase, char *stringpool, unsigned *file_bbox, int z, unsigned x, unsigned y, int detail, int min_detail, int basezoom, sqlite3 *outdb, const char *outdir, double droprate, int buffer, const char *fname, FILE **geomfile, int file_minzoom, int file_maxzoom, double todo, char *geomstart, long long along, double gamma, int nlayers);

int traverse_zooms(int *geomfd, off_t *geom_size, char *metabase, char *stringpool, std::atomic<unsigned> *midx, std::atomic<unsigned> *midy, int &maxzoom, int minzoom, sqlite3 *outdb, const char *outdir, int buffer, const char *fname, const char *tmpdir, double gamma, int full_detail, int low_detail, int min_detail, long long *meta_off, long long *pool_off, unsigned *initial_x, unsigned *initial_y, double simplification, std::vector<std::map<std::string, layermap_entry> > &layermap, const char *prefilter, const char *postfilter, std::map<std::string, attribute_op> const *attribute_accum, struct json_object *filter);
int traverse_zooms(int *geomfd, off_t *geom_size, char *metabase, char *stringpool, std::atomic<unsigned> *midx, std::atomic<unsigned> *midy, int &maxzoom, int minzoom, sqlite3 *outdb, const char *outdir, int buffer, const char *fname, const char *tmpdir, double gamma, int full_detail, int low_detail, int min_detail, long long *meta_off, long long *pool_off, unsigned *initial_x, unsigned *initial_y, double simplification, std::vector<std::map<std::string, layermap_entry> > &layermap, const char *prefilter, const char *postfilter, std::map<std::string, attribute_op> const *attribute_accum, struct json_object *filter, int *fillzoom);

int manage_gap(unsigned long long index, unsigned long long *previndex, double scale, double gamma, double *gap);

Expand Down