Работа с различными TMS

TMS (Tile Map Service) представляет собой стандарт публикации тайловых карт, в котором изображение карты разбивается на квадратные тайлы фиксированного размера и организуется по иерархии уровней масштабирования. Каждый уровень содержит сетку тайлов, где координаты определяются тройкой значений:

  • z — уровень масштабирования
  • x — индекс столбца
  • y — индекс строки

Главная особенность TMS заключается в способе нумерации оси Y: отсчёт начинается снизу вверх, то есть нулевой индекс соответствует нижнему ряду тайлов.

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


Отличие TMS от XYZ

Несмотря на схожесть структуры, TMS и XYZ используют различную систему координат по оси Y.

XYZ (Web Mercator Tiles)

В большинстве веб-картографических сервисов используется схема XYZ, где:

  • (0,0) находится в верхнем левом углу
  • Y увеличивается сверху вниз

Пример URL:

/tiles/{z}/{x}/{y}.png

TMS

В TMS система координат по вертикали перевёрнута:

  • (0,0) находится в нижнем левом углу
  • Y увеличивается снизу вверх

Пример URL:

/tiles/{z}/{x}/{-y-1}.png

Именно это различие требует преобразования координаты при интеграции TMS в OpenLayers.


Координатная система тайлов

Для уровня zoom = z количество тайлов по каждой оси определяется как:

n = 2^z

Для XYZ:

y_xyz = y

Для TMS:

y_tms = (2^z - 1) - y_xyz

Эта формула используется при построении URL тайлов и является ключевым моментом при подключении TMS-источников.


Настройка TileGrid в OpenLayers

В OpenLayers управление тайловой сеткой осуществляется через TileGrid. Для корректной работы с TMS важно синхронизировать:

  • проекцию (обычно EPSG:3857)
  • уровни масштабов
  • размер тайла (обычно 256×256)

Пример базовой конфигурации:

import TileGrid from 'ol/tilegrid/TileGrid';

const tileGrid = new TileGrid({
  extent: [-20037508.342789244, -20037508.342789244,
            20037508.342789244, 20037508.342789244],
  resolutions: Array.from({ length: 19 }, (_, z) =>
    156543.03392804097 / Math.pow(2, z)
  ),
  tileSize: 256
});

Использование XYZ источника для TMS

В OpenLayers чаще всего TMS подключается через ol/source/XYZ с переопределением функции формирования URL.

Инверсия Y в tileUrlFunction

import XYZ from 'ol/source/XYZ';

const tmsSource = new XYZ({
  tileUrlFunction: (tileCoord) => {
    const z = tileCoord[0];
    const x = tileCoord[1];
    const y = tileCoord[2];

    const flippedY = Math.pow(2, z) - y - 1;

    return `https://example.com/tiles/${z}/${x}/${flippedY}.png`;
  }
});

Здесь ключевым элементом является вычисление flippedY, обеспечивающее совместимость с TMS-структурой.


Подключение TMS слоя в карту

После определения источника создаётся слой:

import TileLayer from 'ol/layer/Tile';
import Map from 'ol/Map';

const tmsLayer = new TileLayer({
  source: tmsSource
});

const map = new Map({
  target: 'map',
  layers: [tmsLayer]
});

OpenLayers автоматически запрашивает тайлы только для текущей области видимости и нужного масштаба.


Работа с TileImage и кастомными источниками

В случаях, когда требуется более гибкая логика (например, нестандартные проекции или серверные API), используется TileImage.

import TileImage from 'ol/source/TileImage';

const customTmsSource = new TileImage({
  tileGrid: tileGrid,
  tileUrlFunction: (tileCoord) => {
    const z = tileCoord[0];
    const x = tileCoord[1];
    const y = tileCoord[2];

    const flippedY = (1 << z) - y - 1;

    return `/tms/${z}/${x}/${flippedY}.png`;
  }
});

Использование побитового сдвига (1 << z) является оптимизированной альтернативой Math.pow(2, z).


Особенности работы с локальными TMS-серверами

Многие локальные картографические серверы (например, Mapnik, GeoServer, MapProxy) предоставляют TMS-эндпоинты с фиксированной структурой URL.

Типичная структура:

/tms/1.0.0/{layer}/{z}/{x}/{y}.png

Однако важно учитывать, что некоторые серверы уже возвращают TMS-координаты, и инверсия Y в клиенте не требуется. В таких случаях дополнительное преобразование приводит к ошибкам отображения (сдвиг тайлов по вертикали).


TileGrid и уровни разрешений

Корректная работа TMS зависит от точного соответствия разрешений карты и уровней сервера.

Распространённая ошибка — несоответствие resolution массива:

const resolutions = [
  156543.03392804097,
  78271.51696402048,
  39135.75848201024
];

Если сервер использует нестандартные масштабы, визуальные артефакты проявляются в виде:

  • размытых тайлов
  • несостыковки границ
  • смещения объектов на стыках уровней zoom

Кэширование тайлов

OpenLayers поддерживает встроенное кэширование через механизмы tileCache, которые уменьшают количество повторных запросов.

Важные аспекты:

  • кэш зависит от tileCoord (z/x/y)
  • изменение tileUrlFunction приводит к инвалидации кэша
  • разные источники не разделяют кэш автоматически

При работе с TMS часто возникает необходимость контролировать заголовки HTTP:

  • Cache-Control
  • ETag
  • Expires

Эти параметры определяют поведение браузерного кэша и CDN.


Обработка ошибок загрузки тайлов

При работе с TMS возможны ситуации отсутствия тайла на сервере. OpenLayers обрабатывает это через события источника:

tmsSource.on('tileloaderror', (event) => {
  const tile = event.tile;
  tile.setState(3);
});

Статус 3 соответствует ошибке загрузки.

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


Преобразование нестандартных TMS схем

Некоторые сервисы используют модифицированную TMS-схему:

  • инверсия Y + смещение
  • нестандартный origin (не в углу)
  • разделение по регионам

Обобщённая формула преобразования:

y_final = origin_offset - y_xyz

или комбинация:

y_final = (2^z - 1) - y_xyz + offset

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


Производительность при работе с TMS

Оптимизация загрузки тайлов достигается за счёт:

  • ограничения числа одновременно загружаемых тайлов
  • использования локального кэша браузера
  • правильной настройки TileGrid
  • минимизации перерисовок слоя

Дополнительный эффект даёт использование preload:

const map = new Map({
  preload: 4
});

Это позволяет заранее загружать тайлы соседних уровней.


Синхронизация TMS с проекцией карты

Наиболее часто используется проекция EPSG:3857. При работе с TMS важно, чтобы серверные тайлы были заранее сгенерированы именно в этой проекции.

Если сервер использует EPSG:4326, возникают:

  • искажения на высоких широтах
  • несоответствие масштаба
  • смещение сетки тайлов

В таких случаях требуется явное задание проекции слоя и трансформации координат через ol/proj.


Использование нескольких TMS-источников

Комбинирование нескольких TMS слоёв требует строгого согласования:

  • одинаковый TileGrid
  • совпадающие resolutions
  • идентичные origin и extent

Пример наложения:

const baseLayer = new TileLayer({ source: baseTms });
const overlayLayer = new TileLayer({ source: overlayTms });

map.addLayer(baseLayer);
map.addLayer(overlayLayer);

Несовпадение параметров приводит к визуальному расслоению данных.


Динамическая генерация URL тайлов

При сложных системах хранения тайлов используется динамическая генерация URL:

tileUrlFunction: (tileCoord) => {
  const [z, x, y] = tileCoord;
  const flippedY = (1 << z) - 1 - y;

  const shard = (x + y) % 4;

  return `https://tile${shard}.server.com/${z}/${x}/${flippedY}.png`;
}

Шардирование распределяет нагрузку между несколькими поддоменами, увеличивая параллелизм загрузки.


Объединение TMS с векторными слоями

TMS-слои часто используются как базовая подложка для векторных данных:

  • GeoJSON
  • VectorTile
  • WFS

OpenLayers обеспечивает независимую отрисовку слоёв, где TMS выступает фоном, а векторные данные накладываются поверх через Canvas или WebGL рендерер.

Конфликты обычно возникают только при несовпадении проекций или z-index слоёв.