Skip to main content

개요

Mapbox Vector Tiles (MVT)는 MapLibre 및 Mapbox GL 같은 웹 지도 클라이언트가 네이티브로 렌더링하는 protobuf 인코딩 타일입니다. ClickHouse는 서로 연동되는 두 개의 함수를 사용해 이러한 타일을 전적으로 SQL로 생성할 수 있습니다.
  • MVTEncodeGeom — 지오메트리를 슬리피 맵 타일의 타일 로컬 픽셀 공간으로 투영하고 타일 경계에 맞게 잘라내는 스칼라 함수입니다.
  • MVTEncode — 그룹의 투영된 지오메트리를 모아 단일 레이어 타일의 바이너리 바이트로 인코딩하는 집계 함수입니다.
두 개의 도우미 함수인 MVTBoundingBoxMVTBoundingBoxMercator는 타일의 경계 상자를 반환하므로, WHERE 절에서 인덱스를 사용해 행을 해당 범위로 제한할 수 있습니다. Point, 선, Polygon 지오메트리를 지원하며, Geometry 타입과 구체적인 geo 타입(Point, LineString, MultiLineString, Ring, Polygon, MultiPolygon)도 포함됩니다. 결과 바이트는 완전한 타일이며, FORMAT RawBLOB과 함께 HTTP 인터페이스를 통해 직접 반환할 수 있습니다. 이 함수들은 PostGIS 워크플로를 따르며, PostGIS 이름의 alias로도 사용할 수 있습니다. MVTEncodeGeom의 alias는 ST_AsMVTGeom이고, MVTEncode의 alias는 ST_AsMVT입니다.

MVTEncodeGeom

지리 좌표(경도/위도)로 주어진 도형을 zoom, tile_x, tile_y로 식별되는 slippy-map 타일의 타일 내부 픽셀 공간으로 투영하고, 타일 경계에 맞게 잘라낸 다음 정수 픽셀 그리드에 스냅하여 타일 공간의 도형을 반환합니다. 투영에는 전체 UInt32 좌표 범위에 걸친 Web Mercator를 사용합니다. 반환되는 좌표의 원점은 타일의 왼쪽 위 모서리에 있고 y축은 아래쪽을 향합니다. 이는 Mapbox Vector Tile 포맷의 좌표 규약이므로 결과를 MVTEncode에 바로 전달할 수 있습니다. 좌표는 정수 픽셀로 반올림되므로 MVTEncodeGeom으로 그룹화하면 동일한 그리드에 놓인 도형이 하나의 클러스터로 합쳐집니다. clip이 활성화되면(기본값) 도형은 buffer 픽셀만큼 확장된 타일 범위(각 축에서 [-buffer, extent + buffer])로 잘려 나가며, 완전히 바깥에 있는 도형은 NULL이 됩니다. 이는 PostGIS ST_AsMVTGeom에 대응합니다. 출력 도형 유형은 입력에 따라 달라집니다. PointPoint를 반환하고, LineString 또는 MultiLineStringMultiLineString을 반환하며, Ring, Polygon, MultiPolygonMultiPolygon을 반환합니다 (클리핑 과정에서 하나의 도형이 여러 파트로 분할될 수 있습니다). 구문
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[0, 32] 범위의 Slippy-map 확대/축소 수준입니다. UInt8.
  • tile_x[0, 2^zoom - 1] 범위의 타일 컬럼 인덱스입니다. UInt32.
  • tile_y[0, 2^zoom - 1] 범위의 타일 행 인덱스입니다. UInt32.
  • extent — 타일 한 변의 픽셀 수를 나타내는 선택적 extent이며, 범위는 [1, 2147483647]입니다. 기본값은 Mapbox Vector Tile의 기본값인 4096입니다. UInt32.
  • buffer — 픽셀 단위의 선택적 클립 buffer이며, 범위는 [0, 2147483647]입니다. 기본값은 1입니다. UInt32.
  • clip — 선택적 flag입니다. 0이 아니면(기본값) 지오메트리가 타일과 buffer를 포함한 범위로 클리핑됩니다. UInt8.
반환 값 타일 공간의 지오메트리를 반환합니다. 완전히 클리핑된 경우 NULL을 반환합니다. Geometry. 예시
SELECT MVTEncodeGeom((13.37, 52.52)::Point, 10, 550, 335) AS pixel
┌─pixel──────┐
│ (124,3384) │
└────────────┘

MVTEncode

피처 그룹을 바이너리 Mapbox Vector Tile 레이어로 인코딩합니다. 이는 스칼라 함수 MVTEncodeGeom에 대응하는 집계 함수입니다. 각 입력 행은 하나의 피처가 되며, Point, 선, Polygon 지오메트리를 지원합니다. geometry 인수는 타일 공간 좌표의 Geometry이며, 일반적으로 MVTEncodeGeom으로 생성됩니다. 지오메트리가 NULL인 행(예: MVTEncodeGeom에 의해 잘려 나간 경우)은 건너뜁니다. 선택적 properties 인수는 named tuple이며, 해당 요소의 이름은 피처 속성 키가 되고 요소 타입은 벡터 타일 값 타입을 결정합니다. 결과는 단일 레이어 타일의 원시 바이트(raw bytes)입니다. 빈 그룹은 빈 타일을 생성합니다. 이는 PostGIS ST_AsMVT에 해당합니다. 구문
MVTEncode(layer_name[, extent[, feature_id_name[, stringify_unsupported]]])(geometry[, properties])
매개변수
  • layer_name — 벡터 타일 레이어의 이름입니다. String.
  • extent — 타일 한 변의 픽셀 수이며, 범위는 [1, 2147483647]입니다. 기본값은 4096입니다. UInt32.
  • feature_id_name — 선택 사항으로, properties Tuple에서 부호 없는 정수 요소의 이름을 지정합니다. 이 요소는 태그가 아니라 MVT Feature의 id(UInt64)로 출력됩니다. 부호 있는 정수는 허용되지 않습니다. idNULL이면 해당 feature에서는 생략됩니다. 매개변수는 위치 기반이므로, 이를 사용하려면 extent를 지정해야 합니다. String.
  • stringify_unsupported — 선택적 flag(0/1, 기본값 0)입니다. 1로 설정하면 직접 지원되지 않는 속성 타입(예: 큰 정수, UUID, Decimal)은 오류를 발생시키는 대신 텍스트 string_value로 인코딩됩니다. UInt8.
인수
  • geometry — 타일 좌표계의 지오메트리입니다. 예를 들어 MVTEncodeGeom의 출력이 해당합니다. Geometry.
  • properties — feature 속성의 선택적 named tuple입니다. 요소 이름은 속성 키가 됩니다. Tuple.
반환 값 단일 레이어 Mapbox Vector Tile의 바이너리 콘텐츠를 반환합니다. String.

속성 타입

각 속성 요소는 해당 ClickHouse 타입에 맞는 Mapbox Vector Tile Value variant로 인코딩됩니다:
ClickHouse type벡터 타일 값 타입
String / FixedStringstring_value
Float32 / BFloat16float_value
Float64double_value
Boolbool_value
Int8 / Int16 / Int32 / Int64 / Date32sint_value
UInt8 / UInt16 / UInt32 / UInt64 / Date / DateTimeuint_value
타입은 Nullable 및/또는 LowCardinality로 래핑될 수 있습니다. NULL 값이면 해당 피처에서는 그 속성이 생략되는데, 이는 벡터 타일 포맷에 null 값이 없기 때문입니다. 지원되지 않는 다른 속성 타입은 예외를 발생시킵니다. 단, stringify_unsupported가 설정된 경우에는 텍스트 string_value로 인코딩됩니다. 동일한 속성 값은 레이어의 공유 값 풀에 인터닝되므로, 여러 피처에 나타나는 값도 한 번만 저장됩니다.

속성 튜플 이름 지정

속성 튜플에는 요소 이름을 명시적으로 지정해야 합니다. tuple(...) 내부의 컬럼 별칭은 튜플 요소 이름으로 전달되지 않으므로, cast를 사용해 요소 이름을 지정하십시오:
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

zoom, tile_x, tile_y로 식별되는 슬리피 맵 타일의 지리적 경계 상자를 도 단위의 튜플 (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에서 내부적으로 사용하는 전체 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)  │
└──────────────────────────────┘

행을 타일에 맞게 제한하기

타일에는 해당 타일에 속한 Geometry만 포함되어야 합니다. 이를 가장 잘 구현하는 방법은 서로 보완하는 두 단계입니다. 즉, WHERE 절에서 인덱스를 사용하는 가벼운 경계 상자 프레디케이트(성능)와 MVTEncodeGeom의 클리핑(정확성)입니다. 클리핑은 타일 밖의 Geometry를 제거하므로, 경계 상자 프레디케이트가 다소 느슨하더라도 타일 외부 Geometry가 결과에 섞여 들어가지 않습니다.
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
경계 상자 프레디케이트는 대략적인 사전 필터일 뿐이며, 정확한 타일 경계는 MVTEncodeGeom의 clip에 의해 적용됩니다. 클리핑을 비활성화하고 WHERE 프레디케이트에만 의존하려면 MVTEncodeGeomclip => false(7번째 인수)를 전달하십시오.

HTTP를 통해 타일 제공하기

ClickHouse는 기본적으로 타일 endpoint를 노출하지 않습니다. HTTP 인터페이스는 /에서만 쿼리를 받습니다. 깔끔한 /tile/{z}/{x}/{y} URL은 서버 구성에서 미리 정의된 쿼리 핸들러를 사용해 오퍼레이터가 추가합니다. 이 핸들러의 urlregex: 형식을 사용해 경로 세그먼트를 캡처하고, 이를 쿼리 매개변수에 바인딩한 다음, FORMAT RawBLOB으로 바이트 데이터를 반환합니다. 가장 단순한 경우에는 테이블에 Geometry 컬럼이 있고, 핸들러가 각 행마다 하나의 피처를 제공합니다 — 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>
여기서 shapesgeom Geometry 컬럼(포인트, 선, 다각형이 혼합된 형태)을 가진 테이블입니다. GET /tile/10/550/335는 인코딩된 타일을 반환합니다. 포인트 데이터의 경우, MVTEncodeGeom((lon, lat)::Point, …)로 포인트를 인라인으로 생성하면 일반 longitude/latitude 컬럼에도 마찬가지로 사용할 수 있습니다. 동일한 위치의 피처를 클러스터링하거나, 큰 테이블에 대해 인덱스를 사용하는 경계 상자 사전 필터를 추가하려면 ClusteringRestricting rows to a tile에 표시된 것처럼 내부 쿼리를 확장하십시오.

제한 사항

  • Web Mercator 투영은 위도를 ±85.05112878°로 제한하며, 180도 자오선을 가로지르는 입력은 처리하지 않습니다.
  • Polygon 클리핑은 MVT에서 유효한 출력을 보장하지 않습니다. 클리핑은 링의 방향과 닫힘 여부는 바로잡지만, 자기 교차는 해결하지 않습니다. 따라서 자기 교차하는(“나비넥타이형”) 링은 복구되지 않습니다. 타일과 만나는 방식에 따라 변경 없이 그대로 출력되거나(여전히 유효하지 않음) NULL로 처리됩니다. 예를 들어, 타일 내부에 완전히 포함된 나비넥타이형은 제거되지만, 동일한 네 모서리를 단순한 링으로 감은 경우에는 유지됩니다:
-- 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이면 타일 경계 바로 동쪽에 있는 Point는 먼저 잘려 버리므로, 반올림 우선 방식에서는 유지되었을 경계 픽셀 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