Тайлы являются основой большинства веб-карт. Вместо загрузки одного большого изображения карта разбивается на множество небольших квадратов, обычно размером 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
Извлекает координаты тайла и генерирует изображение.
Может использоваться:
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(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);
}
Иногда построение изображения требует времени.
Например:
В этом случае используется колбэк 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 является наиболее популярным способом динамического построения тайлов.
Создание фона:
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
Такой подход снимает нагрузку с браузера и позволяет использовать большие объёмы данных.
Маршрут обработки тайла:
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.
Пример объекта:
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [37.61, 55.75]
}
}
Во время генерации тайла выполняются действия:
Таким способом формируются полноценные тематические карты.
Тепловые карты особенно хорошо подходят для динамических тайлов.
Алгоритм:
Условный пример:
points.forEach(point => {
ctx.beginPath();
ctx.arc(
point.x,
point.y,
20,
0,
Math.PI * 2
);
ctx.fill();
});
Полученный тайл отображает плотность распределения данных.
Без кэширования производительность быстро снижается.
Каждый тайл определяется тройкой координат:
z/x/y
Пример ключа:
12/2470/1328
Если тайл уже был построен, сервер возвращает сохранённую версию.
Варианты хранения:
Для изменяющихся данных важно правильно выбирать TTL.
Примеры:
| Тип данных | TTL |
|---|---|
| Спутниковые снимки | месяцы |
| Дорожная сеть | недели |
| Прогноз погоды | часы |
| Телеметрия датчиков | минуты |
| Биржевые данные | секунды |
Слишком большой TTL приводит к устареванию данных.
Слишком маленький увеличивает нагрузку на сервер.
При большом количестве объектов Canvas может стать узким местом.
Альтернативой является WebGL.
Преимущества:
Внутри тайла могут использоваться:
const gl = canvas.getContext('webgl');
После чего отрисовка выполняется средствами графического процессора.
Во время масштабирования Leaflet способен инициировать десятки запросов одновременно.
Полезно применять:
Пример стратегии:
Максимум 20 одновременных запросов
Остальные помещаются в очередь
Это предотвращает перегрузку сервера.
Некоторые динамические слои используют гибридный подход.
Этапы:
Такой механизм обеспечивает высокую скорость работы даже при больших объёмах данных.
Во время построения тайла могут возникать ошибки:
Пример:
createTile(coords, done) {
try {
const tile =
document.createElement('canvas');
done(null, tile);
return tile;
} catch(error) {
done(error);
}
}
Leaflet корректно обработает исключение и сможет продолжить загрузку остальных тайлов.
Даже динамически создаваемые тайлы желательно делать компактными.
Основные методы:
Например:
PNG 256×256 → 35 КБ
WebP 256×256 → 12 КБ
Разница становится существенной при тысячах запросов.
Для динамических тайлов полезно отслеживать:
Типичная статистика:
Среднее время генерации: 18 мс
Кэш-хит: 92%
Запросов в минуту: 1400
Подобные показатели позволяют своевременно выявлять узкие места системы.
Каждый тайл содержит актуальные позиции транспорта, полученные из телеметрии.
Генерация осадков, температуры и ветровых потоков в режиме реального времени.
Отображение статистики продаж, населения или посещаемости по регионам.
Построение карт на основе данных датчиков, камер и IoT-устройств.
Представление результатов моделирования и пространственных вычислений.
Формирование оперативной обстановки на основе постоянно изменяющихся данных.
Динамическая генерация тайлов превращает Leaflet из средства отображения статических карт в платформу для построения интерактивных геоинформационных систем, способных визуализировать большие объёмы данных в реальном времени и адаптироваться к изменению информации непосредственно в процессе работы карты.