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

Тайлы являются основой большинства веб-карт. Вместо загрузки одного большого изображения карта разбивается на множество небольших квадратов, обычно размером 256×256 или 512×512 пикселей. Leaflet автоматически загружает только те тайлы, которые необходимы для отображения текущей области карты.

Классическая схема предполагает наличие заранее подготовленного набора тайлов, размещённых на сервере. Однако в ряде задач возникает необходимость генерировать тайлы непосредственно во время запроса:

  • отображение оперативно изменяющихся данных;
  • визуализация результатов вычислений;
  • создание тематических карт на основе пользовательских параметров;
  • отображение данных мониторинга в реальном времени;
  • генерация картографических слоёв из баз данных;
  • формирование аналитических тепловых карт и статистических распределений.

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


Принцип работы тайловой системы

При перемещении карты Leaflet вычисляет координаты необходимых тайлов и отправляет запросы на сервер.

Типичный URL тайла имеет вид:

https://server.com/tiles/{z}/{x}/{y}.png

Где:

Параметр Описание
z уровень масштабирования
x координата тайла по горизонтали
y координата тайла по вертикали

При масштабе z=0 существует только один тайл, покрывающий весь мир.

На каждом следующем уровне масштабирования количество тайлов увеличивается в четыре раза:

z=0 → 1 тайл
z=1 → 4 тайла
z=2 → 16 тайлов
z=3 → 64 тайла
...

Leaflet автоматически подставляет значения {z}, {x} и {y} в URL-шаблон.

Пример подключения динамического слоя:

L.tileLayer('/tiles/{z}/{x}/{y}.png', {
    maxZoom: 18
}).addTo(map);

После этого запросы начинают поступать на сервер по мере перемещения карты.


Архитектура динамической генерации

Система обычно состоит из трёх компонентов:

Клиент

Leaflet отображает карту и запрашивает необходимые тайлы.

const map = L.map('map').setView([55.75, 37.61], 10);

L.tileLayer('/tiles/{z}/{x}/{y}.png').addTo(map);

Сервер

Принимает запросы:

/tiles/10/617/320.png

Извлекает координаты тайла и генерирует изображение.

Источник данных

Может использоваться:

  • PostgreSQL/PostGIS;
  • MongoDB;
  • Redis;
  • внешние API;
  • файлы GeoJSON;
  • потоковые данные датчиков.

Создание собственного класса тайлов

Leaflet позволяет генерировать тайлы непосредственно на стороне клиента.

Для этого используется наследование от L.GridLayer.

Простейший пример:

const DynamicLayer = L.GridLayer.extend({

    createTile: function(coords) {

        const tile = document.createElement('canvas');

        tile.width = 256;
        tile.height = 256;

        const ctx = tile.getContext('2d');

        ctx.fillStyle = '#4caf50';
        ctx.fillRect(0, 0, 256, 256);

        ctx.fillStyle = 'white';
        ctx.font = '20px Arial';

        ctx.fillText(
            `${coords.z}/${coords.x}/${coords.y}`,
            20,
            40
        );

        return tile;
    }

});

const layer = new DynamicLayer();

layer.addTo(map);

Каждый тайл создаётся как элемент Canvas и отображает собственные координаты.


Метод createTile()

Основой динамической генерации является метод createTile().

Сигнатура:

createTile(coords, done)

Параметры:

Параметр Назначение
coords координаты тайла
done функция завершения асинхронной генерации

Свойства объекта coords:

coords.x
coords.y
coords.z

Пример:

createTile(coords) {

    console.log(coords.x);
    console.log(coords.y);
    console.log(coords.z);

}

Асинхронная генерация тайлов

Иногда построение изображения требует времени.

Например:

  • получение данных через API;
  • запрос к базе данных;
  • сложные вычисления;
  • генерация графики.

В этом случае используется колбэк done.

const AsyncLayer = L.GridLayer.extend({

    createTile: function(coords, done) {

        const tile = document.createElement('canvas');

        tile.width = 256;
        tile.height = 256;

        setTimeout(() => {

            const ctx = tile.getContext('2d');

            ctx.fillStyle = 'blue';
            ctx.fillRect(0, 0, 256, 256);

            done(null, tile);

        }, 500);

        return tile;
    }

});

Leaflet дождётся завершения операции и затем отобразит тайл.


Использование Canvas для генерации

Canvas является наиболее популярным способом динамического построения тайлов.

Создание фона:

ctx.fillStyle = '#eeeeee';
ctx.fillRect(0, 0, 256, 256);

Отрисовка линии:

ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(256, 256);
ctx.stroke();

Отрисовка окружности:

ctx.beginPath();
ctx.arc(128, 128, 50, 0, Math.PI * 2);
ctx.fill();

Текст:

ctx.font = '18px Arial';
ctx.fillText('Tile', 20, 30);

Подобный подход позволяет создавать практически любые картографические элементы.


Генерация тайлов на основе данных

Предположим, сервер возвращает информацию о плотности населения.

Запрос:

fetch(`/density/${coords.z}/${coords.x}/${coords.y}`)

Ответ:

{
  "density": 78
}

Построение цвета:

if (density < 20)
    color = '#81c784';
else if (density < 50)
    color = '#ffeb3b';
else
    color = '#f44336';

Отрисовка:

ctx.fillStyle = color;
ctx.fillRect(0, 0, 256, 256);

В результате каждый тайл отражает актуальное состояние данных.


Серверная генерация тайлов

Чаще всего генерация выполняется на сервере.

Схема работы:

Leaflet
   ↓
Запрос тайла
   ↓
Сервер
   ↓
База данных
   ↓
Формирование изображения
   ↓
PNG
   ↓
Leaflet

Запрос:

GET /tiles/8/145/92.png

Ответ:

image/png

Такой подход снимает нагрузку с браузера и позволяет использовать большие объёмы данных.


Пример на Node.js

Маршрут обработки тайла:

app.get('/tiles/:z/:x/:y.png', async (req, res) => {

    const z = Number(req.params.z);
    const x = Number(req.params.x);
    const y = Number(req.params.y);

    const canvas = createCanvas(256, 256);

    const ctx = canvas.getContext('2d');

    ctx.fillStyle = '#2196f3';
    ctx.fillRect(0, 0, 256, 256);

    ctx.fillStyle = 'white';

    ctx.fillText(
        `${z}/${x}/${y}`,
        20,
        40
    );

    res.type('png');
    res.send(canvas.toBuffer());

});

Каждый запрос приводит к созданию нового изображения.


Работа с GeoJSON внутри тайлов

Нередко данные хранятся в формате GeoJSON.

Пример объекта:

{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [37.61, 55.75]
  }
}

Во время генерации тайла выполняются действия:

  1. Определение границ тайла.
  2. Поиск объектов внутри области.
  3. Проекция координат.
  4. Отрисовка на Canvas.

Таким способом формируются полноценные тематические карты.


Генерация тепловых карт

Тепловые карты особенно хорошо подходят для динамических тайлов.

Алгоритм:

  1. Получение точек.
  2. Расчёт интенсивности.
  3. Формирование градиента.
  4. Отрисовка изображения.

Условный пример:

points.forEach(point => {

    ctx.beginPath();

    ctx.arc(
        point.x,
        point.y,
        20,
        0,
        Math.PI * 2
    );

    ctx.fill();

});

Полученный тайл отображает плотность распределения данных.


Кэширование динамических тайлов

Без кэширования производительность быстро снижается.

Каждый тайл определяется тройкой координат:

z/x/y

Пример ключа:

12/2470/1328

Если тайл уже был построен, сервер возвращает сохранённую версию.

Варианты хранения:

  • память процесса;
  • Redis;
  • файловая система;
  • CDN;
  • облачные хранилища.

Контроль срока жизни кэша

Для изменяющихся данных важно правильно выбирать TTL.

Примеры:

Тип данных TTL
Спутниковые снимки месяцы
Дорожная сеть недели
Прогноз погоды часы
Телеметрия датчиков минуты
Биржевые данные секунды

Слишком большой TTL приводит к устареванию данных.

Слишком маленький увеличивает нагрузку на сервер.


Использование WebGL

При большом количестве объектов Canvas может стать узким местом.

Альтернативой является WebGL.

Преимущества:

  • аппаратное ускорение;
  • высокая скорость рендеринга;
  • поддержка миллионов объектов;
  • эффективная работа с анимацией.

Внутри тайла могут использоваться:

const gl = canvas.getContext('webgl');

После чего отрисовка выполняется средствами графического процессора.


Ограничение числа запросов

Во время масштабирования Leaflet способен инициировать десятки запросов одновременно.

Полезно применять:

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

Пример стратегии:

Максимум 20 одновременных запросов
Остальные помещаются в очередь

Это предотвращает перегрузку сервера.


Предварительная генерация

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

Этапы:

  1. Ночью создаются основные тайлы.
  2. Днём обновляются только изменившиеся области.
  3. Leaflet получает уже готовые изображения.

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


Обработка ошибок генерации

Во время построения тайла могут возникать ошибки:

  • недоступность базы данных;
  • сбой API;
  • нехватка памяти;
  • превышение времени выполнения.

Пример:

createTile(coords, done) {

    try {

        const tile =
            document.createElement('canvas');

        done(null, tile);

        return tile;

    } catch(error) {

        done(error);

    }

}

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


Оптимизация размера изображений

Даже динамически создаваемые тайлы желательно делать компактными.

Основные методы:

  • уменьшение глубины цвета;
  • оптимизация PNG;
  • использование WebP;
  • исключение лишних прозрачностей;
  • сокращение количества графических примитивов.

Например:

PNG 256×256 → 35 КБ
WebP 256×256 → 12 КБ

Разница становится существенной при тысячах запросов.


Мониторинг производительности

Для динамических тайлов полезно отслеживать:

  • время генерации;
  • число запросов;
  • частоту попаданий в кэш;
  • нагрузку на процессор;
  • потребление памяти.

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

Среднее время генерации: 18 мс
Кэш-хит: 92%
Запросов в минуту: 1400

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


Практические сценарии применения

Карты мониторинга транспорта

Каждый тайл содержит актуальные позиции транспорта, полученные из телеметрии.

Метеорологические карты

Генерация осадков, температуры и ветровых потоков в режиме реального времени.

Геоаналитика

Отображение статистики продаж, населения или посещаемости по регионам.

Системы наблюдения

Построение карт на основе данных датчиков, камер и IoT-устройств.

Научная визуализация

Представление результатов моделирования и пространственных вычислений.

Военные и диспетчерские системы

Формирование оперативной обстановки на основе постоянно изменяющихся данных.

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