Производительность веб-карт зависит не только от скорости выполнения JavaScript-кода, но и от объёма геоданных, сложности стилей, количества отрисовываемых слоёв, особенностей работы WebGL и частоты обновления карты. В проектах с небольшими объёмами данных проблемы могут быть незаметны, однако при работе с десятками тысяч объектов, сложными векторными тайлами или постоянным обновлением информации производительность становится критически важным фактором.
Основные признаки проблем:
Для эффективной оптимизации необходимо понимать, какие операции являются наиболее затратными.
MapLibre GL JS использует WebGL для аппаратного ускорения визуализации карт.
Цепочка обработки данных выглядит следующим образом:
Каждый этап требует ресурсов.
Особенно дорогостоящими являются:
Если карта вынуждена повторять эти операции слишком часто, частота кадров начинает падать.
Наиболее распространённая причина снижения производительности — использование огромных GeoJSON-файлов.
Пример проблемного источника:
map.addSource('cities', {
type: 'geojson',
data: largeGeoJson
});
Файл может содержать:
Каждый объект должен быть обработан браузером, после чего геометрия передаётся в WebGL.
Особенно тяжёлыми являются:
Перед публикацией данных рекомендуется уменьшать количество вершин.
Например, полигон может содержать:
100000 вершин
После упрощения:
5000 вершин
Визуально разница может быть практически незаметной, но нагрузка на карту снижается многократно.
Для подготовки данных часто используются:
Особенно эффективно упрощение для мелких масштабов, где высокая точность геометрии всё равно не видна.
При больших объёмах данных предпочтительнее использовать векторные тайлы.
GeoJSON загружает весь набор объектов сразу:
map.addSource('roads', {
type: 'geojson',
data: 'roads.geojson'
});
Векторные тайлы загружаются частями:
map.addSource('roads', {
type: 'vector',
tiles: [
'https://server/{z}/{x}/{y}.pbf'
]
});
Преимущества:
При работе с большими картографическими проектами векторные тайлы являются фактически обязательным решением.
Не все данные необходимо показывать одновременно.
Нередко встречаются ситуации:
50000 объектов
отображаются
на масштабе z=3
На таком масштабе отдельные объекты невозможно различить, однако движок всё равно тратит ресурсы на их обработку.
Оптимальный подход:
map.addLayer({
id: 'pois',
type: 'circle',
source: 'pois',
minzoom: 10
});
Слой начнёт отображаться только после достижения указанного масштаба.
Преимущества:
Большое количество маркеров значительно снижает производительность.
Проблемный сценарий:
100000 точек
100000 подписей
Решение — кластеризация.
map.addSource('points', {
type: 'geojson',
data: points,
cluster: true,
clusterRadius: 50
});
Вместо тысяч объектов отображаются агрегированные группы.
Преимущества:
Каждый слой требует отдельного прохода рендеринга.
Плохо:
100 слоёв
Лучше:
10–20 слоёв
Часто множество слоёв можно объединить.
Например:
Вместо:
roads-primary
roads-secondary
roads-local
roads-service
Использовать:
roads
с фильтрами и выражениями.
Чем меньше слоёв, тем меньше переключений состояния WebGL и выше производительность.
MapLibre позволяет использовать мощную систему выражений.
Пример:
[
"case",
["==", ["get", "type"], "city"],
"#ff0000",
["==", ["get", "type"], "town"],
"#00ff00",
["==", ["get", "type"], "village"],
"#0000ff",
"#888888"
]
Такие конструкции вычисляются для большого количества объектов.
Особенно затратными являются:
case;При миллионах объектов даже небольшие вычисления начинают оказывать заметное влияние.
Фильтры выполняются для каждого объекта слоя.
Например:
filter: [
"all",
[">", ["get", "population"], 10000],
["==", ["get", "country"], "KZ"]
]
Если источник содержит большое количество объектов, фильтрация становится дорогой операцией.
Предпочтительно:
Каждый вызов:
source.setData(newData);
заставляет движок:
Проблемный пример:
setInterval(() => {
source.setData(data);
}, 100);
Обновление каждые 100 мс приводит к постоянной переработке данных.
Лучше:
setInterval(() => {
source.setData(data);
}, 1000);
или обновлять данные только при реальном изменении.
HTML-маркеры создаются через DOM.
Пример:
new maplibregl.Marker()
.setLngLat([71, 51])
.addTo(map);
Несколько десятков маркеров обычно не вызывают проблем.
Однако:
5000 Marker
означают:
5000 DOM-элементов
Браузеру приходится:
Для больших наборов данных предпочтительнее использовать слой:
type: 'circle'
или
type: 'symbol'
которые полностью отрисовываются через WebGL.
Подписи являются одной из самых дорогих операций.
Причины:
Пример слоя подписей:
{
type: 'symbol',
layout: {
'text-field': ['get', 'name']
}
}
При большом количестве объектов стоимость вычислений возрастает многократно.
Способы оптимизации:
Большое количество уникальных иконок приводит к увеличению потребления памяти.
Плохо:
1000 различных изображений
Лучше:
несколько десятков переиспользуемых иконок
Также рекомендуется:
Каждая анимация требует постоянного обновления кадров.
Пример:
function animate() {
map.triggerRepaint();
requestAnimationFrame(animate);
}
animate();
Такой код поддерживает непрерывный рендеринг даже тогда, когда карта статична.
Последствия:
Анимации должны запускаться только при необходимости.
Часто проблемы возникают не в самой карте, а в обработчиках событий.
Пример:
map.on('mousemove', e => {
expensiveOperation(e);
});
Событие может вызываться десятки раз в секунду.
Решения:
Например:
const throttledHandler =
throttle(handleMouseMove, 100);
map.on('mousemove', throttledHandler);
Для изменения отдельных объектов нередко выполняется полное обновление GeoJSON.
Плохо:
source.setData(data);
Лучше:
map.setFeatureState(
{
source: 'buildings',
id: featureId
},
{
selected: true
}
);
Преимущества:
Большие проекты могут сталкиваться с накоплением памяти.
Причины:
Удаление ненужных сущностей:
map.removeLayer('layer-id');
map.removeSource('source-id');
Полезно регулярно освобождать ресурсы, которые больше не используются.
Для поиска узких мест применяются инструменты браузера.
Основные инструменты:
Следует анализировать:
Без профилирования многие проблемы оказываются скрытыми и выявляются только на слабых устройствах.
Мобильные устройства обладают ограниченными ресурсами.
Наиболее эффективные меры:
Даже если карта работает быстро на настольном компьютере, это не гарантирует приемлемую производительность на смартфонах и планшетах.
При появлении проблем с производительностью рекомендуется последовательно проверить следующие пункты:
setData() слишком часто.Комплексное применение этих методов позволяет сохранять высокую частоту кадров даже при работе с крупными наборами пространственных данных и сложными интерактивными картографическими интерфейсами на базе MapLibre GL JS.