Обзор
векторные тайлы Mapbox (MVT) — это тайлы, закодированные в protobuf, которые веб-картографические клиенты, такие как MapLibre и Mapbox GL, могут отображать нативно. ClickHouse может строить такие тайлы целиком на SQL с помощью пары взаимосвязанных функций:
MVTEncodeGeom— скалярная функция, которая проецирует геометрию в локальное пиксельное пространство тайла slippy-map и обрезает её по границам тайла.MVTEncode— агрегатная функция, которая собирает спроецированные геометрии группы в бинарное представление однослойного тайла.
Две вспомогательные функции, MVTBoundingBox и MVTBoundingBoxMercator, возвращают ограничивающий прямоугольник тайла, чтобы
отфильтровать по нему строки в предложении WHERE с использованием индекса.
Поддерживаются точечные, линейные и полигональные геометрии, включая тип Geometry и конкретные гео-типы (Point,
MultiPoint, 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.
Координаты Polygon перед проверкой ограничиваются окном 2^30 — это в точности пиксельный диапазон всего мира при
zoom 18 и extent 4096 — поэтому для реалистичных тайлов геометрия проверяется, но никогда не обрезается, и это ограничение влияет
только на геометрию, размещённую при экстремальных значениях zoom или extent.
Тип выходной геометрии зависит от входного: Point возвращает Point; MultiPoint возвращает MultiPoint; 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/MultiPoint/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 / FixedString |
string_value |
Float32 / BFloat16 |
float_value |
Float64 |
double_value |
Bool |
bool_value |
Int8 / Int16 / Int32 / Int64 / Date32 |
sint_value |
UInt8 / UInt16 / UInt32 / UInt64 / Date / DateTime |
uint_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 BY (и count()), чтобы выводить по одной возможности на каждую входную строку
вместо кластеризованных возможностей.
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°и не поддерживает входные данные, пересекающие антимеридиан.