Keep variable-depth pyramids from pruning features above their minzoom (#397)

--generate-variable-depth-tile-pyramid decides a tile is a leaf once its
geometry fits at full detail, then prunes the tile's entire subtree. The
guard that prevents leafing while deeper content is still pending consults
only feature_minzoom (the automatic dot-dropping zoom); it does not consult
tippecanoe_minzoom, the explicit per-feature minzoom set via the
tippecanoe.minzoom attribute.

So a feature carrying an explicit minzoom deeper than where its region leafs
is excluded from the leaf tile (z < minzoom) while its children are never
generated. It ends up in no tile at any zoom, silently dropped.

next_feature() excludes such a feature and continues without returning it,
so the leaf-prevention guard in write_tile() never sees it. Carry a flag out
of next_feature() when an excluded feature first appears beyond the next
zoom, and feed it into the same estimated-complexity path feature_minzoom
already uses, so the pyramid keeps subdividing down to the feature's minzoom.

The flag only affects estimated_complexity_out, which is written solely under
--generate-variable-depth-tile-pyramid, so builds without that flag are
unchanged. make test passes with no fixture diffs.
This commit is contained in:
Brandon Keepers
2026-07-22 17:00:08 -07:00
committed by GitHub
parent 0c650b881a
commit 0dc1e00eee
+7 -1
View File
@@ -1097,6 +1097,9 @@ static bool skip_next_feature(decompressor *geoms, std::atomic<long long> *geomp
struct next_feature_state {
unsigned long long previndex = 0;
unsigned long long prev_not_dropped_index = 0;
// set when a feature excluded by tippecanoe_minzoom first appears beyond the next
// zoom, so variable-depth pyramids don't leaf above the feature's minzoom and prune it away
bool minzoom_feature_pending = false;
};
// This function is called repeatedly from write_tile() to retrieve the next feature
@@ -1202,6 +1205,9 @@ static serial_feature next_feature(decompressor *geoms, std::atomic<long long> *
}
if (sf.tippecanoe_minzoom != -1 && z < sf.tippecanoe_minzoom) {
if (sf.tippecanoe_minzoom > z + 1) {
next_feature_state.minzoom_feature_pending = true;
}
continue;
}
if (sf.tippecanoe_maxzoom != -1 && z > sf.tippecanoe_maxzoom) {
@@ -2315,7 +2321,7 @@ long long write_tile(decompressor *geoms, std::atomic<long long> *geompos_in, ch
if (within[j]) {
long long estimated_complexity_out = geompos[j] - start_geompos[j];
if (dropping_by_rate) {
if (dropping_by_rate || next_feature_state.minzoom_feature_pending) {
// large enough to make it not try to stop early
estimated_complexity_out = 1LL << 32;
}