Skip to content

Commit

Permalink
Improve quality of coalescing-as-needed; add options to limit feature…
Browse files Browse the repository at this point in the history
… 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 commit ace173a.

* Revert "Fix the option name in changelog"

This reverts commit 38dbce8.

* Revert "Add an option to limit geometry vertex count"

This reverts commit d379fdf.

* Remove test for reverted option

* Remove more leftovers from geometry size limiting

* Update changelog
  • Loading branch information
e-n-f authored Nov 17, 2022
1 parent 7733cce commit 8eec2be
Show file tree
Hide file tree
Showing 30 changed files with 1,773 additions and 636 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 2.13.0

* Add --limit-tile-feature-count and --limit-tile-feature-count-at-maximum-zoom
* Coalesce small features only onto other small features with `--coalesce-smallest-as-needed`, never to large features
* Clean coalesced-as-needed features before simplifying them, to improve simplification quality

## 2.12.0

* Add `--drop-denser` option to drop points in dense clusters in preference
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,8 @@ the same layer, enclose them in an `all` expression so they will all be evaluate

* `-M` _bytes_ or `--maximum-tile-bytes=`_bytes_: Use the specified number of _bytes_ as the maximum compressed tile size instead of 500K.
* `-O` _features_ or `--maximum-tile-features=`_features_: Use the specified number of _features_ as the maximum in a tile instead of 200,000.
* `--limit-tile-feature-count=`_features_: Abruptly limit each tile to the specified number of _features_, after ordering them if specified.
* `--limit-tile-feature-count-at-maximum-zoom=`_features_: Abruptly limit each tile at the maximum zoom level to the specified number of _features_, after ordering them if specified.
* `-pf` or `--no-feature-limit`: Don't limit tiles to 200,000 features
* `-pk` or `--no-tile-size-limit`: Don't limit tiles to 500K bytes
* `-pC` or `--no-tile-compression`: Don't compress the PBF vector tile data. If you are getting "Unimplemented type 3" error messages from a renderer, it is probably because it expects uncompressed tiles using this option rather than the normal gzip-compressed tiles.
Expand Down
9 changes: 6 additions & 3 deletions geometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -632,12 +632,12 @@ drawvec reduce_tiny_poly(drawvec &geom, int z, int detail, bool *reduced, double
// inner rings must just have their area de-accumulated rather
// than being drawn since we don't really know where they are.

// i.e., this ring (inner or outer) is small enough that we are including it
// i.e., this outer ring is small enough that we are including it
// in a tiny polygon rather than letting it represent itself,
// OR it is an inner ring and we haven't output an outer ring for it to be
// cut out of, so we are just subtracting its area from the tiny polygon
// rather than trying to deal with it geometrically
if (std::fabs(area) <= pixel * pixel || (area < 0 && !included_last_outer)) {
if ((area > 0 && area <= pixel * pixel) || (area < 0 && !included_last_outer)) {
// printf("area is only %f vs %lld so using square\n", area, pixel * pixel);

*accum_area += area;
Expand All @@ -659,10 +659,13 @@ drawvec reduce_tiny_poly(drawvec &geom, int z, int detail, bool *reduced, double
}
}
// i.e., this ring is large enough that it gets to represent itself
// or it is a tiny hole out of a real polygon, which we are still treating
// as a real geometry because otherwise we can accumulate enough tiny holes
// that we will drop the next several outer rings getting back up to 0.
else {
// printf("area is %f so keeping instead of %lld\n", area, pixel * pixel);

for (size_t k = i; k <= j && k < geom.size(); k++) {
for (size_t k = i; k < j && k < geom.size(); k++) {
out.push_back(geom[k]);
}

Expand Down
8 changes: 8 additions & 0 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ int cluster_distance = 0;
int tiny_polygon_size = 2;
long justx = -1, justy = -1;
std::string attribute_for_id = "";
size_t limit_tile_feature_count = 0;
size_t limit_tile_feature_count_at_maxzoom = 0;
unsigned int drop_denser = 0;

std::vector<order_field> order_by;
Expand Down Expand Up @@ -2851,6 +2853,8 @@ int main(int argc, char **argv) {
{"Setting or disabling tile size limits", 0, 0, 0},
{"maximum-tile-bytes", required_argument, 0, 'M'},
{"maximum-tile-features", required_argument, 0, 'O'},
{"limit-tile-feature-count", required_argument, 0, '~'},
{"limit-tile-feature-count-at-maximum-zoom", required_argument, 0, '~'},
{"no-feature-limit", no_argument, &prevent[P_FEATURE_LIMIT], 1},
{"no-tile-size-limit", no_argument, &prevent[P_KILOBYTE_LIMIT], 1},
{"no-tile-compression", no_argument, &prevent[P_TILE_COMPRESSION], 1},
Expand Down Expand Up @@ -2981,6 +2985,10 @@ int main(int argc, char **argv) {
exit(EXIT_ARGS);
}
break;
} else if (strcmp(opt, "limit-tile-feature-count") == 0) {
limit_tile_feature_count = atoll_require(optarg, "Limit tile feature count");
} else if (strcmp(opt, "limit-tile-feature-count-at-maximum-zoom") == 0) {
limit_tile_feature_count_at_maxzoom = atoll_require(optarg, "Limit tile feature count at maxzoom");
} else if (strcmp(opt, "drop-denser") == 0) {
drop_denser = atoi_require(optarg, "Drop denser rate");
if (drop_denser > 100) {
Expand Down
2 changes: 2 additions & 0 deletions main.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ extern size_t max_tile_features;
extern int cluster_distance;
extern std::string attribute_for_id;
extern int tiny_polygon_size;
extern size_t limit_tile_feature_count;
extern size_t limit_tile_feature_count_at_maxzoom;

struct order_field {
std::string name;
Expand Down
4 changes: 4 additions & 0 deletions man/tippecanoe.1
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,10 @@ the line or polygon within one tile unit of its proper location. You can probabl
.IP \(bu 2
\fB\fC\-O\fR \fIfeatures\fP or \fB\fC\-\-maximum\-tile\-features=\fR\fIfeatures\fP: Use the specified number of \fIfeatures\fP as the maximum in a tile instead of 200,000.
.IP \(bu 2
\fB\fC\-\-limit\-tile\-feature\-count=\fR\fIfeatures\fP: Abruptly limit each tile to the specified number of \fIfeatures\fP, after ordering them if specified.
.IP \(bu 2
\fB\fC\-\-limit\-tile\-feature\-count\-at\-maximum\-zoom=\fR\fIfeatures\fP: Abruptly limit each tile at the maximum zoom level to the specified number of \fIfeatures\fP, after ordering them if specified.
.IP \(bu 2
\fB\fC\-pf\fR or \fB\fC\-\-no\-feature\-limit\fR: Don't limit tiles to 200,000 features
.IP \(bu 2
\fB\fC\-pk\fR or \fB\fC\-\-no\-tile\-size\-limit\fR: Don't limit tiles to 500K bytes
Expand Down
6 changes: 6 additions & 0 deletions mbtiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,12 @@ std::string stringify_strategies(std::vector<strategy> const &strategies) {
any = true;
}

if (strategies[i].feature_count > 0) {
state.json_write_string("feature_count_desired");
state.json_write_number(strategies[i].feature_count);
any = true;
}

state.json_end_hash();
}
state.json_end_array();
Expand Down
1 change: 0 additions & 1 deletion options.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
#define A_PREFER_RADIX_SORT ((int) 'R')
#define A_CALCULATE_FEATURE_DENSITY ((int) 'g')
#define A_INCREASE_GAMMA_AS_NEEDED ((int) 'G')
#define A_MERGE_POLYGONS_AS_NEEDED ((int) 'm')
#define A_DROP_DENSEST_AS_NEEDED ((int) 's')
#define A_DROP_FRACTION_AS_NEEDED ((int) 'd')
#define A_DROP_SMALLEST_AS_NEEDED ((int) 'n')
Expand Down
2 changes: 1 addition & 1 deletion tests/loop/out/-z0_-O200_--cluster-densest-as-needed.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"maxzoom": "0",
"minzoom": "0",
"name": "tests/loop/out/-z0_-O200_--cluster-densest-as-needed.json.check.mbtiles",
"strategies": "[ { \"coalesced_as_needed\": 999 } ]",
"strategies": "[ { \"coalesced_as_needed\": 999, \"feature_count_desired\": 1000 } ]",
"type": "overlay",
"version": "2"
}, "features": [
Expand Down
2 changes: 1 addition & 1 deletion tests/loop/out/-z0_-O200_--drop-densest-as-needed.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"maxzoom": "0",
"minzoom": "0",
"name": "tests/loop/out/-z0_-O200_--drop-densest-as-needed.json.check.mbtiles",
"strategies": "[ { \"dropped_as_needed\": 999 } ]",
"strategies": "[ { \"dropped_as_needed\": 999, \"feature_count_desired\": 1000 } ]",
"type": "overlay",
"version": "2"
}, "features": [
Expand Down
2 changes: 1 addition & 1 deletion tests/loop/out/-z0_-O200_--drop-fraction-as-needed.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"maxzoom": "0",
"minzoom": "0",
"name": "tests/loop/out/-z0_-O200_--drop-fraction-as-needed.json.check.mbtiles",
"strategies": "[ { \"dropped_as_needed\": 811 } ]",
"strategies": "[ { \"dropped_as_needed\": 811, \"feature_count_desired\": 1000 } ]",
"type": "overlay",
"version": "2"
}, "features": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"maxzoom": "13",
"minzoom": "11",
"name": "tests/muni/out/-Z11_-z13_-O100_--cluster-densest-as-needed.json.check.mbtiles",
"strategies": "[ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { \"dropped_by_rate\": 4080, \"coalesced_as_needed\": 662 }, { \"dropped_by_rate\": 2973, \"coalesced_as_needed\": 1747 }, { \"coalesced_as_needed\": 4342 } ]",
"strategies": "[ { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { \"dropped_by_rate\": 4080, \"coalesced_as_needed\": 662, \"feature_count_desired\": 687 }, { \"dropped_by_rate\": 2973, \"coalesced_as_needed\": 1747, \"feature_count_desired\": 755 }, { \"coalesced_as_needed\": 4342, \"feature_count_desired\": 856 } ]",
"type": "overlay",
"version": "2"
}, "features": [
Expand Down
Loading

0 comments on commit 8eec2be

Please sign in to comment.