Skip to main content

概要

Mapbox Vector Tiles (MVT) は、MapLibre や Mapbox GL などの Web マップ クライアントがネイティブに描画する、protobuf エンコードされたタイルです。ClickHouse では、このようなタイルを SQL だけで、 連携して動作する 2 つの関数を使って完全に構築できます。
  • MVTEncodeGeom — ジオメトリをスリッピーマップ タイル内のローカルなピクセル空間に投影し、 タイルの範囲にクリップするスカラー関数です。
  • MVTEncode — グループ内の投影済みジオメトリを集約し、単一レイヤーのタイルのバイナリ バイト列に変換する 集約関数です。
2 つの補助関数 MVTBoundingBoxMVTBoundingBoxMercator はタイルのバウンディング ボックスを返すため、行を WHERE 句でその範囲内に絞り込み、索引を使用できます。 Point、line、polygon のジオメトリがサポートされており、Geometry 型と具体的な Geo 型 (Point, LineString, MultiLineString, Ring, Polygon, MultiPolygon) も含まれます。 結果として得られるバイト列は完全なタイルであり、FORMAT RawBLOB を使って HTTPインターフェイス 経由で直接返すことができます。 これらの関数は PostGIS のワークフローに対応しており、別名として PostGIS 名でも利用できます。MVTEncodeGeom の別名が ST_AsMVTGeomMVTEncode の別名が ST_AsMVT です。

MVTEncodeGeom

地理座標 (経度/緯度) で与えられたジオメトリを、zoomtile_xtile_y で識別される slippy-map タイルのタイル内ローカルなピクセル空間に投影し、タイルにクリップして整数ピクセルグリッドにスナップし、 タイル空間のジオメトリを返します。 投影には、UInt32 の座標範囲全体にわたる Web Mercator を使用します。返される座標の始点はタイルの 左上隅で、y 軸は下向きです。これは Mapbox Vector Tile フォーマットの座標規約であるため、結果はそのまま MVTEncode に渡せます。座標は整数ピクセルに丸められるため、 MVTEncodeGeom でグループ化すると、同じグリッド上にあるジオメトリは 1 つのクラスターに集約されます。 clip が有効な場合 (デフォルト) 、ジオメトリは buffer ピクセルだけ拡張されたタイル (各軸で [-buffer, extent + buffer] の範囲) にクリップされます。完全に外側にあるジオメトリは NULL になります。これは PostGIS の ST_AsMVTGeom に相当します。 出力ジオメトリの型は入力に応じて決まります。PointPoint を返します。LineString または MultiLineStringMultiLineString を返します。RingPolygonMultiPolygonMultiPolygon を返します (クリッピングによりジオメトリが 複数のパーツに分割される場合があります) 。 構文
MVTEncodeGeom(geometry, zoom, tile_x, tile_y[, extent[, buffer[, clip]]])
引数
  • geometry — 経度/緯度 (度単位) の Geometry。経度は [-180, 180] に、緯度は Web Mercator の範囲 [-85.05112878, 85.05112878] に収まるように丸められます。Point / LineString / MultiLineString / Ring / Polygon / MultiPolygon / Geometry
  • zoom — Slippy-map のズームレベル。範囲は [0, 32] です。UInt8
  • tile_x — タイルの列インデックス。範囲は [0, 2^zoom - 1] です。UInt32
  • tile_y — タイルの行インデックス。範囲は [0, 2^zoom - 1] です。UInt32
  • extent — 省略可能なタイル extent (1 辺あたりのピクセル数) 。範囲は [1, 2147483647] です。既定値は Mapbox Vector Tile の既定値である 4096 です。UInt32
  • buffer — 省略可能なクリップバッファ (ピクセル単位) 。範囲は [0, 2147483647] です。既定値は 1 です。UInt32
  • clip — 省略可能なフラグです。0 以外 (既定値) の場合、ジオメトリはタイルとバッファを含む範囲にクリップされます。UInt8
戻り値 タイル空間のジオメトリ、または完全にクリップされる場合は NULL を返します。Geometry
SELECT MVTEncodeGeom((13.37, 52.52)::Point, 10, 550, 335) AS pixel
┌─pixel──────┐
│ (124,3384) │
└────────────┘

MVTEncode

複数のフィーチャをバイナリの Mapbox Vector Tile レイヤーにエンコードします。これはスカラー関数 MVTEncodeGeom に対応する集約関数です。各入力行は 1 つのフィーチャになり、Point、line、Polygon のジオメトリをサポートします。 geometry 引数はタイル空間座標の Geometry で、通常は MVTEncodeGeom によって生成されます。ジオメトリが NULL の行 (たとえば MVTEncodeGeom によってクリップアウトされたもの) はスキップされます。省略可能な properties 引数は名前付きタプルで、その要素名がフィーチャの attribute キーとなり、要素の型がベクタータイルの値の型を決定します。 結果は単一レイヤーのタイルの生バイト列です。空のグループからは空のタイルが生成されます。これは PostGIS の ST_AsMVT に相当します。 構文
MVTEncode(layer_name[, extent[, feature_id_name[, stringify_unsupported]]])(geometry[, properties])
パラメータ
  • layer_name — ベクタータイルレイヤーの名前。String
  • extent — タイルの各辺のピクセル数で表した extent。範囲は [1, 2147483647] です。デフォルトは 4096UInt32
  • feature_id_nameproperties タプル内の符号なし整数要素の名前を指定する省略可能なパラメータです。指定した要素は、タグではなく MVT Feature の id (UInt64) として出力されます。符号付き整数は受け付けられません。NULLid はそのフィーチャでは省略されます。パラメータは位置指定のため、これを使うには extent を指定する必要があります。String
  • stringify_unsupported — 省略可能なフラグ (0/1、デフォルトは 0) 。1 を指定すると、直接サポートされていないプロパティ型 (例: 大きな整数、UUIDDecimal) は、error を発生させる代わりにテキストの string_value としてエンコードされます。UInt8
引数
  • geometry — タイル空間のジオメトリ。たとえば MVTEncodeGeom の出力です。Geometry
  • properties — フィーチャの属性を表す省略可能な named tuple。要素名は属性キーになります。Tuple
戻り値 単一レイヤーの Mapbox Vector Tile のバイナリ内容を返します。String

プロパティ型

各プロパティ要素は、その ClickHouse 型に対応する Mapbox Vector Tile の Value バリアントとしてエンコードされます。
ClickHouse 型ベクタータイルの値型
String / FixedStringstring_value
Float32 / BFloat16float_value
Float64double_value
Boolbool_value
Int8 / Int16 / Int32 / Int64 / Date32sint_value
UInt8 / UInt16 / UInt32 / UInt64 / Date / DateTimeuint_value
型は NullableLowCardinality でラップされることがあります。NULL 値の場合、そのフィーチャでは 해당 attribute は省略されます。これは、ベクタータイル形式では null を扱えないためです。その他のプロパティ型では例外が発生しますが、stringify_unsupported が設定されている場合は、テキストの string_value としてエンコードされます。 同じプロパティ値はレイヤーの共有値プールにインターンされるため、多数のフィーチャで現れる値でも 一度だけ格納されます。

プロパティ タプルへの命名

プロパティ タプルでは、要素名を明示的に指定する必要があります。tuple(...) 内のカラムの別名はタプルの 要素名に引き継がれないため、キャストを使って要素に名前を付けてください:
tuple(count(), any(id))::Tuple(cluster_count UInt64, id String)

クラスタリング

クラスタリングは関数ではなく SQL で表現します。MVTEncodeGeom はピクセル単位の整数に丸めるため、ピクセルジオメトリでグループ化すると重なったジオメトリはマージされます。グループはサブクエリで集約し、クラスターごとに 1 行だけを MVTEncode に渡します。
SELECT MVTEncode('points')(geom, tuple(cluster_count)::Tuple(cluster_count UInt64)) AS tile
FROM
(
    SELECT MVTEncodeGeom((lon, lat)::Point, 10, 550, 335) AS geom, count() AS cluster_count
    FROM points
    GROUP BY geom
)
SETTINGS allow_suspicious_types_in_group_by = 1;
Geometry 値でグループ化するには、allow_suspicious_types_in_group_by = 1 が必要です。これは、Variant ベースの Geometry 型によるグループ化がデフォルトで制限されているためです。クラスタ化されたフィーチャではなく、入力の各行につき 1 つのフィーチャを出力するには、 内側の GROUP BY (および count()) を省略してください。

MVTBoundingBox

zoomtile_xtile_y で識別される slippy-map タイルの地理的なバウンディングボックスを、度単位のタプル (min_lon, min_lat, max_lon, max_lat) として返します。 これを使うと、各行で Web Mercator 投影を再計算する代わりに、longitude/latitude カラムを直接フィルタしながら、対象の行をタイル内に絞り込めます。これにより、それらのカラムの主キーや 索引を利用できます。省略可能な margin は、タイルサイズに対するその割合だけボックスを各辺で拡張します。MVTEncodeGeom のクリップバッファをカバーするには、これを buffer / extent に設定してください。 構文
MVTBoundingBox(zoom, tile_x, tile_y[, margin])
引数
  • zoom — Slippy-map のズームレベル。範囲は [0, 32] です。UInt8
  • tile_x — タイルの列インデックス。範囲は [0, 2^zoom - 1] です。UInt32
  • tile_y — タイルの行インデックス。範囲は [0, 2^zoom - 1] です。UInt32
  • margin — 各辺でボックスを拡張するための、タイルサイズに対するオプションの比率です。デフォルトは 0 です。Float64
戻り値 タイルのバウンディングボックスを、度単位のタプル (min_lon, min_lat, max_lon, max_lat) として返します。Tuple(Float64, Float64, Float64, Float64)
SELECT MVTBoundingBox(0, 0, 0) AS bbox
┌─bbox────────────────────────────────────────────┐
│ (-180,-85.05112877980659,180,85.05112877980659)  │
└──────────────────────────────────────────────────┘

MVTBoundingBoxMercator

MVTBoundingBox の Web Mercator 版です。MVTEncodeGeom が内部で使用する full-UInt32 の Web Mercator 座標空間におけるタイルの バウンディングボックスを、タプル (min_x, min_y, max_x, max_y) として返します。y 軸は下向きに増加します (上が北です) 。longitude/latitude の代わりに、Mercator 座標のカラムをマテリアライズして それらに索引を作成するテーブルを想定しています。 構文
MVTBoundingBoxMercator(zoom, tile_x, tile_y[, margin])
引数 MVTBoundingBox と同じです。 戻り値 タイルのバウンディングボックスを、Web Mercator 座標系でのタプル (min_x, min_y, max_x, max_y) として返します。Tuple(Float64, Float64, Float64, Float64)
SELECT MVTBoundingBoxMercator(1, 0, 0) AS bbox
┌─bbox────────────────────────┐
│ (0,0,2147483648,2147483648)  │
└──────────────────────────────┘

行をタイルに制限する

タイルには、それに属するジオメトリだけを含める必要があります。これは、相互に補完し合う 2 つのステップで表すのが最適です。つまり、WHERE 句で低コストかつ索引を利用するバウンディングボックス条件 (性能) と、MVTEncodeGeom によるクリップ処理 (正確性) です。 このクリップ処理ではタイル外のジオメトリが除外されるため、バウンディングボックス条件が多少緩くても、タイル外のジオメトリが結果に入り込むことはありません。
WITH
    1 AS buffer,
    4096 AS extent,
    MVTBoundingBox({z:UInt8}, {x:UInt32}, {y:UInt32}, buffer / extent) AS bounding_box   -- margin matches the clip buffer
SELECT MVTEncode('points')(geom, tuple(cluster_count)::Tuple(cluster_count UInt64))
FROM
(
    SELECT MVTEncodeGeom((lon, lat)::Point, {z:UInt8}, {x:UInt32}, {y:UInt32}) AS geom, count() AS cluster_count
    FROM points
    WHERE lon BETWEEN bounding_box.1 AND bounding_box.3 AND lat BETWEEN bounding_box.2 AND bounding_box.4   -- index-using prefilter
    GROUP BY geom
)
SETTINGS allow_suspicious_types_in_group_by = 1
バウンディングボックスのpredicateは、あくまで粗い事前filterにすぎません。正確なタイル境界は MVTEncodeGeom のclipによって適用されます。クリッピングを無効にし、 WHERE predicateのみに依存するには、clip => false (7番目のargument) を MVTEncodeGeom に渡します。

HTTP 経由でタイルを配信する

ClickHouse はデフォルトではタイル用のエンドポイントを公開していません。HTTP インターフェイスは / でのみクエリを受け付けます。すっきりした /tile/{z}/{x}/{y} URL は、サーバー設定の predefined query ハンドラー を使ってオペレーターが追加します。ハンドラーの urlregex: 形式を使ってパスセグメントを取り込み、それらをクエリ パラメータにバインドし、FORMAT RawBLOB でバイト列を返します。 もっとも単純なケースでは、テーブルに Geometry カラムがあり、ハンドラーは 1 行につき 1 つの地物を返します。MVTEncodeGeom は各ジオメトリを要求されたタイルに投影してクリップするため、タイル外の行は自動的に除外されます。
<http_handlers>
    <rule>
        <methods>GET</methods>
        <url><![CDATA[regex:/tile/(?P<z>\d+)/(?P<x>\d+)/(?P<y>\d+)]]></url>
        <handler>
            <type>predefined_query_handler</type>
            <query>
                SELECT MVTEncode('shapes')(
                    MVTEncodeGeom(geom, {z:UInt8}, {x:UInt32}, {y:UInt32}),
                    tuple(id, name)::Tuple(id UInt32, name String))
                FROM shapes
                FORMAT RawBLOB
            </query>
            <content_type>application/vnd.mapbox-vector-tile</content_type>
        </handler>
    </rule>
    <defaults/>
</http_handlers>
ここで shapes は、geom Geometry カラム (points、lines、polygons を任意に混在できる) を持つテーブルです。GET /tile/10/550/335 は、エンコードされたタイルを返します。 ポイントデータの場合も、MVTEncodeGeom((lon, lat)::Point, …) を使ってポイントをインラインで構築すれば、通常の longitude/latitude カラムに対して同様に機能します。同一位置のフィーチャをクラスター化したり、大きなテーブルに対して索引を使用するバウンディングボックスの事前フィルタを追加したりするには、Clustering および Restricting rows to a tile に示すように内部クエリを拡張してください。

制限事項

  • Web Mercator 投影では緯度は ±85.05112878° に制限され、反子午線をまたぐ入力は処理できません。
  • Polygon のクリッピングでは、MVT として有効な出力は保証されません。 クリッピングによってリングの向きと閉合は修正されますが、自己交差は修正されません。したがって、自己交差する (“bow-tie” 型の) リングは修復されず、タイルとの交わり方によっては、そのまま出力される (この場合も無効なまま) か、NULL として破棄されます。たとえば、タイル内に完全に収まる bow-tie は破棄されますが、同じ 4 つの頂点を単純なリングとして結んだものは保持されます。
-- self-intersecting ring -> dropped (NULL)
SELECT MVTEncodeGeom([[(40.0, 40.0), (50.0, 50.0), (50.0, 40.0), (40.0, 50.0), (40.0, 40.0)]]::Polygon, 2, 2, 1) IS NULL;  -- 1
-- simple ring, same four corners -> kept
SELECT MVTEncodeGeom([[(40.0, 40.0), (50.0, 40.0), (50.0, 50.0), (40.0, 50.0), (40.0, 40.0)]]::Polygon, 2, 2, 1) IS NULL;  -- 0
  • ジオメトリは、整数ピクセルグリッドに丸められる前にクリップされます。 PostGIS では、まずジオメトリを整数ピクセルグリッドにスナップし、その後でクリップします。一方、MVTEncodeGeom では、まずクリップし (浮動小数点の投影座標上で実行) 、その後で丸めます。タイルの端付近では、この違いにより、本来なら境界ピクセルに丸められるはずの座標が落とされることがあります。たとえば、buffer = 0 の場合、タイル境界のすぐ東にある点はクリップされます。これは、先に丸める方式であれば保持される境界ピクセル 4096 に丸められるにもかかわらずです:
-- floating-point x ~= 4096.23 is just past the east edge (extent = 4096) -> clipped
SELECT MVTEncodeGeom((90.005, 30.0)::Point, 2, 2, 1, 4096, 0) IS NULL;          -- 1
-- the same point projected without clipping rounds onto the edge pixel:
SELECT MVTEncodeGeom((90.005, 30.0)::Point, 2, 2, 1, 4096, 0, false);           -- (4096,2664)
Last modified on June 29, 2026