はじめに
PMTilesはラスタータイルを単一のファイルにパッケージ化できるフォーマットです。ディレクトリにタイルを配置する従来の方法と比べてタイルの取り回しが簡単になるという利点があります。
しかし、単一ファイルに複数のタイルが格納されているため、タイルの一覧を取得したり、特定のタイルを抽出したい場合は自前で実装が必要です。
今回はPythonを使ってPMTilesファイルに含まれるタイル一覧の取得、および特定タイルの抽出方法を紹介します。
環境準備
Python環境にpmtiles, aiopmtilesをインストールします。なお、これらのパッケージは2025年2月時点では実験的なモジュールであることに注意してください。
pip install pmtiles git+http://github.com/developmentseed/aiopmtiles
タイル一覧の取得
実装
PMTilesファイルに含まれるタイルの情報を取得する実装です。
import asyncio
from aiopmtiles import Reader
from pmtiles.tile import Entry, deserialize_directory, tileid_to_zxy
async def extract_tile_entries(directory: list[Entry], header: dict, src: Reader) -> list[tuple[int, int, int, int]]:
"""PMTilesのディレクトリからタイル情報のリストを抽出します
Args:
directory (list): PMTilesのディレクトリエントリのリスト
header (dict): PMTilesのヘッダー情報
src (Reader): PMTilesリーダーオブジェクト
Returns:
list[tuple[int, int, int, int]]: タイル情報のリスト (z, x, y, length)
"""
tile_entries = []
for entry in directory:
if entry.run_length > 0:
for i in range(entry.run_length):
z, x, y = tileid_to_zxy(entry.tile_id + i)
tile_entries.append((z, x, y, entry.length))
else:
leaf_offset = header["leaf_directory_offset"] + entry.offset
leaf_values = await src._get(leaf_offset, entry.length - 1)
leaf_directory = deserialize_directory(leaf_values)
leaf_entries = await extract_tile_entries(leaf_directory, header, src)
tile_entries.extend(leaf_entries)
return tile_entries
async def list_tiles(pmtiles_url: str) -> list[tuple[int, int, int, int]]:
"""PMTilesファイルからタイル情報を取得します
Args:
pmtiles_url (str): PMTilesファイルのURL
Returns:
list[tuple[int, int, int, int]]: タイル情報のリスト (z, x, y, length)
"""
async with Reader(pmtiles_url) as src:
directory_values = await src._get(src._header["root_offset"], src._header["root_length"] - 1)
directory = deserialize_directory(directory_values)
return await extract_tile_entries(directory, src._header, src)
使用例
PMTilesファイルに含まれるすべてのタイル情報をCSVファイルに保存する例です。
import asyncio
import csv
async def main():
pmtiles_url = "path/to/your/file.pmtiles"
# タイルリストの取得
tile_entries = await list_tiles(pmtiles_url)
# CSVファイルに保存
output_path = "tiles.csv"
with open(output_path, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["z", "x", "y", "length"])
writer.writerows(tile_entries)
if __name__ == "__main__":
asyncio.run(main())
特定のタイルの抽出
実装
ズームレベル(z)とx/y座標を指定して、個別のタイルを取得する実装です。
async def get_tile(pmtiles_url: str, z: int, x: int, y: int) -> bytes | None:
"""PMTilesファイルから特定のタイルを取得します
Args:
pmtiles_url (str): PMTilesファイルのURL
z (int): ズームレベル
x (int): タイルのX座標
y (int): タイルのY座標
Returns:
bytes | None: タイルデータ。タイルが存在しない場合はNone
"""
async with Reader(pmtiles_url) as src:
return await src.get_tile(z, x, y)
使用例
指定した座標のタイルを取得してファイルに保存する例です。
import asyncio
from pathlib import Path
async def main():
pmtiles_url = "path/to/your/file.pmtiles"
# タイル座標の指定
zoom = 15
tile_x = 28376
tile_y = 13013
# 特定のタイルの取得
tile_data = await get_tile(pmtiles_url, z=zoom, x=tile_x, y=tile_y)
if tile_data is None:
print("タイルが存在しません")
return
# タイルの保存(PNG形式)
output_dir = "tiles"
output_path = Path(output_dir) / str(zoom) / str(tile_x)
output_path.mkdir(parents=True, exist_ok=True)
tile_path = output_path / f"{tile_y}.png"
with open(tile_path, "wb") as f:
f.write(tile_data)
if __name__ == "__main__":
asyncio.run(main())
参考文献
タイル一覧の取得の実装は、以下を参考にさせていただきました。