From 76706a54c4c33ff0cab07730cb5810a208eb2218 Mon Sep 17 00:00:00 2001 From: Luka S Date: Mon, 29 Dec 2025 09:47:20 +0000 Subject: [PATCH] feat[rio-pmtiles]: allow min and/or max zoom to be omitted when specifying zoom levels (#623) * Allow min and/or max zoom to be omitted when specifying zoom levels * Fix help message for `--zoom-levels` * Add tests --- python/rio-pmtiles/rio_pmtiles/scripts/cli.py | 16 ++++--- python/rio-pmtiles/tests/test_cli.py | 43 ++++++++++++++++++- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/python/rio-pmtiles/rio_pmtiles/scripts/cli.py b/python/rio-pmtiles/rio_pmtiles/scripts/cli.py index dfc4ffd..884d606 100644 --- a/python/rio-pmtiles/rio_pmtiles/scripts/cli.py +++ b/python/rio-pmtiles/rio_pmtiles/scripts/cli.py @@ -150,10 +150,12 @@ def guess_maxzoom(crs, bounds, width, height, tile_size): "--zoom-levels", default=None, metavar="MIN..MAX", - help="A min...max range of export zoom levels. " - "The default zoom level " - "is the one at which the dataset is contained within " - "a single tile.", + help="A min..max range of export zoom levels. " + "The default min zoom level is 0 (dataset " + "contained in a single tile), and the default " + "max is calculated based on the available " + "detail in the dataset. Either or both of " + "min/max may be omitted.", ) @click.option( "-j", @@ -337,7 +339,11 @@ def pmtiles( # Resolve the minimum and maximum zoom levels for export. maxzoom_in_file = guess_maxzoom(src.crs, src.bounds, src.width, src.height, tile_size) if zoom_levels: - minzoom, maxzoom = map(int, zoom_levels.split("..")) + specified_levels = zoom_levels.split("..") + minzoom = specified_levels[0] + minzoom = 0 if minzoom == "" else int(minzoom) + maxzoom = specified_levels[1] + maxzoom = maxzoom_in_file if maxzoom == "" else int(maxzoom) else: minzoom = 0 maxzoom = maxzoom_in_file diff --git a/python/rio-pmtiles/tests/test_cli.py b/python/rio-pmtiles/tests/test_cli.py index 6a9f7f2..732b640 100644 --- a/python/rio-pmtiles/tests/test_cli.py +++ b/python/rio-pmtiles/tests/test_cli.py @@ -75,7 +75,8 @@ def test_export_tiles(tmpdir, data): src = MmapSource(f) assert len(list(all_tiles(src))) == 19 -def test_export_zoom(tmpdir, data): + +def test_export_zoom_both(tmpdir, data): inputfile = str(data.join("RGB.byte.tif")) outputfile = str(tmpdir.join("export.pmtiles")) runner = CliRunner() @@ -88,6 +89,46 @@ def test_export_zoom(tmpdir, data): src = MmapSource(f) assert len(list(all_tiles(src))) == 6 +def test_export_zoom_neither(tmpdir, data): + inputfile = str(data.join("RGB.byte.tif")) + outputfile = str(tmpdir.join("export.pmtiles")) + runner = CliRunner() + result = runner.invoke( + main_group, ["pmtiles", inputfile, outputfile, "--zoom-levels", ".."] + ) + assert result.exit_code == 0 + + with open(outputfile, "rb") as f: + src = MmapSource(f) + assert len(list(all_tiles(src))) == 19 + +def test_export_zoom_only_min(tmpdir, data): + inputfile = str(data.join("RGB.byte.tif")) + outputfile = str(tmpdir.join("export.pmtiles")) + runner = CliRunner() + result = runner.invoke( + main_group, ["pmtiles", inputfile, outputfile, "--zoom-levels", "6.."] + ) + assert result.exit_code == 0 + + with open(outputfile, "rb") as f: + src = MmapSource(f) + assert len(list(all_tiles(src))) == 12 + +def test_export_zoom_only_max(tmpdir, data): + inputfile = str(data.join("RGB.byte.tif")) + outputfile = str(tmpdir.join("export.pmtiles")) + runner = CliRunner() + result = runner.invoke( + main_group, ["pmtiles", inputfile, outputfile, "--zoom-levels", "..7"] + ) + assert result.exit_code == 0 + + with open(outputfile, "rb") as f: + src = MmapSource(f) + assert len(list(all_tiles(src))) == 13 + + def test_export_jobs(tmpdir, data): inputfile = str(data.join("RGB.byte.tif")) outputfile = str(tmpdir.join("export.pmtiles"))