これは DuckDB Advent Calendar 2025 の21日目の記事です。
概要
GeoParquetをブラウザ上で読み込み、DuckDB-WASMを使って動的にベクトルタイル(MVT)に変換してMapLibre GL JSで表示するビューワーを作成しました。

上記のテストデータとして、FlateauのGeoParquetを利用していますので、試される場合はこちらを利用すると間違いないです。
構成
- データソース: GeoParquetファイル(ローカルファイル または URL)
- 処理エンジン: DuckDB-WASM
- 表示: MapLibre GL JS
ポイント
ほぼ以下の記事の内容をそのまま使わせていただいております。
GeoParquet対応にしました、というくらい。
-
DuckDB-WASMとSpatial拡張のセットアップ
まずはDuckDBを初期化し、地理空間情報を扱うための spatial 拡張をロードします。import * as duckdb from '@duckdb/duckdb-wasm'; // ... 初期化コード ... const conn = await db.connect(); await conn.query(`INSTALL spatial; LOAD spatial;`); -
MapLibreのカスタムプロトコル (duckdb://)
MapLibreにはaddProtocolという強力な機能があり、独自のURLスキームに対する処理を記述できます。これを利用して、duckdb://table_name/z/x/yというリクエストをインターセプトします。maplibregl.addProtocol('duckdb', async (params) => { // URLから z, x, y をパース const [layer, z, x, y] = parseUrl(params.url); // SQLを実行してMVTバイナリを取得 const mvtBuffer = await generateMvt(layer, z, x, y); return { data: mvtBuffer }; }); -
SQLによる動的MVT生成
DuckDBのST_AsMVTとST_AsMVTGeom関数を使います。WITH mvt_data AS ( SELECT ST_AsMVTGeom( ST_Transform(geom, 'EPSG:4326', 'EPSG:3857'), -- Webメルカトルへの変換 ST_TileEnvelope(${z}, ${x}, ${y}), -- タイル範囲 4096, 0, true -- バッファ設定など ) AS mvt_geom, prop1, prop2 -- 属性カラム FROM table_name WHERE -- タイル範囲に含まれるデータのみ抽出(インデックスが効く) ST_Intersects( ST_Transform(geom, 'EPSG:4326', 'EPSG:3857'), ST_TileEnvelope(${z}, ${x}, ${y}) ) ) SELECT ST_AsMVT(mvt_data) FROM mvt_data;
躓いたところ
- "This geometry seems to be written with a newer version..."
GeoParquetファイルを作成したライブラリ(GDALやPythonのpyarrow/duckdb等)と、ブラウザ上のDuckDB-WASMのSpatial拡張のバージョンが合わず、このエラーが出ました。
解決策: @duckdb/duckdb-wasm のバージョンを最新の開発版に上げることで解決しました。今回は 1.33.1-dev4.0 を使用しています。 - MVT生成時の型エラー
DATE 型や DECIMAL 型が含まれているとエラーになりました。
解決策: SQL構築時にカラムの型をチェックし、MVTがサポートしていない型は明示的にキャストしました。-- 例: DATE型の場合は文字列に、DECIMAL型はDOUBLEに変換 CASE WHEN type = 'DATE' THEN CAST(col AS VARCHAR) WHEN type = 'DECIMAL' THEN CAST(col AS DOUBLE) ELSE col END - 空のジオメトリによるエラー
タイルの境界で切り取られた結果、ジオメトリが空(Empty)やNULLになることがあり、それがST_AsMVTをクラッシュさせることがありました。
解決策:ST_AsMVTGeomの後段でWHERE mvt_geom IS NOT NULLおよびNOT ST_IsEmpty(mvt_geom)のフィルタを入れることで安定しました。
まとめと感想
GeoParquetをクラウドのアクセスできるところに置いておくだけで、ベクトルタイルとしてMapビューワーに表示できるという手軽さが良いなと思いました。
ちなみに、ポリゴンデータだと動的なタイル生成に時間がかかりすぎるかなという印象です。このあたり、おそらく解消法がありそうなのですが、そこまで深く追求できませんでした。
ポリゴンデータじゃなくてポイントデータならもっと速いのかも?とは思いました。
まだできたばかりの機能ですので、今後良くなっていくだろうとは思います。