diff --git a/mb-util b/mb-util index d88a333..48a5717 100755 --- a/mb-util +++ b/mb-util @@ -48,7 +48,7 @@ if __name__ == '__main__': parser.add_option('--image_format', dest='format', help='''The format of the image tiles, either png, jpg, webp or pbf''', choices=['png', 'jpg', 'pbf', 'webp'], - default='png') + default=None) parser.add_option('--grid_callback', dest='callback', help='''Option to control JSONP callback for UTFGrid tiles. If grids are not ''' diff --git a/mbutil/util.py b/mbutil/util.py index 925995b..616848c 100644 --- a/mbutil/util.py +++ b/mbutil/util.py @@ -221,7 +221,7 @@ def disk_to_mbtiles(directory_path, mbtiles_file, **kwargs): optimize_connection(cur) mbtiles_setup(cur, use_deduplication=use_compression) - image_format = kwargs.get('format', 'png') + image_format = kwargs.get('format') or 'png' # Load metadata try: @@ -517,9 +517,9 @@ def mbtiles_to_disk(mbtiles_file, directory_path, **kwargs): os.makedirs(tile_dir) if kwargs.get('scheme') == 'wms': - tile = os.path.join(tile_dir, '%03d.%s' % (int(y) % 1000, kwargs.get('format', 'png'))) + tile = os.path.join(tile_dir, '%03d.%s' % (int(y) % 1000, kwargs.get('format') or 'png')) else: - tile = os.path.join(tile_dir, '%s.%s' % (y, kwargs.get('format', 'png'))) + tile = os.path.join(tile_dir, '%s.%s' % (y, kwargs.get('format') or 'png')) f = open(tile, 'wb') f.write(t[3]) @@ -668,7 +668,7 @@ def disk_to_pmtiles(directory_path, pmtiles_file, **kwargs): logger.info("Importing disk to PMTiles") logger.debug("%s --> %s" % (directory_path, pmtiles_file)) - image_format = kwargs.get('format', 'png') + image_format = kwargs.get('format') or 'png' stats = { 'total_tiles': 0, @@ -848,7 +848,7 @@ def pmtiles_metadata_to_disk(pmtiles_file, **kwargs): reader = Reader(MmapSource(f)) header = reader.header() metadata = reader.metadata() - pmtiles_header_to_metadata(header, metadata, kwargs.get('format', 'png')) + pmtiles_header_to_metadata(header, metadata, kwargs.get('format') or 'png') if not silent: logger.debug(json.dumps(metadata, indent=2)) @@ -867,10 +867,10 @@ def pmtiles_to_disk(pmtiles_file, directory_path, **kwargs): header = reader.header() metadata = reader.metadata() - file_ext = get_tile_ext(header, kwargs.get('format', 'png')) + file_ext = get_tile_ext(header, kwargs.get('format') or 'png') # Populate metadata with missing standard fields from the PMTiles header - pmtiles_header_to_metadata(header, metadata, kwargs.get('format', 'png')) + pmtiles_header_to_metadata(header, metadata, kwargs.get('format') or 'png') with open(os.path.join(directory_path, 'metadata.json'), 'w') as md_f: json.dump(metadata, md_f, indent=4) @@ -957,7 +957,7 @@ def mbtiles_to_pmtiles_cmd(mbtiles_file, pmtiles_file, **kwargs): mbtiles_metadata.pop('scheme', None) # Resolve format: user flag > metadata > default 'png' - image_format = kwargs.get('format', mbtiles_metadata.get('format', 'png')) + image_format = kwargs.get('format') or mbtiles_metadata.get('format', 'png') mbtiles_metadata['format'] = image_format is_pbf = image_format in ("pbf", "mvt") @@ -1034,9 +1034,9 @@ def pmtiles_to_mbtiles_cmd(pmtiles_file, mbtiles_file, **kwargs): header = reader.header() metadata = reader.metadata() - file_ext = get_tile_ext(header, kwargs.get('format', 'png')) + file_ext = get_tile_ext(header, kwargs.get('format') or 'png') - pmtiles_header_to_metadata(header, metadata, kwargs.get('format', 'png')) + pmtiles_header_to_metadata(header, metadata, kwargs.get('format') or 'png') # MBTiles stores tiles in TMS scheme metadata['scheme'] = 'tms' diff --git a/test/data/one_tile_webp.mbtiles b/test/data/one_tile_webp.mbtiles new file mode 100644 index 0000000..9037da6 Binary files /dev/null and b/test/data/one_tile_webp.mbtiles differ diff --git a/test/test_pmtiles.py b/test/test_pmtiles.py index d1573b9..33e88c1 100644 --- a/test/test_pmtiles.py +++ b/test/test_pmtiles.py @@ -16,6 +16,7 @@ OUTPUT_DIR = 'test/output' ONE_TILE_MBTILES = 'test/data/one_tile.mbtiles' +ONE_TILE_WEBP_MBTILES = 'test/data/one_tile_webp.mbtiles' def _output(*parts): @@ -207,5 +208,92 @@ def test_prepare_metadata_no_json_row_when_no_vector_layers(self): self.assertNotIn('json', packed) +class WebpFormatPreservationTestCase(unittest.TestCase): + """Regression tests for webp format being preserved through conversions. + + Previously, the --image_format CLI option defaulted to 'png', which meant + mbtiles_to_pmtiles_cmd always received format='png' in kwargs — overriding + the 'webp' value stored in the MBTiles metadata. + """ + + def setUp(self): + shutil.rmtree(OUTPUT_DIR, ignore_errors=True) + os.makedirs(OUTPUT_DIR, exist_ok=True) + self.webp_mbtiles = ONE_TILE_WEBP_MBTILES + + def tearDown(self): + shutil.rmtree(OUTPUT_DIR, ignore_errors=True) + + def _read_pmtiles_header_and_meta(self, path): + import sys + import os as _os + _pmtiles_path = _os.path.abspath(_os.path.join( + _os.path.dirname(__file__), '..', 'PMTiles', 'python', 'pmtiles')) + if _pmtiles_path not in sys.path: + sys.path.append(_pmtiles_path) + from pmtiles.reader import Reader, MmapSource + with open(path, 'rb') as f: + reader = Reader(MmapSource(f)) + return reader.header(), reader.metadata() + + def test_webp_format_preserved_in_pmtiles_header(self): + """mbtiles→pmtiles: tile_type in PMTiles header must reflect webp, not png.""" + import sys + import os as _os + _pmtiles_path = _os.path.abspath(_os.path.join( + _os.path.dirname(__file__), '..', 'PMTiles', 'python', 'pmtiles')) + if _pmtiles_path not in sys.path: + sys.path.append(_pmtiles_path) + from pmtiles.tile import TileType + + out = _output('webp.pmtiles') + # No format kwarg passed — should auto-detect 'webp' from metadata + mbtiles_to_pmtiles_cmd(self.webp_mbtiles, out, silent=True) + + header, _ = self._read_pmtiles_header_and_meta(out) + self.assertEqual(header['tile_type'], TileType.WEBP, + "PMTiles header tile_type should be WEBP, got %s" % header['tile_type']) + + def test_webp_format_preserved_in_pmtiles_metadata(self): + """mbtiles→pmtiles: 'format' key in PMTiles metadata must be 'webp'.""" + out = _output('webp.pmtiles') + mbtiles_to_pmtiles_cmd(self.webp_mbtiles, out, silent=True) + + _, meta = self._read_pmtiles_header_and_meta(out) + self.assertEqual(meta.get('format'), 'webp', + "PMTiles metadata format should be 'webp', got %r" % meta.get('format')) + + def test_explicit_format_flag_overrides_metadata(self): + """Explicit --image_format=png overrides source metadata format.""" + import sys + import os as _os + _pmtiles_path = _os.path.abspath(_os.path.join( + _os.path.dirname(__file__), '..', 'PMTiles', 'python', 'pmtiles')) + if _pmtiles_path not in sys.path: + sys.path.append(_pmtiles_path) + from pmtiles.tile import TileType + + out = _output('webp_forced_png.pmtiles') + mbtiles_to_pmtiles_cmd(self.webp_mbtiles, out, silent=True, format='png') + + header, meta = self._read_pmtiles_header_and_meta(out) + self.assertEqual(header['tile_type'], TileType.PNG) + self.assertEqual(meta.get('format'), 'png') + + def test_webp_format_survives_pmtiles_to_mbtiles_roundtrip(self): + """webp format survives mbtiles → pmtiles → mbtiles roundtrip.""" + pmtiles_path = _output('webp.pmtiles') + mbtiles_out = _output('webp_roundtrip.mbtiles') + + mbtiles_to_pmtiles_cmd(self.webp_mbtiles, pmtiles_path, silent=True) + pmtiles_to_mbtiles_cmd(pmtiles_path, mbtiles_out, silent=True) + + con = sqlite3.connect(mbtiles_out) + meta = dict(con.execute("SELECT name, value FROM metadata")) + con.close() + self.assertEqual(meta.get('format'), 'webp', + "format should be 'webp' after roundtrip, got %r" % meta.get('format')) + + if __name__ == '__main__': unittest.main()