Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mb-util
Original file line number Diff line number Diff line change
Expand Up @@ -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 '''
Expand Down
20 changes: 10 additions & 10 deletions mbutil/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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))
Expand All @@ -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)
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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'
Expand Down
Binary file added test/data/one_tile_webp.mbtiles
Binary file not shown.
88 changes: 88 additions & 0 deletions test/test_pmtiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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()
Loading