From 0dc1e00eee8efa68b7d1a1834a59910dedddf903 Mon Sep 17 00:00:00 2001 From: Brandon Keepers Date: Wed, 22 Jul 2026 20:00:08 -0400 Subject: [PATCH] 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. --- tile.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tile.cpp b/tile.cpp index a4f12c75..8f9b1bca 100644 --- a/tile.cpp +++ b/tile.cpp @@ -1097,6 +1097,9 @@ static bool skip_next_feature(decompressor *geoms, std::atomic *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 * } 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 *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; }