Variable-depth pyramids: don't prune children while a minzoom-gated feature is still pending (#399)

Don't prune variable-depth children while a minzoom-gated feature is pending

The minzoom_feature_pending flag from #397 keeps a variable-depth pyramid
subdividing until explicit per-feature minzooms are satisfied, but two gaps
let features still be dropped:

The flag was only set when tippecanoe_minzoom > z + 1, so a feature whose
minzoom is exactly z + 1 never marked the tile pending, even though a leaf
at z carries only z-visible content.

The early-stop commit never consulted the flag: a tile that succeeded in
stopping early inserted itself into skip_children_out unconditionally,
pruning the children the pending feature needed. The flag only inflated
estimated_complexity_out, which the pruning ignores.

Set the flag for any feature excluded below its minzoom, include it in the
early-stop veto, and skip child pruning while it is set. Adds a fixture
covering the minzoom == z + 1 boundary; make test passes with no diffs to
existing fixtures.
This commit is contained in:
Brandon Keepers
2026-07-27 09:02:52 -07:00
committed by GitHub
parent 0dc1e00eee
commit 0badb242be
3 changed files with 38 additions and 7 deletions
+8 -7
View File
@@ -1205,9 +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;
}
// a leaf at z carries only z-visible content, so an excluded feature at
// any deeper minzoom (even z + 1) must block leafing here
next_feature_state.minzoom_feature_pending = true;
continue;
}
if (sf.tippecanoe_maxzoom != -1 && z > sf.tippecanoe_maxzoom) {
@@ -2684,7 +2684,7 @@ long long write_tile(decompressor *geoms, std::atomic<long long> *geompos_in, ch
oprogress = progress;
}
if (trying_to_stop_early && line_detail == first_detail && !can_stop_early) {
if (trying_to_stop_early && line_detail == first_detail && (!can_stop_early || next_feature_state.minzoom_feature_pending)) {
// didn't work, try a lower detail
continue;
}
@@ -3036,9 +3036,10 @@ long long write_tile(decompressor *geoms, std::atomic<long long> *geompos_in, ch
exit(EXIT_PTHREAD);
}
if (trying_to_stop_early && line_detail == first_detail) {
// We succeeded in stopping early.
// Prune the child tiles.
if (trying_to_stop_early && line_detail == first_detail &&
!next_feature_state.minzoom_feature_pending) {
// We succeeded in stopping early (and no excluded feature is
// waiting for a deeper zoom). Prune the child tiles.
strategy.truncated_zooms++;
skip_children_out.insert(zxy(z, tx, ty));