> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-mintlify-fbfa8bee.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> Документация по кодированию векторных тайлов Mapbox

# Функции для кодирования векторных тайлов Mapbox

<div id="overview">
  ## Обзор
</div>

[векторные тайлы Mapbox](https://github.com/mapbox/vector-tile-spec) (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`.

<div id="mvtencodegeom">
  ## MVTEncodeGeom
</div>

Проецирует геометрию, заданную в географических координатах (долгота/широта), в локальное пиксельное пространство тайла
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` (при обрезке геометрия может разделиться на
несколько частей).

**Синтаксис**

```sql theme={null}
MVTEncodeGeom(geometry, zoom, tile_x, tile_y[, extent[, buffer[, clip]]])
```

**Аргументы**

* `geometry` — Geometry в градусах долготы и широты. Долгота ограничивается диапазоном `[-180, 180]`, а широта — диапазоном Web Mercator `[-85.05112878, 85.05112878]`. [`Point`](/ru/reference/data-types/geo) / [`LineString`](/ru/reference/data-types/geo) / [`MultiLineString`](/ru/reference/data-types/geo) / [`Ring`](/ru/reference/data-types/geo) / [`Polygon`](/ru/reference/data-types/geo) / [`MultiPolygon`](/ru/reference/data-types/geo) / [`Geometry`](/ru/reference/data-types/geo).
* `zoom` — Уровень масштабирования Slippy-map в диапазоне `[0, 32]`. [`UInt8`](/ru/reference/data-types/int-uint).
* `tile_x` — Индекс столбца тайла в диапазоне `[0, 2^zoom - 1]`. [`UInt32`](/ru/reference/data-types/int-uint).
* `tile_y` — Индекс строки тайла в диапазоне `[0, 2^zoom - 1]`. [`UInt32`](/ru/reference/data-types/int-uint).
* `extent` — Необязательный размер тайла в пикселях на сторону, в диапазоне `[1, 2147483647]`. По умолчанию `4096` — стандартное значение Mapbox Vector Tile. [`UInt32`](/ru/reference/data-types/int-uint).
* `buffer` — Необязательный буфер обрезки в пикселях, в диапазоне `[0, 2147483647]`. По умолчанию `1`. [`UInt32`](/ru/reference/data-types/int-uint).
* `clip` — Необязательный флаг; если значение ненулевое (по умолчанию), геометрия обрезается по границам тайла с учётом буфера. [`UInt8`](/ru/reference/data-types/int-uint).

**Возвращаемое значение**

Возвращает геометрию в координатах тайла или `NULL`, если она полностью обрезается. [`Geometry`](/ru/reference/data-types/geo).

**Пример**

```sql theme={null}
SELECT MVTEncodeGeom((13.37, 52.52)::Point, 10, 550, 335) AS pixel
```

```text theme={null}
┌─pixel──────┐
│ (124,3384) │
└────────────┘
```

<div id="mvtencode">
  ## MVTEncode
</div>

Кодирует группу возможностей в бинарный слой Mapbox Vector Tile. Это агрегатный аналог скалярной
функции `MVTEncodeGeom`. Каждая входная строка становится отдельной возможностью; поддерживаются геометрии Point, line и Polygon.

Аргумент `geometry` — это `Geometry` с координатами в пространстве тайла, обычно создаваемая `MVTEncodeGeom`. Строки, у которых
геометрия имеет значение `NULL` (например, если она была отсечена функцией `MVTEncodeGeom`), пропускаются. Необязательный аргумент `properties` — это
именованный кортеж, имена элементов которого становятся ключами атрибутов возможности, а типы элементов определяют типы значений
векторного тайла.

Результат представляет собой raw bytes одного слоя тайла. Пустая группа создаёт пустой тайл. Это аналог
PostGIS `ST_AsMVT`.

**Синтаксис**

```sql theme={null}
MVTEncode(layer_name[, extent[, feature_id_name[, stringify_unsupported]]])(geometry[, properties])
```

**Параметры**

* `layer_name` — Имя слоя векторного тайла. [`String`](/ru/reference/data-types/string).
* `extent` — Размер тайла в пикселях по каждой стороне, в диапазоне `[1, 2147483647]`. Значение по умолчанию — `4096`. [`UInt32`](/ru/reference/data-types/int-uint).
* `feature_id_name` — Необязательное имя элемента типа беззнакового целого в кортеже `properties`, который будет записан как `id` возможности MVT (типа `UInt64`), а не как тег. Целые числа со знаком не допускаются. Идентификатор `NULL` для такой возможности опускается. Параметры позиционные, поэтому для использования этого параметра нужно указать `extent`. [`String`](/ru/reference/data-types/string).
* `stringify_unsupported` — Необязательный флаг (`0`/`1`, по умолчанию `0`); при значении `1` типы свойств, которые не поддерживаются напрямую (например, большие целые числа, `UUID`, `Decimal`), кодируются как их текстовое значение `string_value` вместо выдачи ошибки. [`UInt8`](/ru/reference/data-types/int-uint).

**Аргументы**

* `geometry` — Геометрия в пространстве тайла, например из `MVTEncodeGeom`. [`Geometry`](/ru/reference/data-types/geo).
* `properties` — Необязательный именованный кортеж атрибутов возможности. Имена элементов становятся ключами атрибутов. [`Tuple`](/ru/reference/data-types/tuple).

**Возвращаемое значение**

Возвращает бинарное содержимое однослойного тайла Mapbox Vector Tile. [`String`](/ru/reference/data-types/string).

<div id="property-types">
  ### Типы свойств
</div>

Каждый элемент свойства кодируется как вариант `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`.

Одинаковые значения свойств интернируются в общий пул значений слоя, поэтому значение, которое встречается у многих возможностей,
сохраняется только один раз.

<div id="naming-the-properties-tuple">
  ### Именование кортежа `properties`
</div>

Кортеж `properties` должен иметь явно заданные имена элементов. Псевдонимы столбцов внутри `tuple(...)` **не** становятся именами элементов кортежа, поэтому задавайте имена элементов с помощью приведения типа:

```sql theme={null}
tuple(count(), any(id))::Tuple(cluster_count UInt64, id String)
```

<div id="clustering">
  ### Кластеризация
</div>

Кластеризация задаётся в SQL, а не самой функцией. Поскольку `MVTEncodeGeom` округляет до целых пикселей, группировка по
пиксельной геометрии объединяет совпадающие геометрические объекты; выполните агрегацию в подзапросе, а затем передайте в
`MVTEncode` по одной строке на каждый кластер:

```sql theme={null}
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()`), чтобы выводить по одной возможности на каждую входную строку
вместо кластеризованных возможностей.

<div id="mvtboundingbox">
  ## MVTBoundingBox
</div>

Возвращает географический ограничивающий прямоугольник тайла slippy-map, заданного `zoom`, `tile_x` и `tile_y`, в виде кортежа
`(min_lon, min_lat, max_lon, max_lat)` в градусах.

Используйте эту функцию, чтобы ограничить строки тайлом, фильтруя напрямую по столбцам `longitude`/`latitude`, — так можно использовать первичный ключ или
индекс по этим столбцам — вместо того чтобы заново вычислять проекцию Web Mercator для каждой строки. Необязательный параметр `margin`
расширяет ограничивающий прямоугольник с каждой стороны на указанную долю размера тайла; установите его в `buffer / extent`, чтобы охватить буфер отсечения
`MVTEncodeGeom`.

**Синтаксис**

```sql theme={null}
MVTBoundingBox(zoom, tile_x, tile_y[, margin])
```

**Аргументы**

* `zoom` — Уровень масштабирования Slippy Map, в диапазоне `[0, 32]`. [`UInt8`](/ru/reference/data-types/int-uint).
* `tile_x` — Индекс столбца тайла, в диапазоне `[0, 2^zoom - 1]`. [`UInt32`](/ru/reference/data-types/int-uint).
* `tile_y` — Индекс строки тайла, в диапазоне `[0, 2^zoom - 1]`. [`UInt32`](/ru/reference/data-types/int-uint).
* `margin` — Необязательная доля размера тайла, на которую ограничивающий прямоугольник расширяется с каждой стороны. По умолчанию `0`. [`Float64`](/ru/reference/data-types/float).

**Возвращаемое значение**

Возвращает ограничивающий прямоугольник тайла в виде кортежа `(min_lon, min_lat, max_lon, max_lat)` в градусах. [`Tuple(Float64, Float64, Float64, Float64)`](/ru/reference/data-types/tuple).

**Пример**

```sql theme={null}
SELECT MVTBoundingBox(0, 0, 0) AS bbox
```

```text theme={null}
┌─bbox────────────────────────────────────────────┐
│ (-180,-85.05112877980659,180,85.05112877980659)  │
└──────────────────────────────────────────────────┘
```

<div id="mvtboundingboxmercator">
  ## MVTBoundingBoxMercator
</div>

Эквивалент `MVTBoundingBox` для Web Mercator. Возвращает
ограничивающий прямоугольник тайла в полном координатном пространстве Web Mercator с `UInt32`, которое внутренне использует `MVTEncodeGeom`, в виде кортежа
`(min_x, min_y, max_x, max_y)`. Ось y направлена вниз (север сверху). Предназначена для таблиц, которые материализуют
столбцы с координатами Mercator и индексируют их вместо `longitude`/`latitude`.

**Синтаксис**

```sql theme={null}
MVTBoundingBoxMercator(zoom, tile_x, tile_y[, margin])
```

**Аргументы**

То же, что и у [`MVTBoundingBox`](#mvtboundingbox).

**Возвращаемое значение**

Возвращает ограничивающий прямоугольник тайла в виде кортежа `(min_x, min_y, max_x, max_y)` в координатах проекции Web Mercator. [`Tuple(Float64, Float64, Float64, Float64)`](/ru/reference/data-types/tuple).

**Пример**

```sql theme={null}
SELECT MVTBoundingBoxMercator(1, 0, 0) AS bbox
```

```text theme={null}
┌─bbox────────────────────────┐
│ (0,0,2147483648,2147483648)  │
└──────────────────────────────┘
```

<div id="restricting-rows-to-a-tile">
  ## Ограничение строк рамками тайла
</div>

Тайл должен содержать только ту геометрию, которая к нему относится. Лучше всего это выразить двумя взаимодополняющими шагами: недорогим
предикатом ограничивающего прямоугольника, использующим индекс, в предложении `WHERE` (для производительности) и обрезкой в `MVTEncodeGeom` (для корректности).
Обрезка отсекает геометрию за пределами тайла, поэтому даже неточный предикат ограничивающего прямоугольника не даст геометрии вне тайла попасть
в результат.

```sql theme={null}
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`.

<div id="serving-tiles-over-http">
  ## Раздача тайлов по HTTP
</div>

ClickHouse по умолчанию не предоставляет конечную точку для тайлов: HTTP-интерфейс принимает запросы только по адресу `/`. Удобный
URL `/tile/{z}/{x}/{y}` добавляется оператором с помощью [обработчика предопределённого запроса](/ru/concepts/features/interfaces/http) в
конфигурации сервера. `url` обработчика использует форму `regex:` для захвата сегментов пути, связывает их с
параметрами запроса и возвращает байты в формате `FORMAT RawBLOB`.

В самом простом случае таблица содержит столбец `Geometry`, а обработчик отдаёт по одной возможности на строку — `MVTEncodeGeom`
проецирует каждую геометрию в запрошенный тайл и обрезает её, поэтому строки вне тайла автоматически отбрасываются:

```xml theme={null}
<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, …)`. Чтобы кластеризовать совпадающие возможности или добавить для больших таблиц предварительный фильтр по ограничивающему прямоугольнику с использованием индекса,
расширьте внутренний запрос, как показано в разделах [Кластеризация](#clustering) и
[Ограничение строк пределами тайла](#restricting-rows-to-a-tile).

<div id="limitations">
  ## Ограничения
</div>

* Проекция Web Mercator ограничивает широту значениями `±85.05112878°` и не поддерживает входные данные, пересекающие антимеридиан.

* **Обрезка полигонов не гарантирует выходные данные, корректные для MVT.** Обрезка исправляет ориентацию и замыкание контуров, но не самопересечения. Поэтому самопересекающийся контур («галстук-бабочка») не исправляется: в зависимости от того, как он пересекает тайл, он либо выводится без изменений (и остаётся некорректным), либо заменяется на `NULL`. Например, контур в форме галстука-бабочки, полностью лежащий внутри тайла, заменяется на `NULL`, тогда как те же четыре угла, заданные как простой контур, сохраняются:

```sql theme={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`, который при подходе с округлением перед обрезкой был бы сохранён:

```sql theme={null}
-- 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)
```
