mirror of
https://github.com/felt/tippecanoe.git
synced 2026-10-02 16:35:40 +02:00
Improve quality of coalescing-as-needed; add options to limit feature count (#25)
* Add an option to limit geometry vertex count * Update docs and changelog * Track desired feature count and geometry size in strategies * Remove dead code for a long-forgotten inaccessible option * Fix the option name in changelog * Refine coalesce-smallest to only coalesce onto other small features * Clean polygons before coalescing-as-needed * Don't accumulate tiny polygon holes as negative dust * Add the option not to limit the feature count at maxzoom * Add options to limit feature count more abruptly in each tile * Revert "Add the option not to limit the feature count at maxzoom" This reverts commitace173ab7a. * Revert "Fix the option name in changelog" This reverts commit38dbce8405. * Revert "Add an option to limit geometry vertex count" This reverts commitd379fdf06b. * Remove test for reverted option * Remove more leftovers from geometry size limiting * Update changelog
This commit is contained in:
@@ -450,6 +450,7 @@ struct partial {
|
||||
unsigned long long label_point = 0;
|
||||
int segment = 0;
|
||||
bool reduced = 0;
|
||||
bool coalesced = 0;
|
||||
int z = 0;
|
||||
int tx = 0;
|
||||
int ty = 0;
|
||||
@@ -562,6 +563,13 @@ void *partial_feature_worker(void *v) {
|
||||
}
|
||||
|
||||
if (!already_marked) {
|
||||
if ((*partials)[i].coalesced && t == VT_POLYGON) {
|
||||
// clean coalesced polygons before simplification to avoid
|
||||
// introducing shards between shapes that otherwise would have
|
||||
// unioned exactly
|
||||
geom = clean_or_clip_poly(geom, 0, 0, false);
|
||||
}
|
||||
|
||||
// continues to simplify to line_detail even if we have extra detail
|
||||
drawvec ngeom = simplify_lines(geom, z, line_detail, !(prevent[P_CLIPPING] || prevent[P_DUPLICATION]), (*partials)[i].simplification, t == VT_POLYGON ? 4 : 0, *(a->shared_nodes));
|
||||
|
||||
@@ -1304,6 +1312,7 @@ struct write_tile_args {
|
||||
double fraction = 0;
|
||||
double fraction_out = 0;
|
||||
size_t tile_size_out = 0;
|
||||
size_t feature_count_out = 0;
|
||||
const char *prefilter = NULL;
|
||||
const char *postfilter = NULL;
|
||||
std::map<std::string, attribute_op> const *attribute_accum = NULL;
|
||||
@@ -1774,13 +1783,13 @@ void preserve_attributes(std::map<std::string, attribute_op> const *attribute_ac
|
||||
}
|
||||
}
|
||||
|
||||
bool find_partial(std::vector<partial> &partials, serial_feature &sf, ssize_t &out, std::vector<std::vector<std::string>> *layer_unmaps) {
|
||||
bool find_partial(std::vector<partial> &partials, serial_feature &sf, ssize_t &out, std::vector<std::vector<std::string>> *layer_unmaps, long long maxextent) {
|
||||
for (size_t i = partials.size(); i > 0; i--) {
|
||||
if (partials[i - 1].t == sf.t) {
|
||||
std::string &layername1 = (*layer_unmaps)[partials[i - 1].segment][partials[i - 1].layer];
|
||||
std::string &layername2 = (*layer_unmaps)[sf.segment][sf.layer];
|
||||
|
||||
if (layername1 == layername2) {
|
||||
if (layername1 == layername2 && partials[i - 1].extent <= maxextent) {
|
||||
out = i - 1;
|
||||
return true;
|
||||
}
|
||||
@@ -1838,8 +1847,6 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
|
||||
}
|
||||
}
|
||||
|
||||
bool has_polygons = false;
|
||||
|
||||
bool first_time = true;
|
||||
// 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.
|
||||
@@ -1984,7 +1991,7 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
|
||||
}
|
||||
|
||||
if (sf.dropped) {
|
||||
if (find_partial(partials, sf, which_partial, layer_unmaps)) {
|
||||
if (find_partial(partials, sf, which_partial, layer_unmaps, LLONG_MAX)) {
|
||||
preserve_attributes(arg->attribute_accum, sf, stringpool, pool_off, partials[which_partial]);
|
||||
strategy->dropped_by_rate++;
|
||||
continue;
|
||||
@@ -1992,7 +1999,7 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
|
||||
}
|
||||
|
||||
if (gamma > 0) {
|
||||
if (manage_gap(sf.index, &previndex, scale, gamma, &gap) && find_partial(partials, sf, which_partial, layer_unmaps)) {
|
||||
if (manage_gap(sf.index, &previndex, scale, gamma, &gap) && find_partial(partials, sf, which_partial, layer_unmaps, LLONG_MAX)) {
|
||||
preserve_attributes(arg->attribute_accum, sf, stringpool, pool_off, partials[which_partial]);
|
||||
strategy->dropped_by_gamma++;
|
||||
continue;
|
||||
@@ -2001,7 +2008,7 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
|
||||
|
||||
if (additional[A_CLUSTER_DENSEST_AS_NEEDED] || cluster_distance != 0) {
|
||||
indices.push_back(sf.index);
|
||||
if ((sf.index < merge_previndex || sf.index - merge_previndex < mingap) && find_partial(partials, sf, which_partial, layer_unmaps)) {
|
||||
if ((sf.index < merge_previndex || sf.index - merge_previndex < mingap) && find_partial(partials, sf, which_partial, layer_unmaps, LLONG_MAX)) {
|
||||
partials[which_partial].clustered++;
|
||||
|
||||
if (partials[which_partial].t == VT_POINT &&
|
||||
@@ -2022,15 +2029,16 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
|
||||
}
|
||||
} else if (additional[A_DROP_DENSEST_AS_NEEDED]) {
|
||||
indices.push_back(sf.index);
|
||||
if (sf.index - merge_previndex < mingap && find_partial(partials, sf, which_partial, layer_unmaps)) {
|
||||
if (sf.index - merge_previndex < mingap && find_partial(partials, sf, which_partial, layer_unmaps, LLONG_MAX)) {
|
||||
preserve_attributes(arg->attribute_accum, sf, stringpool, pool_off, partials[which_partial]);
|
||||
strategy->dropped_as_needed++;
|
||||
continue;
|
||||
}
|
||||
} else if (additional[A_COALESCE_DENSEST_AS_NEEDED]) {
|
||||
indices.push_back(sf.index);
|
||||
if (sf.index - merge_previndex < mingap && find_partial(partials, sf, which_partial, layer_unmaps)) {
|
||||
if (sf.index - merge_previndex < mingap && find_partial(partials, sf, which_partial, layer_unmaps, LLONG_MAX)) {
|
||||
partials[which_partial].geoms.push_back(sf.geometry);
|
||||
partials[which_partial].coalesced = true;
|
||||
coalesced_area += sf.extent;
|
||||
preserve_attributes(arg->attribute_accum, sf, stringpool, pool_off, partials[which_partial]);
|
||||
strategy->coalesced_as_needed++;
|
||||
@@ -2038,15 +2046,16 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
|
||||
}
|
||||
} else if (additional[A_DROP_SMALLEST_AS_NEEDED]) {
|
||||
extents.push_back(sf.extent);
|
||||
if (sf.extent + coalesced_area <= minextent && find_partial(partials, sf, which_partial, layer_unmaps)) {
|
||||
if (sf.extent + coalesced_area <= minextent && find_partial(partials, sf, which_partial, layer_unmaps, minextent)) {
|
||||
preserve_attributes(arg->attribute_accum, sf, stringpool, pool_off, partials[which_partial]);
|
||||
strategy->dropped_as_needed++;
|
||||
continue;
|
||||
}
|
||||
} else if (additional[A_COALESCE_SMALLEST_AS_NEEDED]) {
|
||||
extents.push_back(sf.extent);
|
||||
if (sf.extent + coalesced_area <= minextent && find_partial(partials, sf, which_partial, layer_unmaps)) {
|
||||
if (sf.extent + coalesced_area <= minextent && find_partial(partials, sf, which_partial, layer_unmaps, minextent)) {
|
||||
partials[which_partial].geoms.push_back(sf.geometry);
|
||||
partials[which_partial].coalesced = true;
|
||||
coalesced_area += sf.extent;
|
||||
preserve_attributes(arg->attribute_accum, sf, stringpool, pool_off, partials[which_partial]);
|
||||
strategy->coalesced_as_needed++;
|
||||
@@ -2067,9 +2076,10 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
|
||||
}
|
||||
|
||||
fraction_accum += fraction;
|
||||
if (fraction_accum < 1 && find_partial(partials, sf, which_partial, layer_unmaps)) {
|
||||
if (fraction_accum < 1 && find_partial(partials, sf, which_partial, layer_unmaps, LLONG_MAX)) {
|
||||
if (additional[A_COALESCE_FRACTION_AS_NEEDED]) {
|
||||
partials[which_partial].geoms.push_back(sf.geometry);
|
||||
partials[which_partial].coalesced = true;
|
||||
coalesced_area += sf.extent;
|
||||
strategy->coalesced_as_needed++;
|
||||
} else {
|
||||
@@ -2088,7 +2098,6 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
|
||||
strategy->tiny_polygons++;
|
||||
}
|
||||
}
|
||||
has_polygons = true;
|
||||
}
|
||||
if (sf.t == VT_POLYGON || sf.t == VT_LINE) {
|
||||
if (line_is_too_small(sf.geometry, z, line_detail)) {
|
||||
@@ -2117,6 +2126,7 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
|
||||
p.segment = sf.segment;
|
||||
p.original_seq = sf.seq;
|
||||
p.reduced = reduced;
|
||||
p.coalesced = false;
|
||||
p.z = z;
|
||||
p.tx = tx;
|
||||
p.ty = ty;
|
||||
@@ -2238,10 +2248,9 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
|
||||
}
|
||||
|
||||
first_time = false;
|
||||
bool merge_successful = true;
|
||||
|
||||
if (additional[A_DETECT_SHARED_BORDERS] || (additional[A_MERGE_POLYGONS_AS_NEEDED] && merge_fraction < 1)) {
|
||||
merge_successful = find_common_edges(partials, z, line_detail, simplification, maxzoom, merge_fraction);
|
||||
if (additional[A_DETECT_SHARED_BORDERS]) {
|
||||
find_common_edges(partials, z, line_detail, simplification, maxzoom, merge_fraction);
|
||||
}
|
||||
|
||||
int tasks = ceil((double) CPUS / *running);
|
||||
@@ -2395,6 +2404,16 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
|
||||
if (order_by.size() != 0) {
|
||||
std::sort(layer_features.begin(), layer_features.end(), ordercmp);
|
||||
}
|
||||
|
||||
if (z == maxzoom && limit_tile_feature_count_at_maxzoom != 0) {
|
||||
if (layer_features.size() > limit_tile_feature_count_at_maxzoom) {
|
||||
layer_features.resize(limit_tile_feature_count_at_maxzoom);
|
||||
}
|
||||
} else if (limit_tile_feature_count != 0) {
|
||||
if (layer_features.size() > limit_tile_feature_count) {
|
||||
layer_features.resize(limit_tile_feature_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mvt_tile tile;
|
||||
@@ -2490,18 +2509,15 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
|
||||
|
||||
if (totalsize > 0 && tile.layers.size() > 0) {
|
||||
if (totalsize > max_tile_features && !prevent[P_FEATURE_LIMIT]) {
|
||||
if (totalsize > arg->feature_count_out) {
|
||||
arg->feature_count_out = totalsize;
|
||||
}
|
||||
|
||||
if (!quiet) {
|
||||
fprintf(stderr, "tile %d/%u/%u has %zu features, >%zu \n", z, tx, ty, totalsize, max_tile_features);
|
||||
}
|
||||
|
||||
if (has_polygons && additional[A_MERGE_POLYGONS_AS_NEEDED] && merge_fraction > .05 && merge_successful) {
|
||||
merge_fraction = merge_fraction * max_tile_features / tile.layers.size() * 0.95;
|
||||
if (!quiet) {
|
||||
fprintf(stderr, "Going to try merging %0.2f%% of the polygons to make it fit\n", 100 - merge_fraction * 100);
|
||||
}
|
||||
line_detail++; // to keep it the same when the loop decrements it
|
||||
continue;
|
||||
} else if (additional[A_INCREASE_GAMMA_AS_NEEDED] && gamma < 10) {
|
||||
if (additional[A_INCREASE_GAMMA_AS_NEEDED] && gamma < 10) {
|
||||
if (gamma < 1) {
|
||||
gamma = 1;
|
||||
} else {
|
||||
@@ -2603,13 +2619,7 @@ long long write_tile(FILE *geoms, std::atomic<long long> *geompos_in, char *meta
|
||||
}
|
||||
}
|
||||
|
||||
if (has_polygons && additional[A_MERGE_POLYGONS_AS_NEEDED] && merge_fraction > .05 && merge_successful) {
|
||||
merge_fraction = merge_fraction * max_tile_size / (kept_adjust * compressed.size()) * 0.95;
|
||||
if (!quiet) {
|
||||
fprintf(stderr, "Going to try merging %0.2f%% of the polygons to make it fit\n", 100 - merge_fraction * 100);
|
||||
}
|
||||
line_detail++; // to keep it the same when the loop decrements it
|
||||
} else if (additional[A_INCREASE_GAMMA_AS_NEEDED] && gamma < 10) {
|
||||
if (additional[A_INCREASE_GAMMA_AS_NEEDED] && gamma < 10) {
|
||||
if (gamma < 1) {
|
||||
gamma = 1;
|
||||
} else {
|
||||
@@ -2964,6 +2974,7 @@ int traverse_zooms(int *geomfd, off_t *geom_size, char *metabase, char *stringpo
|
||||
long long zoom_minextent = 0;
|
||||
double zoom_fraction = 1;
|
||||
size_t zoom_tile_size = 0;
|
||||
size_t zoom_feature_count = 0;
|
||||
|
||||
for (size_t pass = start; pass < 2; pass++) {
|
||||
pthread_t pthreads[threads];
|
||||
@@ -2993,6 +3004,7 @@ int traverse_zooms(int *geomfd, off_t *geom_size, char *metabase, char *stringpo
|
||||
args[thread].fraction = zoom_fraction;
|
||||
args[thread].fraction_out = zoom_fraction;
|
||||
args[thread].tile_size_out = 0;
|
||||
args[thread].feature_count_out = 0;
|
||||
args[thread].child_shards = TEMP_FILES / threads;
|
||||
|
||||
if (i == maxzoom && maxzoom_simplification > 0) {
|
||||
@@ -3062,6 +3074,9 @@ int traverse_zooms(int *geomfd, off_t *geom_size, char *metabase, char *stringpo
|
||||
if (args[thread].tile_size_out > zoom_tile_size) {
|
||||
zoom_tile_size = args[thread].tile_size_out;
|
||||
}
|
||||
if (args[thread].feature_count_out > zoom_feature_count) {
|
||||
zoom_feature_count = args[thread].feature_count_out;
|
||||
}
|
||||
|
||||
// Zoom counter might be lower than reality if zooms are being skipped
|
||||
if (args[thread].wrote_zoom > i) {
|
||||
@@ -3076,7 +3091,7 @@ int traverse_zooms(int *geomfd, off_t *geom_size, char *metabase, char *stringpo
|
||||
if ((size_t) i >= strategies.size()) {
|
||||
strategies.resize(i + 1);
|
||||
}
|
||||
struct strategy s(strategy, zoom_tile_size);
|
||||
struct strategy s(strategy, zoom_tile_size, zoom_feature_count);
|
||||
strategies[i] = s;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user