PostgreSQLとPostGISを活用したMapbox Vector Tiles (MVT) の動的生成手法

ベクトルタイル(MVT)アーキテクチャの概要

Mapbox Vector Tiles (MVT) は、地理空間データを効率的に配信するためのフォーマットであり、従来のラスター画像とは異なり、クライアント側での動的なスタイリングや高解像度でのレンダリングを可能にします。PostgreSQLとPostGISの拡張機能を利用することで、静的なファイルを作成することなく、データベース上のデータをリアルタイムにMVT形式へ変換して配信するサーバーレスなアーキテクチャを構築できます。

MVT生成におけるPostGIS関数の役割

MVTを生成するプロセスは、座標変換、タイル境界の計算、クリッピング、そしてバイナリ変換のステップで構成されます。以下にそれぞれの処理で使用されるPostGISの主要な関数とその役割を解説します。

座標系の変換と正規化

Webマップの標準的な投影法であるWebメルカトル(EPSG:3857)へ変換を行います。生データが緯度経度(EPSG:4326)である場合、以下のように変換を行います。

ST_Transform(ST_SetSRID(geo_shape, 4326), 3857)

ここでは、まず ST_SetSRID で幾何形状に正しい空間参照IDを付与し、ST_Transform を用いてWebメルカトル投影へ変換します。この変換は、MVTの座標空間と地理空間を整合させるために不可欠です。

タイル領域の定義

特定のズームレベル(Z)とタイル座標(X, Y)に基づき、そのタイルがカバーする地理的な範囲(バウンディングボックス)を計算します。

ST_TileEnvelope(z_level, x_index, y_index)
  • z_level: ズームレベルを指定します(例: 14)。
  • x_index, y_index: タイルグリッド上のX座標およびY座標を指定します。
  • この関数は、Webメルカトル投影における正確な矩形領域(Box2D)を返します。

ジオメトリのタイル座標空間へのマッピング

地理的なジオメトリをタイル内のピクセル座標(通常は0〜4096の範囲)に変換し、タイル境界でクリッピングを行います。ST_AsMVTGeom がこの役割を担います。

ST_AsMVTGeom(
    target_geom,
    tile_boundary,
    4096,
    64
)
  • target_geom: Webメルカトル投影へ変換済みのジオメトリ。
  • tile_boundary: ST_TileEnvelopeで求めたタイルの境界。
  • 4096: MVT仕様で定義されているエクステント(解像度)。
  • 64: バッファサイズ。タイルの端でレンダリングされる要素が切れないようにするための余白です。

空間インデックスを用いた効率的なフィルタリング

データベースへの負荷を軽減するため、タイルに関連するデータのみを抽出する必要があります。ST_Intersects を用いて、タイル境界と交差するデータのみを対象とします。

ST_Intersects(
    ST_Transform(ST_SetSRID(geo_shape, 4326), 3857),
    ST_TileEnvelope(z_level, x_index, y_index)
)

最適化されたSQL実装例

以下のクエリは、共通テーブル式(CTE)を用いて可読性とパフォーマンスを向上させた実装例です。ここでは、インフラ施設データを例に、属性フィルタリングと空間フィルタリングを組み合わせています。

WITH target_tile AS (
    -- タイルの境界を一度計算して再利用
    SELECT ST_TileEnvelope(14, 12005, 6032) AS bounds
)
SELECT ST_AsMVT(q, 'infrastructure_layer', 4096, 'geom') AS mvt_data
FROM (
    SELECT
        asset_id,
        category,
        ST_AsMVTGeom(
            ST_Transform(ST_SetSRID(geo_shape, 4326), 3857),
            t.bounds,
            4096,
            256  -- 描画品質向上のためバッファを確保
        ) AS geom
    FROM
        public.infrastructure_assets,
        target_tile t
    WHERE
        -- 空間フィルタリング
        ST_Intersects(
            ST_Transform(ST_SetSRID(geo_shape, 4326), 3857),
            t.bounds
        )
        AND
        -- 属性フィルタリングの例
        status = 'active'
) AS q;

パフォーマンスチューニングのポイント

大規模なデータセットを扱う場合、以下の対策によりレスポンスタイムを大幅に短縮できます。

  • GISTインデックスの作成: ジオメトリカラムに対してGISTインデックスを作成し、バウンディングボックス検索を高速化します。
    CREATE INDEX idx_infra_geo ON public.infrastructure_assets USING GIST (geo_shape);
  • ジオメトリの簡略化: 低ズームレベルでは詳細な形状は不要なため、ST_SimplifyVW (Visvalingam-Whyattアルゴリズム) 等を用いて頂点数を削減します。
  • 分割による検索効率化: 複雑なポリゴンがある場合、ST_Subdivide を使用してジオメトリを小さなパーツに分割し、インデックスのヒット率を向上させます。
  • 接続プールの導入: PgBouncerなどを導入し、頻繁な接続・切断によるオーバーヘッドを削減します。

フロントエンドへの統合

生成されたMVTデータは、バイナリ形式(.pbf)で配信され、Mapbox GL JSやLeafletなどのクライアント側ライブラリで消費されます。

Mapbox GL JS での表示例

map.addSource('vector-source', {
    type: 'vector',
    tiles: ['https://api.example.com/tiles/{z}/{x}/{y}.pbf'],
    minzoom: 10,
    maxzoom: 16
});

map.addLayer({
    'id': 'infra-pois',
    'type': 'circle',
    'source': 'vector-source',
    'source-layer': 'infrastructure_layer',
    'paint': {
        'circle-radius': 6,
        'circle-color': [
            'match',
            ['get', 'category'],
            'power', '#FF0000',
            'water', '#0000FF',
            '#888888'
        ]
    }
});

Leaflet (VectorGrid プラグイン) での表示例

L.vectorGrid.protobuf("https://api.example.com/tiles/{z}/{x}/{y}.pbf", {
    rendererFactory: L.canvas.tile,
    vectorTileLayerStyles: {
        infrastructure_layer: {
            weight: 1,
            fillColor: '#00ff00',
            fillOpacity: 0.5,
            color: '#000000'
        }
    }
}).addTo(map);

タグ: PostgreSQL PostGIS Mapbox Vector Tiles WebGIS SQL

7月19日 20:37 投稿