MapLibre GL JS опирается на векторные и растровые тайлы, каждый из которых загружается по мере необходимости в зависимости от текущего viewport, зума и состояния стиля. Основная причина избыточной нагрузки — неконтролируемое расширение источников данных и слоёв, приводящее к лавинообразному росту HTTP-запросов.
Ключевой механизм оптимизации заключается в ограничении числа
одновременно активных источников (sources) и слоёв
(layers). Каждый источник в стиле может инициировать
собственные запросы к тайловому серверу, поэтому архитектура стиля
должна быть строго нормализована.
Практика снижения количества запросов:
minzoom и maxzoom на уровне
источникаmap.addSource('roads', {
type: 'vector',
tiles: ['https://tiles.example.com/roads/{z}/{x}/{y}.pbf'],
minzoom: 5,
maxzoom: 14
});
Чем уже диапазон зумов, тем меньше вероятность загрузки лишних тайлов при периферийных уровнях масштаба.
Фильтрация через filter в слоях MapLibre выполняется на
стороне клиента после загрузки тайла. Избыточная или сложная фильтрация
увеличивает нагрузку на main thread и снижает FPS при взаимодействии с
картой.
Рекомендации:
any, all, вложенные условия)Плохая практика:
filter: ['all',
['==', ['get', 'type'], 'road'],
['>', ['get', 'priority'], 3],
['in', ['get', 'status'], 'active', 'planned', 'construction']
]
Если такие условия часто изменяются, выгоднее разделить данные на несколько слоёв уже в tileset.
Каждый source в стиле MapLibre — потенциальный источник
сетевой активности. Избыточное дробление данных приводит к росту
количества параллельных запросов и перегрузке браузера.
Оптимальная стратегия:
promoteId для унификации идентификаторов
вместо дублирующих источниковmap.addSource('buildings', {
type: 'vector',
tiles: ['https://tiles.example.com/buildings/{z}/{x}/{y}.pbf'],
promoteId: 'id'
});
promoteId снижает необходимость дополнительных запросов
на уточнение атрибутов объектов, так как идентификатор становится
стабильным.
MapLibre GL JS использует внутренний кэш тайлов, однако эффективность кэширования зависит от стабильности URL и стратегии серверной отдачи данных.
Критические условия эффективного кэша:
Cache-Control и
ETagПлохой паттерн:
/tiles/{z}/{x}/{y}.pbf?timestamp=123456
Любое изменение параметра инвалидирует кэш и приводит к повторным запросам.
Многие лишние запросы косвенно вызваны частыми обновлениями стиля или
данных при событиях карты (move, moveend,
zoom, render). Неправильная привязка
обработчиков приводит к постоянным пересборкам источников и повторной
загрузке тайлов.
Оптимизация заключается в строгом разграничении событий:
move — только визуальные изменения без запросовmoveend — триггер для загрузки дополнительных
данныхidle — безопасная точка для тяжёлых операцийПример анти-паттерна:
map.on('move', () => {
updateData(); // вызывает новые запросы на каждый пиксель движения
});
Оптимизированный вариант:
map.on('moveend', () => {
updateData();
});
queryRenderedFeaturesМетод queryRenderedFeatures может быть источником
значительной нагрузки при частом вызове во время перемещения карты или
взаимодействия с курсором. Несмотря на то что он работает локально, он
инициирует обход внутренних структур тайлов и слоёв.
Типичная проблема — вызов на каждое событие
mousemove.
Оптимизация:
let timeout;
map.on('mousemove', (e) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
const features = map.queryRenderedFeatures(e.point);
process(features);
}, 100);
});
Снижение частоты обращений уменьшает нагрузку на main thread и ускоряет реакцию интерфейса.
GeoJSON источники особенно чувствительны к размеру данных, так как обрабатываются полностью в браузере. При росте количества объектов резко увеличивается стоимость парсинга и отрисовки.
Методы оптимизации:
cluster для точечных данныхПример кластеризации:
map.addSource('points', {
type: 'geojson',
data: 'https://example.com/points.geojson',
cluster: true,
clusterMaxZoom: 12,
clusterRadius: 50
});
Кластеризация снижает количество отрисовываемых объектов на высоких уровнях зума и уменьшает число операций отрисовки.
Каждое изменение layout или paint свойств
слоя вызывает перерасчёт стиля и может приводить к повторной выборке
данных. Частые динамические изменения стилей создают эффект
псевдо-постоянных запросов.
Рекомендации:
setPaintProperty в
циклахbatch-обновленияfeature-state вместо прямой модификации
слоёвmap.setFeatureState(
{ source: 'buildings', id: 42 },
{ hover: true }
);
feature-state обновляет визуализацию без перезагрузки
тайлов и без сетевых запросов.
Каждый слой в стиле увеличивает стоимость рендеринга и косвенно
влияет на количество обращений к данным источников. Особенно это
критично при использовании большого количества символических
(symbol) слоёв.
Стратегии оптимизации:
minzoom / maxzoom для
отключения слоёв вне диапазонаtext-field выражений с
динамическими даннымиПример ограничения:
{
id: 'labels',
type: 'symbol',
source: 'places',
minzoom: 10,
maxzoom: 18,
layout: {
'text-field': ['get', 'name']
}
}
Функция transformRequest позволяет перехватывать все
сетевые запросы MapLibre GL JS. Это ключевой инструмент для оптимизации,
позволяющий внедрять кастомный кэш, подписывать запросы или
перенаправлять их на CDN.
Пример снижения нагрузки через добавление заголовков:
const map = new maplibregl.Map({
container: 'map',
style: 'style.json',
transformRequest: (url, resourceType) => {
return {
url,
headers: {
'X-Requested-With': 'MapLibre'
}
};
}
});
Также возможно маршрутизировать запросы к локальному кэшу или сервис-воркеру, снижая количество сетевых обращений.
Пересекающиеся источники данных часто приводят к повторной загрузке одинаковых тайлов разными слоями. Это особенно заметно при использовании нескольких стилей или динамическом добавлении источников.
Оптимальное решение — унификация tileset и переиспользование одного source для всех слоёв, работающих с одной географической областью.
MapLibre GL JS использует внутренний приоритет загрузки тайлов, основанный на расстоянии от центра экрана и текущем зуме. Однако при большом количестве источников этот механизм может быть перегружен.
Снижение нагрузки достигается:
renderWorldCopies при глобальных
картахmap.setLayoutProperty('layer-id', 'visibility', 'none');
Отключённые слои не участвуют в запросах и рендеринге, что напрямую уменьшает сетевую активность.