Repository: mapbox/rio-mbtiles

Issue:
Add support for GDAL creation options for output tiles 
Output tiles are written by GDAL, remember.

Comments:

Patch:
diff --git a/CHANGES.txt b/CHANGES.txt
index 151e1d8..057614e 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,13 @@
 Changes
 =======
 
+1.5.1 (2021-02-02)
+------------------
+
+- Add --co (creation) options for fine control over quality of tiles using any
+  of a format's valid GDAL creation options (#73).
+- Add support for WebP tiles (#72).
+
 1.5.0 (2020-10-30)
 ------------------
 
diff --git a/README.rst b/README.rst
index 2020e36..16c7395 100644
--- a/README.rst
+++ b/README.rst
@@ -6,8 +6,8 @@ rio-mbtiles
 
 A plugin for the
 `Rasterio CLI <https://github.com/mapbox/rasterio/blob/master/docs/cli.rst>`__
-that exports a raster dataset to the MBTiles (version 1.1) format. Features
-include automatic reprojection and parallel processing.
+that exports a raster dataset to the MBTiles (version 1.3) format. Features
+include automatic reprojection and concurrent tile generation.
 
 Usage
 -----
@@ -17,15 +17,21 @@ Usage
     $ rio mbtiles --help
     Usage: rio mbtiles [OPTIONS] INPUT [OUTPUT]
 
-      Export a dataset to MBTiles (version 1.1) in a SQLite file.
+      Export a dataset to MBTiles (version 1.3) in a SQLite file.
 
       The input dataset may have any coordinate reference system. It must have
       at least three bands, which will be become the red, blue, and green bands
       of the output image tiles.
 
       An optional fourth alpha band may be copied to the output tiles by using
-      the --rgba option in combination with the PNG format. This option requires
-      that the input dataset has at least 4 bands.
+      the --rgba option in combination with the PNG or WEBP formats. This option
+      requires that the input dataset has at least 4 bands.
+
+      The default quality for JPEG and WEBP output (possible range: 10-100) is
+      75. This value can be changed with the use of the QUALITY creation option,
+      e.g. `--co QUALITY=90`.  The default zlib compression level for PNG output
+      (possible range: 1-9) is 6. This value can be changed like `--co
+      ZLEVEL=8`.  Lossless WEBP can be chosen with `--co LOSSLESS=TRUE`.
 
       If no zoom levels are specified, the defaults are the zoom levels nearest
       to the one at which one tile may contain the entire source dataset.
@@ -48,7 +54,7 @@ Usage
       --description TEXT              MBTiles dataset description.
       --overlay                       Export as an overlay (the default).
       --baselayer                     Export as a base layer.
-      -f, --format [JPEG|PNG]         Tile image format.
+      -f, --format [JPEG|PNG|WEBP]    Tile image format.
       --tile-size INTEGER             Width and height of individual square tiles
                                       to create.  [default: 256]
 
@@ -69,7 +75,7 @@ Usage
                                       nearest]
 
       --version                       Show the version and exit.
-      --rgba                          Select RGBA output. For PNG only.
+      --rgba                          Select RGBA output. For PNG or WEBP only.
       --implementation [cf|mp]        Concurrency implementation. Use
                                       concurrent.futures (cf) or multiprocessing
                                       (mp).
@@ -85,6 +91,10 @@ Usage
                                       GDAL format driver documentation for more
                                       information.
 
+      --co, --profile NAME=VALUE      Driver specific creation options. See the
+                                      documentation for the selected output driver
+                                      for more information.
+
       --wo NAME=VALUE                 See the GDAL warp options documentation for
                                       more information.
 
diff --git a/mbtiles/__init__.py b/mbtiles/__init__.py
index 88ac678..a8fa51f 100644
--- a/mbtiles/__init__.py
+++ b/mbtiles/__init__.py
@@ -3,7 +3,7 @@
 import sys
 import warnings
 
-__version__ = "1.5.0"
+__version__ = "1.5.1"
 
 if sys.version_info < (3, 7):
     warnings.warn(
diff --git a/mbtiles/cf.py b/mbtiles/cf.py
index 9a2d306..89e2ff4 100644
--- a/mbtiles/cf.py
+++ b/mbtiles/cf.py
@@ -25,13 +25,14 @@ def process_tiles(
     progress_bar=None,
     open_options=None,
     warp_options=None,
+    creation_options=None,
 ):
     """Warp imagery into tiles and commit to mbtiles database.
     """
     with concurrent.futures.ProcessPoolExecutor(
         max_workers=num_workers,
         initializer=init_worker,
-        initargs=(inputfile, base_kwds, resampling, open_options, warp_options),
+        initargs=(inputfile, base_kwds, resampling, open_options, warp_options, creation_options),
     ) as executor:
         group = islice(tiles, BATCH_SIZE)
         futures = {executor.submit(process_tile, tile) for tile in group}
diff --git a/mbtiles/mp.py b/mbtiles/mp.py
index 4c3c48d..8c0de7d 100644
--- a/mbtiles/mp.py
+++ b/mbtiles/mp.py
@@ -29,13 +29,14 @@ def process_tiles(
     progress_bar=None,
     open_options=None,
     warp_options=None,
+    creation_options=None,
 ):
     """Warp raster into tiles and commit tiles to mbtiles database.
     """
     pool = Pool(
         num_workers,
         init_worker,
-        (inputfile, base_kwds, resampling, open_options, warp_options),
+        (inputfile, base_kwds, resampling, open_options, warp_options, creation_options),
         100 * BATCH_SIZE,
     )
 
diff --git a/mbtiles/scripts/cli.py b/mbtiles/scripts/cli.py
index ba579bb..06b4a97 100644
--- a/mbtiles/scripts/cli.py
+++ b/mbtiles/scripts/cli.py
@@ -13,7 +13,7 @@ import mercantile
 import rasterio
 from rasterio.enums import Resampling
 from rasterio.errors import FileOverwriteError
-from rasterio.rio.options import output_opt, _cb_key_val
+from rasterio.rio.options import creation_options, output_opt, _cb_key_val
 from rasterio.warp import transform, transform_geom
 import shapely.affinity
 from shapely.geometry import mapping, shape
@@ -180,7 +180,7 @@ def extract_features(ctx, param, value):
 )
 @click.version_option(version=mbtiles_version, message="%(version)s")
 @click.option(
-    "--rgba", default=False, is_flag=True, help="Select RGBA output. For PNG only."
+    "--rgba", default=False, is_flag=True, help="Select RGBA output. For PNG or WEBP only."
 )
 @click.option(
     "--implementation",
@@ -208,6 +208,7 @@ def extract_features(ctx, param, value):
     callback=_cb_key_val,
     help="Format driver-specific options to be used when accessing the input dataset. See the GDAL format driver documentation for more information.",
 )
+@creation_options
 @click.option(
     "--wo",
     "warp_options",
@@ -239,17 +240,25 @@ def mbtiles(
     covers,
     cutline,
     open_options,
+    creation_options,
     warp_options,
 ):
-    """Export a dataset to MBTiles (version 1.1) in a SQLite file.
+    """Export a dataset to MBTiles (version 1.3) in a SQLite file.
 
     The input dataset may have any coordinate reference system. It must
     have at least three bands, which will be become the red, blue, and
     green bands of the output image tiles.
 
     An optional fourth alpha band may be copied to the output tiles by
-    using the --rgba option in combination with the PNG format. This
-    option requires that the input dataset has at least 4 bands.
+    using the --rgba option in combination with the PNG or WEBP formats.
+    This option requires that the input dataset has at least 4 bands.
+
+    The default quality for JPEG and WEBP output (possible range:
+    10-100) is 75. This value can be changed with the use of the QUALITY
+    creation option, e.g. `--co QUALITY=90`.  The default zlib
+    compression level for PNG output (possible range: 1-9) is 6. This
+    value can be changed like `--co ZLEVEL=8`.  Lossless WEBP can be
+    chosen with `--co LOSSLESS=TRUE`.
 
     If no zoom levels are specified, the defaults are the zoom levels
     nearest to the one at which one tile may contain the entire source
@@ -576,6 +585,7 @@ def mbtiles(
                 image_dump=image_dump,
                 progress_bar=pbar,
                 open_options=open_options,
+                creation_options=creation_options,
                 warp_options=warp_options,
             )
 
diff --git a/mbtiles/worker.py b/mbtiles/worker.py
index 9a6568d..c0099ca 100644
--- a/mbtiles/worker.py
+++ b/mbtiles/worker.py
@@ -17,13 +17,14 @@ TILES_CRS = "EPSG:3857"
 log = logging.getLogger(__name__)
 
 
-def init_worker(path, profile, resampling_method, open_opts, warp_opts):
-    global base_kwds, filename, resampling, open_options, warp_options
+def init_worker(path, profile, resampling_method, open_opts=None, warp_opts=None, creation_opts=None):
+    global base_kwds, filename, resampling, open_options, warp_options, creation_options
     resampling = Resampling[resampling_method]
     base_kwds = profile.copy()
     filename = path
     open_options = open_opts.copy() if open_opts is not None else {}
     warp_options = warp_opts.copy() if warp_opts is not None else {}
+    creation_options = creation_opts.copy() if creation_opts is not None else {}
 
 
 def process_tile(tile):
@@ -44,7 +45,7 @@ def process_tile(tile):
         Image bytes corresponding to the tile.
 
     """
-    global base_kwds, resampling, filename, open_options, warp_options
+    global base_kwds, resampling, filename, open_options, warp_options, creation_options
 
     with rasterio.open(filename, **open_options) as src:
 
@@ -53,6 +54,7 @@ def process_tile(tile):
         lrx, lry = mercantile.xy(*mercantile.ul(tile.x + 1, tile.y + 1, tile.z))
 
         kwds = base_kwds.copy()
+        kwds.update(**creation_options)
         kwds["transform"] = transform_from_bounds(
             ulx, lry, lrx, uly, kwds["width"], kwds["height"]
         )


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 1d50ad3..02a4c2f 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -18,7 +18,7 @@ def test_cli_help():
     runner = CliRunner()
     result = runner.invoke(main_group, ["mbtiles", "--help"])
     assert result.exit_code == 0
-    assert "Export a dataset to MBTiles (version 1.1)" in result.output
+    assert "Export a dataset to MBTiles (version 1.3)" in result.output
 
 
 @pytest.mark.skipif("sys.version_info >= (3, 7)", reason="Test requires Python < 3.7")