Оптимизация для продакшена

Архитектура рендеринга и ключевые узкие места

MapLibre GL JS использует WebGL-рендеринг и многопоточную архитектуру с вынесением части вычислений в воркеры. Производительность карты в продакшене определяется тремя основными слоями:

  • Сетевой уровень (загрузка тайлов, стилей, шрифтов, спрайтов)
  • Данные и стиль (GeoJSON, векторные тайлы, фильтры, выражения)
  • GPU-рендеринг (дорожки отрисовки, символы, растровые слои)

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


Оптимизация источников данных

Переход от GeoJSON к векторным тайлам

GeoJSON подходит для небольших наборов данных, но становится критическим узким местом при масштабировании.

Проблемы GeoJSON:

  • Полная пересылка данных при каждом обновлении
  • Отсутствие тайловой структуры
  • Высокая нагрузка на CPU при парсинге

Рекомендуемый подход — использование векторных тайлов (Vector Tiles).

Подготовка данных обычно выполняется через инструменты типа tippecanoe:

  • генерация MBTiles
  • предагрегация геометрии
  • упрощение геометрий по zoom level

Оптимизация GeoJSON при неизбежном использовании

Если GeoJSON остаётся частью архитектуры:

  • уменьшение количества координат (simplification)
  • удаление лишних properties
  • разбиение на чанки
  • использование generateId: true для стабильного diff

Пример:

map.addSource('points', {
  type: 'geojson',
  data: '/data/points.json',
  generateId: true
});

Упрощение геометрии и уровни детализации

Simplify по zoom уровням

Чем выше zoom — тем больше деталей можно позволить, но базовые уровни должны быть максимально облегчены.

Ключевой принцип:

  • z0–z5: сильно упрощённые геометрии
  • z6–z10: средняя детализация
  • z11+: полная детализация

Оптимизация стилей MapLibre

Минимизация количества слоёв

Каждый слой — это отдельный проход рендеринга. Избыточное количество слоёв резко снижает FPS.

Рекомендации:

  • объединение слоёв с одинаковыми стилями
  • использование filter вместо дублирования слоёв
  • отказ от дробления символов на множество категорий

Оптимизация filter и expressions

Сложные выражения вычисляются на GPU/CPU в зависимости от типа слоя.

Проблемные конструкции:

  • глубокие case-цепочки
  • вложенные match с десятками значений
  • динамические вычисления на каждом кадре

Более эффективно:

  • перенос логики в данные (preprocessing)
  • минимизация вычислений в style runtime

Layout vs Paint: правильное распределение

  • layout properties пересчитываются реже, но тяжелее
  • paint properties обновляются чаще, но дешевле

Оптимально:

  • статические параметры → layout
  • анимации и визуальные эффекты → paint

Оптимизация символов (labels, icons)

Конфликты и collision detection

Символические слои — одна из самых дорогих частей рендеринга.

Критические настройки:

  • text-allow-overlap: false
  • icon-allow-overlap: false
  • ограничение symbol-placement

Пример:

{
  id: 'labels',
  type: 'symbol',
  source: 'points',
  layout: {
    'text-field': ['get', 'name'],
    'text-size': 12,
    'text-allow-overlap': false
  }
}

Уменьшение количества символов

Практики:

  • кластеризация точек
  • агрегация на сервере
  • отображение только на определённых zoom уровнях

Кластеризация данных

Для точечных данных критично использовать кластеризацию:

map.addSource('points', {
  type: 'geojson',
  data: '/points.json',
  cluster: true,
  clusterMaxZoom: 14,
  clusterRadius: 50
});

Эффект:

  • резкое снижение количества rendered features
  • уменьшение нагрузки на layout и collision engine

Управление слоями и видимостью

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

Каждый слой должен иметь ограничение видимости:

paint: {},
minzoom: 6,
maxzoom: 14

Это снижает:

  • количество отрисовываемых объектов
  • нагрузку на GPU
  • стоимость фильтрации

Динамическое скрытие слоёв

Слои, не попадающие в viewport, должны отключаться:

map.setLayoutProperty('layer-id', 'visibility', 'none');

Оптимизация загрузки ресурсов

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

MapLibre активно использует HTTP-кэш, поэтому важно:

  • использовать стабильные URL
  • избегать query string с timestamp без необходимости
  • включать CDN

transformRequest для контроля загрузки

Позволяет внедрить авторизацию и кеширование:

transformRequest: (url, resourceType) => {
  return {
    url,
    headers: {
      'Authorization': 'Bearer TOKEN'
    }
  };
}

Сжатие и протоколы

Критично включать:

  • gzip / brotli для tiles
  • HTTP/2 или HTTP/3
  • CDN с edge caching

Оптимизация рендеринга WebGL

Управление FPS и repaint

MapLibre перерисовывает сцену при изменениях состояния. Лишние вызовы:

  • setData
  • setLayoutProperty
  • setPaintProperty

следует минимизировать.


Отключение ненужных опций карты

При статичных или полустатичных картах:

new maplibregl.Map({
  container: 'map',
  interactive: false,
  attributionControl: false
});

Оптимизация антисериализации

  • избегать preserveDrawingBuffer: true
  • использовать antialias: false для больших слоёв
  • отключать fadeDuration при ненужных переходах

Управление памятью

Утечки через Map instance

Карта должна корректно уничтожаться:

map.remove();

Игнорирование приводит к:

  • росту WebGL контекста
  • утечкам текстур
  • деградации FPS

Переиспользование источников

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

source.setData(newData);

Оптимизация шрифтов и спрайтов

Glyphs

Шрифты загружаются как SDF-тайлы. Оптимизация:

  • ограничение используемых символов
  • генерация subset fonts
  • использование одного font stack

Sprites

Спрайты должны быть:

  • атласированы
  • сжатые (WebP/PNG оптимизация)
  • кэшируемые через CDN

Ограничение области карты

Max bounds

Ограничение области снижает количество тайлов:

map.setMaxBounds([
  [longitudeMin, latitudeMin],
  [longitudeMax, latitudeMax]
]);

Оптимизация обновлений состояния

Батчинг изменений

Множественные изменения стилей должны быть сгруппированы:

Плохо:

  • 10 вызовов setLayoutProperty

Лучше:

  • единое обновление конфигурации слоя

Debounce событий

Для событий движения:

  • move
  • zoom

следует использовать debounce или throttle


Производственные паттерны архитектуры

Разделение слоёв ответственности

  • data layer (источники)
  • style layer (визуализация)
  • interaction layer (события)

Такое разделение снижает связность и облегчает оптимизацию.


Lazy loading слоёв

Слои должны загружаться:

  • только при достижении zoom
  • только при попадании в viewport
  • только по требованию пользователя

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

Performance API

Используется для анализа:

  • frame time
  • long tasks
  • layout shifts

MapLibre debug tools

map.showTileBoundaries = true;
map.showCollisionBoxes = true;

Позволяет выявить:

  • перегруженные тайлы
  • проблемные символы
  • лишние рендер-проходы

Метрики

Ключевые показатели:

  • FPS (целевой минимум 30–60)
  • tile loading time
  • number of rendered layers
  • number of visible features

Стратегии масштабирования

Серверная агрегация

Чем больше данных, тем важнее:

  • pre-aggregation
  • spatial indexing (H3, S2)
  • генерация тайлов на сервере

Progressive rendering

  • сначала упрощённые тайлы
  • затем детализация
  • затем символы

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

  • минимизация GeoJSON
  • строгая тайловая архитектура
  • контроль количества слоёв
  • агрессивная кластеризация
  • ограничение символов
  • предобработка данных на сервере
  • кэширование всего, что возможно
  • контроль repaint и событий
  • освобождение ресурсов через destroy/remove