rio-pmtiles improvements to defaults (#545)

* rio-pmtiles: show progress by default

* change --progress-bar to --silent [#338]

* rio-pmtiles: change defaults from JPEG/256/nearest to WEBP/512/bilinear [#338]

* rename title to name [#338]

* rio-pmtiles: automatic guessing of maxzoom [#338]

* determine maxzoom based on image dimensions and tile_size.

* rio-pmtiles: 1.0.0
This commit is contained in:
Brandon Liu
2025-04-02 12:21:01 +08:00
committed by GitHub
parent 29de7d2521
commit c793dc6435
3 changed files with 46 additions and 29 deletions

View File

@@ -1,5 +1,3 @@
"""rio-pmtiles package""" """rio-pmtiles package"""
import sys __version__ = "1.0.0"
__version__ = "0.0.6"

View File

@@ -35,6 +35,7 @@ from rio_pmtiles.worker import init_worker, process_tile
DEFAULT_NUM_WORKERS = None DEFAULT_NUM_WORKERS = None
RESAMPLING_METHODS = [method.name for method in Resampling] RESAMPLING_METHODS = [method.name for method in Resampling]
TILES_CRS = "EPSG:3857" TILES_CRS = "EPSG:3857"
WEBMERC_EXTENT = 40075016.68
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -96,6 +97,17 @@ def extract_features(ctx, param, value):
else: else:
return None return None
def guess_maxzoom(crs, bounds, width, height, tile_size):
(west, east), (south, north) = transform(
crs, "EPSG:3857", bounds[::2], bounds[1::2]
)
if east <= -WEBMERC_EXTENT/2:
east = -east
res_x = (east - west) / width
res_y = (north - south) / height
raster_resolution = min(res_x, res_y)
return math.ceil(math.log2(WEBMERC_EXTENT / (tile_size * raster_resolution)))
@click.command(short_help="Export a dataset to PMTiles.") @click.command(short_help="Export a dataset to PMTiles.")
@click.argument( @click.argument(
@@ -106,8 +118,8 @@ def extract_features(ctx, param, value):
metavar="INPUT [OUTPUT]", metavar="INPUT [OUTPUT]",
) )
@output_opt @output_opt
@click.option("--title", help="PMTiles dataset title.") @click.option("--name", help="PMTiles metadata name.")
@click.option("--description", help="PMTiles dataset description.") @click.option("--description", help="PMTiles metadata description.")
@click.option( @click.option(
"--overlay", "--overlay",
"layer_type", "layer_type",
@@ -123,12 +135,12 @@ def extract_features(ctx, param, value):
"--format", "--format",
"img_format", "img_format",
type=click.Choice(["JPEG", "PNG", "WEBP"]), type=click.Choice(["JPEG", "PNG", "WEBP"]),
default="JPEG", default="WEBP",
help="Tile image format.", help="Tile image format.",
) )
@click.option( @click.option(
"--tile-size", "--tile-size",
default=256, default=512,
show_default=True, show_default=True,
type=int, type=int,
help="Width and height of individual square tiles to create.", help="Width and height of individual square tiles to create.",
@@ -166,7 +178,7 @@ def extract_features(ctx, param, value):
@click.option( @click.option(
"--resampling", "--resampling",
type=click.Choice(RESAMPLING_METHODS), type=click.Choice(RESAMPLING_METHODS),
default="nearest", default="bilinear",
show_default=True, show_default=True,
help="Resampling method to use.", help="Resampling method to use.",
) )
@@ -175,7 +187,7 @@ def extract_features(ctx, param, value):
"--rgba", default=False, is_flag=True, help="Select RGBA output. For PNG or WEBP only." "--rgba", default=False, is_flag=True, help="Select RGBA output. For PNG or WEBP only."
) )
@click.option( @click.option(
"--progress-bar", "-#", default=False, is_flag=True, help="Don't display progress bar." "--silent", default=False, is_flag=True, help="Don't display progress bar."
) )
@click.option( @click.option(
"--cutline", "--cutline",
@@ -212,7 +224,7 @@ def pmtiles(
ctx, ctx,
files, files,
output, output,
title, name,
description, description,
layer_type, layer_type,
img_format, img_format,
@@ -223,7 +235,7 @@ def pmtiles(
dst_nodata, dst_nodata,
resampling, resampling,
rgba, rgba,
progress_bar, silent,
cutline, cutline,
open_options, open_options,
creation_options, creation_options,
@@ -251,7 +263,7 @@ def pmtiles(
nearest to the one at which one tile may contain the entire source nearest to the one at which one tile may contain the entire source
dataset. dataset.
If a title or description for the output file are not provided, If a name or description for the output file are not provided,
they will be taken from the input dataset's filename. they will be taken from the input dataset's filename.
This command is suited for small to medium (~1 GB) sized sources. This command is suited for small to medium (~1 GB) sized sources.
@@ -286,7 +298,7 @@ def pmtiles(
base_kwds.update(nodata=dst_nodata) base_kwds.update(nodata=dst_nodata)
# Name and description. # Name and description.
title = title or os.path.basename(src.name) name = name or os.path.basename(src.name)
description = description or src.name description = description or src.name
# Compute the geographic bounding box of the dataset. # Compute the geographic bounding box of the dataset.
@@ -323,10 +335,8 @@ def pmtiles(
if zoom_levels: if zoom_levels:
minzoom, maxzoom = map(int, zoom_levels.split("..")) minzoom, maxzoom = map(int, zoom_levels.split(".."))
else: else:
zw = int(round(math.log(360.0 / (east - west), 2.0))) minzoom = 0
zh = int(round(math.log(170.1022 / (north - south), 2.0))) maxzoom = guess_maxzoom(src.crs, src.bounds, src.width, src.height, tile_size)
minzoom = min(zw, zh)
maxzoom = max(zw, zh)
log.debug("Zoom range: %d..%d", minzoom, maxzoom) log.debug("Zoom range: %d..%d", minzoom, maxzoom)
@@ -364,7 +374,7 @@ def pmtiles(
outfile.write(b"\x00" * 16384) outfile.write(b"\x00" * 16384)
entries = [] entries = []
metadata = gzip.compress(json.dumps({'name':title,'type':layer_type,'description':description,'writer':f'rio-pmtiles {rio_pmtiles_version}'}).encode()) metadata = gzip.compress(json.dumps({'name':name,'type':layer_type,'description':description,'writer':f'rio-pmtiles {rio_pmtiles_version}'}).encode())
outfile.write(metadata) outfile.write(metadata)
header = {} header = {}
@@ -409,10 +419,10 @@ def pmtiles(
z, x, y = tileid_to_zxy(tile_id) z, x, y = tileid_to_zxy(tile_id)
yield mercantile.Tile(x,y,z) yield mercantile.Tile(x,y,z)
if progress_bar: if silent:
pbar = tqdm(total=len(tiles))
else:
pbar = None pbar = None
else:
pbar = tqdm(total=len(tiles))
tile_data_offset = 0 tile_data_offset = 0

View File

@@ -11,6 +11,7 @@ from rasterio.rio.main import main_group
from pmtiles.reader import Reader, MmapSource, all_tiles from pmtiles.reader import Reader, MmapSource, all_tiles
import rio_pmtiles.scripts.cli import rio_pmtiles.scripts.cli
from rio_pmtiles.scripts.cli import guess_maxzoom
from conftest import mock from conftest import mock
@@ -72,7 +73,7 @@ def test_export_tiles(tmpdir, data):
with open(outputfile, 'rb') as f: with open(outputfile, 'rb') as f:
src = MmapSource(f) src = MmapSource(f)
assert len(list(all_tiles(src))) == 6 assert len(list(all_tiles(src))) == 19
def test_export_zoom(tmpdir, data): def test_export_zoom(tmpdir, data):
inputfile = str(data.join("RGB.byte.tif")) inputfile = str(data.join("RGB.byte.tif"))
@@ -96,7 +97,7 @@ def test_export_jobs(tmpdir, data):
with open(outputfile, 'rb') as f: with open(outputfile, 'rb') as f:
src = MmapSource(f) src = MmapSource(f)
assert len(list(all_tiles(src))) == 6 assert len(list(all_tiles(src))) == 19
def test_export_src_nodata(tmpdir, data): def test_export_src_nodata(tmpdir, data):
@@ -111,7 +112,7 @@ def test_export_src_nodata(tmpdir, data):
with open(outputfile, 'rb') as f: with open(outputfile, 'rb') as f:
src = MmapSource(f) src = MmapSource(f)
assert len(list(all_tiles(src))) == 6 assert len(list(all_tiles(src))) == 19
def test_export_bilinear(tmpdir, data): def test_export_bilinear(tmpdir, data):
@@ -125,7 +126,7 @@ def test_export_bilinear(tmpdir, data):
with open(outputfile, 'rb') as f: with open(outputfile, 'rb') as f:
src = MmapSource(f) src = MmapSource(f)
assert len(list(all_tiles(src))) == 6 assert len(list(all_tiles(src))) == 19
def test_skip_empty(tmpdir, empty_data): def test_skip_empty(tmpdir, empty_data):
@@ -153,7 +154,7 @@ def test_include_empty(tmpdir, empty_data):
with open(outputfile, 'rb') as f: with open(outputfile, 'rb') as f:
src = MmapSource(f) src = MmapSource(f)
assert len(list(all_tiles(src))) == 6 assert len(list(all_tiles(src))) == 19
def test_invalid_format_rgba(tmpdir, empty_data): def test_invalid_format_rgba(tmpdir, empty_data):
@@ -217,7 +218,6 @@ def test_progress_bar(tmpdir, data, filename):
main_group, main_group,
[ [
"pmtiles", "pmtiles",
"-#",
"--zoom-levels", "--zoom-levels",
"4..11", "4..11",
"--rgba", "--rgba",
@@ -249,7 +249,6 @@ def test_cutline_progress_bar(tmpdir, data, rgba_cutline_path, filename):
main_group, main_group,
[ [
"pmtiles", "pmtiles",
"-#",
"--zoom-levels", "--zoom-levels",
"4..11", "4..11",
"--rgba", "--rgba",
@@ -275,7 +274,6 @@ def test_invalid_cutline(tmpdir, data, rgba_points_path, filename):
main_group, main_group,
[ [
"pmtiles", "pmtiles",
"-#",
"--zoom-levels", "--zoom-levels",
"4..11", "4..11",
"--rgba", "--rgba",
@@ -288,3 +286,14 @@ def test_invalid_cutline(tmpdir, data, rgba_points_path, filename):
], ],
) )
assert result.exit_code == 1 assert result.exit_code == 1
@pytest.mark.parametrize(
"crs,bounds,width,height,tile_size,maxzoom",
[
("EPSG:4326",[-180,-90,180,90],256,256,256,0),
("EPSG:4326",[-180,-90,180,90],512,512,256,1),
("EPSG:4326",[-180,-90,180.00000000007202,90],512,1,256,1),
],
)
def test_guess_maxzoom(crs, bounds, width, height, tile_size, maxzoom):
assert guess_maxzoom(crs, bounds, width, height, tile_size) == maxzoom