Skip to main content

Обзор

векторные тайлы Mapbox (MVT) — это тайлы, закодированные в protobuf, которые веб-картографические клиенты, такие как MapLibre и Mapbox GL, могут отображать нативно. ClickHouse может строить такие тайлы целиком на SQL с помощью пары взаимосвязанных функций:
  • MVTEncodeGeom — скалярная функция, которая проецирует геометрию в локальное пиксельное пространство тайла slippy-map и обрезает её по границам тайла.
  • MVTEncode — агрегатная функция, которая собирает спроецированные геометрии группы в бинарное представление однослойного тайла.
Две вспомогательные функции, MVTBoundingBox и MVTBoundingBoxMercator, возвращают ограничивающий прямоугольник тайла, чтобы отфильтровать по нему строки в условии WHERE с использованием индекса. Поддерживаются точечные, линейные и полигональные геометрии, включая тип Geometry и конкретные гео-типы (Point, LineString, MultiLineString, Ring, Polygon, MultiPolygon). Результирующие байты представляют собой полноценный тайл, который можно напрямую вернуть через HTTP-интерфейс с FORMAT RawBLOB. Эти функции повторяют workflow PostGIS и также доступны под своими именами PostGIS как псевдонимы: ST_AsMVTGeom для MVTEncodeGeom и ST_AsMVT для MVTEncode.

MVTEncodeGeom

Проецирует геометрию, заданную в географических координатах (долгота/широта), в локальное пиксельное пространство тайла slippy-map, заданного zoom, tile_x и tile_y, обрезает её по границам тайла, привязывает к целочисленной пиксельной сетке и возвращает геометрию в координатах тайла. Используется проекция Web Mercator во всём диапазоне координат UInt32. Возвращаемые координаты имеют начало в левом верхнем углу тайла, а ось y направлена вниз, что соответствует соглашению о координатах формата Mapbox Vector Tile, поэтому результат можно напрямую передавать в MVTEncode. Координаты округляются до целых пикселей, поэтому группировка по MVTEncodeGeom объединяет геометрии, попадающие в одну и ту же ячейку сетки, в один кластер. Когда clip включён (по умолчанию), геометрия обрезается по тайлу, расширенному на buffer пикселей (диапазон [-buffer, extent + buffer] по каждой оси); геометрия, полностью выходящая за эти пределы, становится NULL. Это аналог PostGIS ST_AsMVTGeom. Тип выходной геометрии зависит от входного: Point возвращает Point; LineString или MultiLineString возвращает MultiLineString; Ring, Polygon или MultiPolygon возвращает MultiPolygon (при обрезке геометрия может разделиться на несколько частей). Синтаксис
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 — Необязательный размер тайла в пикселях на сторону, в диапазоне [1, 2147483647]. По умолчанию 4096 — стандартное значение Mapbox Vector Tile. UInt32.
  • buffer — Необязательный буфер обрезки в пикселях, в диапазоне [0, 2147483647]. По умолчанию 1. UInt32.
  • clip — Необязательный флаг; если значение ненулевое (по умолчанию), геометрия обрезается по границам тайла с учётом буфера. UInt8.
Возвращаемое значение Возвращает геометрию в координатах тайла или NULL, если она полностью обрезается. Geometry. Пример
SELECT MVTEncodeGeom((13.37, 52.52)::Point, 10, 550, 335) AS pixel
┌─pixel──────┐
│ (124,3384) │
└────────────┘

MVTEncode

Кодирует группу возможностей в бинарный слой Mapbox Vector Tile. Это агрегатный аналог скалярной функции MVTEncodeGeom. Каждая входная строка становится отдельной возможностью; поддерживаются геометрии Point, line и Polygon. Аргумент geometry — это Geometry с координатами в пространстве тайла, обычно создаваемая MVTEncodeGeom. Строки, у которых геометрия имеет значение NULL (например, если она была отсечена функцией MVTEncodeGeom), пропускаются. Необязательный аргумент properties — это именованный кортеж, имена элементов которого становятся ключами атрибутов возможности, а типы элементов определяют типы значений векторного тайла. Результат представляет собой 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, который будет записан как id возможности MVT (типа UInt64), а не как тег. Целые числа со знаком не допускаются. Идентификатор NULL для такой возможности опускается. Параметры позиционные, поэтому для использования этого параметра нужно указать extent. String.
  • stringify_unsupported — Необязательный флаг (0/1, по умолчанию 0); при значении 1 типы свойств, которые не поддерживаются напрямую (например, большие целые числа, UUID, Decimal), кодируются как их текстовое значение string_value вместо выдачи ошибки. UInt8.
Аргументы
  • geometry — Геометрия в пространстве тайла, например из MVTEncodeGeom. Geometry.
  • properties — Необязательный именованный кортеж атрибутов возможности. Имена элементов становятся ключами атрибутов. Tuple.
Возвращаемое значение Возвращает бинарное содержимое однослойного тайла Mapbox Vector Tile. String.

Типы свойств

Каждый элемент свойства кодируется как вариант Value Mapbox Vector Tile, соответствующий его типу ClickHouse:
Тип ClickHouseТип значения векторного тайла
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. Одинаковые значения свойств интернируются в общий пул значений слоя, поэтому значение, которое встречается у многих возможностей, сохраняется только один раз.

Именование кортежа properties

Кортеж properties должен иметь явно заданные имена элементов. Псевдонимы столбцов внутри tuple(...) не становятся именами элементов кортежа, поэтому задавайте имена элементов с помощью приведения типа:
tuple(count(), any(id))::Tuple(cluster_count UInt64, id String)

Кластеризация

Кластеризация задаётся в SQL, а не самой функцией. Поскольку MVTEncodeGeom округляет до целых пикселей, группировка по пиксельной геометрии объединяет совпадающие геометрические объекты; выполните агрегацию в подзапросе, а затем передайте в 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, поскольку группировка по типу Geometry на базе Variant по умолчанию запрещена. Опустите внутренний GROUP BYcount()), чтобы выводить по одной возможности на каждую входную строку вместо кластеризованных возможностей.

MVTBoundingBox

Возвращает географический ограничивающий прямоугольник тайла slippy-map, заданного zoom, tile_x и tile_y, в виде кортежа (min_lon, min_lat, max_lon, max_lat) в градусах. Используйте эту функцию, чтобы ограничить строки тайлом, фильтруя напрямую по столбцам longitude/latitude, — так можно использовать первичный ключ или индекс по этим столбцам — вместо того чтобы заново вычислять проекцию Web Mercator для каждой строки. Необязательный параметр margin расширяет ограничивающий прямоугольник с каждой стороны на указанную долю размера тайла; установите его в buffer / extent, чтобы охватить буфер отсечения MVTEncodeGeom. Синтаксис
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. Возвращает ограничивающий прямоугольник тайла в полном координатном пространстве Web Mercator с UInt32, которое внутренне использует MVTEncodeGeom, в виде кортежа (min_x, min_y, max_x, max_y). Ось y направлена вниз (север сверху). Предназначена для таблиц, которые материализуют столбцы с координатами Mercator и индексируют их вместо longitude/latitude. Синтаксис
MVTBoundingBoxMercator(zoom, tile_x, tile_y[, margin])
Аргументы То же, что и у MVTBoundingBox. Возвращаемое значение Возвращает ограничивающий прямоугольник тайла в виде кортежа (min_x, min_y, max_x, max_y) в координатах проекции Web Mercator. Tuple(Float64, Float64, Float64, Float64). Пример
SELECT MVTBoundingBoxMercator(1, 0, 0) AS bbox
┌─bbox────────────────────────┐
│ (0,0,2147483648,2147483648)  │
└──────────────────────────────┘

Ограничение строк рамками тайла

Тайл должен содержать только ту геометрию, которая к нему относится. Лучше всего это выразить двумя взаимодополняющими шагами: недорогим предикатом ограничивающего прямоугольника, использующим индекс, в предложении 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
Предикат ограничивающего прямоугольника — это лишь грубый предварительный фильтр; точная граница тайла обеспечивается обрезкой в MVTEncodeGeom. Передайте clip => false (седьмой аргумент) в MVTEncodeGeom, чтобы отключить обрезку и полагаться только на предикат WHERE.

Раздача тайлов по HTTP

ClickHouse по умолчанию не предоставляет конечную точку для тайлов: HTTP-интерфейс принимает запросы только по адресу /. Удобный URL /tile/{z}/{x}/{y} добавляется оператором с помощью обработчика предопределённого запроса в конфигурации сервера. url обработчика использует форму regex: для захвата сегментов пути, связывает их с параметрами запроса и возвращает байты в формате 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>
Здесь shapes — это таблица со столбцом geom Geometry (любое сочетание точек, линий и полигонов). GET /tile/10/550/335 возвращает закодированный тайл. Для точечных данных это так же хорошо работает и с обычными столбцами longitude/latitude, если формировать точку прямо в запросе с помощью MVTEncodeGeom((lon, lat)::Point, …). Чтобы кластеризовать совпадающие возможности или добавить для больших таблиц предварительный фильтр по ограничивающему прямоугольнику с использованием индекса, расширьте внутренний запрос, как показано в разделах Кластеризация и Ограничение строк пределами тайла.

Ограничения

  • Проекция Web Mercator ограничивает широту значениями ±85.05112878° и не поддерживает входные данные, пересекающие антимеридиан.
  • Обрезка полигонов не гарантирует выходные данные, корректные для MVT. Обрезка исправляет ориентацию и замыкание контуров, но не самопересечения. Поэтому самопересекающийся контур («галстук-бабочка») не исправляется: в зависимости от того, как он пересекает тайл, он либо выводится без изменений (и остаётся некорректным), либо заменяется на NULL. Например, контур в форме галстука-бабочки, полностью лежащий внутри тайла, заменяется на 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
  • Geometry обрезается перед округлением до целочисленной пиксельной сетки. 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