GIS開発研究所 > PostGISからベクトルタイルを配信する方法|ST_AsMVTとMapLibre

PostGISからベクトルタイルを配信する方法|ST_AsMVTとMapLibre

2026年7月22日

7分で読めます

PostGISのST_AsMVTとST_AsMVTGeomを使い、MapLibreへMVTを配信する構成を解説します。z/x/yからタイル範囲を作るSQL、座標変換、GiSTでの空間絞り込み、APIレスポンス、キャッシュ、本番運用の注意点まで紹介します。

PostGIS
MVT
MapLibre
ベクトルタイル

PostGISに保存した設備、道路、行政界などをWeb地図へ表示するとき、GeoJSONで全件を返す構成はデータ増加に伴って重くなります。PostGISのST_AsMVTを使うと、現在のズームと表示範囲に必要な地物をMVTとして生成できます。

この記事では、z/x/yを受け取るタイルAPIを想定し、PostGISでMVTを生成してMapLibre GL JSへ表示するまでの基本構成を説明します。

先に結論

実装では、次の4点を分けて考えます。

  1. ST_TileEnvelopeで対象タイルの範囲を作る
  2. GiST空間インデックスを使って対象地物を絞る
  3. ST_AsMVTGeomでタイル座標へ変換する
  4. ST_AsMVTでバイナリのMVTを生成する

表示用の座標系はWebメルカトルのEPSG:3857を使います。元データがEPSG:4326の場合は、比較用の範囲とMVT生成用の形状を適切に変換します。

MVT配信の全体構成

MapLibre GL JS
  ↓ GET /tiles/facilities/{z}/{x}/{y}.mvt
タイルAPI
  ↓ z/x/yをSQLパラメータとして渡す
PostGIS
  ↓ bytea形式のMVT
タイルAPI
  ↓ Content-Typeを付けて返す
MapLibre GL JS

SQL文字列へz/x/yを直接連結せず、整数として検証したうえでプレースホルダーへ渡します。想定外のズームや座標を拒否することで、SQLインジェクションだけでなく過大な処理も防げます。

PostGISでMVTを生成するSQL

次は、facilitiesテーブルからMVTを生成する例です。geomはEPSG:4326を想定しています。

WITH bounds AS (
  SELECT ST_TileEnvelope($1, $2, $3) AS geom
),
mvtgeom AS (
  SELECT
    f.id,
    f.name,
    f.category,
    ST_AsMVTGeom(
      ST_Transform(f.geom, 3857),
      bounds.geom,
      extent => 4096,
      buffer => 64
    ) AS geom
  FROM facilities AS f
  CROSS JOIN bounds
  WHERE f.geom && ST_Transform(
    ST_TileEnvelope($1, $2, $3, margin => 64.0 / 4096),
    ST_SRID(f.geom)
  )
)
SELECT ST_AsMVT(mvtgeom.*, 'facilities', 4096, 'geom', 'id')
FROM mvtgeom;

$1$2$3には順にzxyを渡します。

ST_TileEnvelope

ST_TileEnvelope(z, x, y)は、指定したXYZタイルの範囲をEPSG:3857で返します。marginを付けると、ラベルや線がタイル境界で切れにくいよう、検索範囲を少し広げられます。

ST_AsMVTGeom

ST_AsMVTGeomは、地物の座標をMVTのタイル座標へ変換します。

  • extent: タイル内の座標解像度。一般的には4096
  • buffer: タイル外側へ含める余白
  • clip_geom: 既定ではタイル範囲へクリップ

元データがEPSG:4326の場合、ST_Transform(f.geom, 3857)でWebメルカトルへ変換してから渡します。

ST_AsMVT

ST_AsMVTは行の集合をMVTのバイナリへ変換します。第2引数のfacilitiesはレイヤー名です。MapLibre側のsource-layerと一致させます。

空間インデックスを確認する

タイル範囲で地物を絞る列には、GiST空間インデックスを作成します。

CREATE INDEX facilities_geom_gix
ON facilities
USING GIST (geom);

ANALYZE facilities;

実行計画では、対象タイルの地物数が少ないときにインデックスが使われているか確認します。

EXPLAIN (ANALYZE, BUFFERS)
SELECT id
FROM facilities
WHERE geom && ST_Transform(
  ST_TileEnvelope(12, 3637, 1612),
  ST_SRID(geom)
);

全件をST_Transformしてから比較する書き方は、元のgeomに作ったインデックスを使いにくくします。検索範囲を元データのSRIDへ変換し、インデックス列はそのまま比較するのが基本です。

タイルAPIから返す内容

SQLの結果はbyteaです。APIではテキストやJSONへ変換せず、そのままバイナリとして返します。

Content-Type: application/vnd.mapbox-vector-tile
Cache-Control: public, max-age=3600

地物がないタイルは、空のレスポンスになることがあります。APIフレームワークがnullをJSONへ変換しないように確認します。

タイルのURLが同じでもデータが更新される場合は、キャッシュ期限を短くするか、更新時にキャッシュを無効化します。日次更新なら長め、リアルタイム更新なら短めなど、レイヤーごとに方針を分けます。

MapLibreで表示する

MapLibre GL JSでは、vector sourceにタイルURLを指定します。

map.addSource("facilities", {
  type: "vector",
  tiles: [
    "https://example.com/tiles/facilities/{z}/{x}/{y}.mvt",
  ],
  minzoom: 5,
  maxzoom: 16,
});

map.addLayer({
  id: "facility-points",
  type: "circle",
  source: "facilities",
  "source-layer": "facilities",
  paint: {
    "circle-radius": 5,
    "circle-color": "#1f5b45",
  },
});

何も表示されない場合は、最初にsource-layerがSQLのレイヤー名と一致しているか確認します。次に、タイルAPIが200を返しているか、レスポンスサイズが0バイトではないかを確認します。

表示されないときの切り分け

SRIDが一致していない

ST_TileEnvelopeはEPSG:3857です。ST_AsMVTGeomへ渡す形状もEPSG:3857にそろえます。一方、絞り込み条件はインデックスを使うため、タイル範囲を元データのSRIDへ変換します。

SELECT ST_SRID(geom), COUNT(*)
FROM facilities
GROUP BY ST_SRID(geom);

SRIDが0や混在している場合は、タイル実装より先にデータを整えます。

z/x/yの順番が違う

URLルーティングとSQLパラメータの順番がずれると、別の地域のタイルを生成します。サーバーログへz/x/yと返却バイト数を残すと発見しやすくなります。

MVTへ属性を入れすぎている

説明文、内部メモ、更新履歴などをすべてタイルへ入れると、タイルが大きくなります。色分けやラベルに必要な属性だけを含め、詳細はIDを使って別APIから取得します。

低ズームで地物が多すぎる

日本全体を表示するズームで設備をすべて返すと、MVTでも重くなります。低ズームでは集計結果や代表点を返し、拡大後に個別地物を返します。

WHERE
  ($1 >= 12)
  AND f.geom && ST_Transform(
    ST_TileEnvelope($1, $2, $3),
    ST_SRID(f.geom)
  )

本番運用で必要な設計

  • z/x/yの範囲をAPIで検証する
  • タイルへ機密情報を含めない
  • レイヤーごとに最大ズームと表示条件を決める
  • タイル生成時間と返却バイト数を記録する
  • GiST空間インデックスと統計情報を維持する
  • 更新頻度に合わせてキャッシュ期限を決める
  • 詳細APIと編集APIをMVT配信から分離する

MVT化は通信量を減らすだけではありません。低ズームの集約、表示属性、権限、キャッシュまで含めて設計すると、データが増えても運用しやすい地図になります。

PostGISとMapLibreの開発をご相談ください

BitLightでは、既存GeoJSONの負荷調査、MVT用SQL、タイルAPI、MapLibreのレイヤー設計まで支援しています。

現在の地図を止めずに、重いレイヤーから段階的にMVTへ移行することも可能です。GIS開発を無料相談する

参考資料

GISシステム開発・改善支援

既存GISの改善から新規開発まで相談できます

QGIS、PostGIS、GeoServer、MapLibre、GeoDjangoを使った現地調査基盤、Web地図、データ処理を支援します。

GIS開発の無料相談

同期・性能・地図表示の問題を相談できます

QGIS、PostGIS、GeoServer、MapLibre、GeoDjangoの構成を確認し、原因の切り分けと改善順を整理します。

著者
守高 成悟
守高 成悟

代表取締役 CEO

千葉県出身。10歳の頃からプログラミングを始め、ゲーム、Webサイト、ロボット、スマホアプリなどを制作。大阪大学基礎工学部情報科学科で情報工学と統計学を学び、大学時代はAIを研究。大学在学中にWeb広告代理店でのインターンや人材系Webサービスの立ち上げを経験し、卒業後はフリーランスエンジニアとしてGISシステム、データ基盤構築、Webシステムの開発に従事。10年以上のWebアプリ開発・データ分析経験を基に、2023年9月に株式会社ビットライトを設立し、現場業務の仕組み化からデータ基盤構築、データ活用支援までを一気通貫で支援。

運営会社
株式会社ビットライト
株式会社ビットライト

顧客が本当に必要だった価値を、実装する。

現場業務の仕組み化からデータ基盤構築、データ活用支援までを一気通貫で支援しています。

コーポレートサイトを見る