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

MapLibre GL JS использует WebGL-пайплайн, где каждый кадр формируется через последовательность этапов: загрузка источников данных, подготовка тайлов, построение буферов геометрии, применение стилей и финальный рендеринг на GPU. Производительность в такой архитектуре определяется не одной точкой, а совокупностью узких мест: сеть, память, CPU (worker-потоки) и GPU (отрисовка слоёв).

Оптимизация рендеринга сводится к контролю количества обрабатываемых данных, снижению стоимости пересчёта стилей и минимизации количества draw calls.


Управление источниками данных и объёмом геометрии

Сокращение объёма GeoJSON

Наиболее частая причина падения FPS — перегруженные GeoJSON-источники. MapLibre обрабатывает их в worker-потоках, но дальнейший рендер всё равно зависит от итогового количества вершин.

Ключевые подходы:

  • упрощение геометрии (Douglas–Peucker или подобные алгоритмы)
  • удаление неиспользуемых свойств
  • разбиение больших коллекций на несколько источников
  • ограничение maxzoom для тяжелых слоёв
map.addSource('cities', {
  type: 'geojson',
  data: simplifiedGeojson,
  maxzoom: 10
});

Векторные тайлы вместо GeoJSON

Векторные тайлы позволяют переносить тяжёлую обработку на этап генерации тайлов, а клиенту отдавать уже сегментированные данные.

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

  • снижение CPU-нагрузки в браузере
  • эффективное кэширование
  • отрисовка только видимой области

Важно контролировать:

  • размер тайлов
  • плотность геометрии внутри тайла
  • генерацию индексов на сервере

Кластеризация точек

Для больших наборов точек кластеризация снижает количество renderable-объектов.

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

Оптимизационный эффект достигается за счёт уменьшения количества символов и collision checks.


Оптимизация слоёв и порядка отрисовки

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

Каждый слой — это отдельный проход рендеринга. Даже при одинаковом источнике несколько слоёв увеличивают количество draw calls.

Практика:

  • объединение стилей в один слой через case expressions
  • использование фильтров вместо дублирования слоёв
  • удаление скрытых слоёв из карты

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

Фильтры выполняются на CPU и могут становиться узким местом при сложных выражениях.

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

  • избегать глубоких вложенных case
  • предварительно сегментировать данные на уровне источника
  • использовать числовые поля вместо строковых сравнений
filter: ['all',
  ['>=', ['get', 'value'], 10],
  ['<', ['get', 'value'], 100]
]

Контроль порядка символов

Symbol layers используют collision detection, который является дорогой операцией.

Оптимизация:

  • разделение слоёв по важности
  • использование symbol-sort-key
  • ограничение text-optional
layout: {
  'symbol-sort-key': ['get', 'priority']
}

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

Растровые слои

Raster sources дешевле в GPU-обработке, но зависят от сети и памяти.

Оптимизация:

  • уменьшение tile size
  • использование tileSize: 256 вместо 512 при необходимости
  • агрессивное кэширование
map.addSource('tiles', {
  type: 'raster',
  tiles: ['https://example.com/tiles/{z}/{x}/{y}.png'],
  tileSize: 256
});

Векторные слои и стили

Векторные слои требуют больше CPU, но дают гибкость. Основные оптимизации:

  • уменьшение числа paint properties
  • избегание динамических выражений в paint
  • кэширование выражений через константы

GPU-нагрузка и сложность стилей

Paint свойства как источник нагрузки

Каждое динамическое выражение в paint пересчитывается при каждом кадре.

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

  • интерполяции по большим диапазонам
  • match с большим количеством условий
  • выражения с zoom + feature-state

Рекомендация — выносить вычисления в подготовку данных.


Ограничение прозрачности и смешивания

Blending операций много на GPU приводит к падению производительности:

  • перекрывающиеся полупрозрачные слои
  • сложные градиенты
  • множественные shadow эффекты

Практика:

  • уменьшение alpha-слоёв
  • использование непрозрачных фонов
  • группировка визуально похожих слоёв

Работа с кадрами и перерисовкой

Throttling изменений источников

Частые вызовы setData создают перерасчёт геометрии и пересборку буферов.

let timeout;
function updateData(data) {
  clearTimeout(timeout);
  timeout = setTimeout(() => {
    source.setData(data);
  }, 100);
}

Минимизация trigger repaint

Любое изменение:

  • layout properties
  • visibility
  • filter

может инициировать полный или частичный repaint.

Оптимизация:

  • группировать изменения через batch operations
  • избегать частых toggle слоёв
  • использовать setLayoutProperty точечно

Оптимизация шрифтов и иконок

Glyphs и текстовые слои

Загрузка glyph atlas может стать узким местом.

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

  • ограничение наборов символов через text-font
  • использование одного семейства шрифтов
  • уменьшение text-size диапазона

Sprite sheets

Иконки хранятся в sprite atlas, и его размер влияет на:

  • память GPU
  • скорость загрузки стилей

Практика:

  • объединение иконок в один sprite
  • удаление неиспользуемых ресурсов
  • использование SVG только на этапе сборки

Tile caching и сетевые оптимизации

HTTP-кэширование

MapLibre активно использует HTTP cache для тайлов.

Настройки сервера:

  • Cache-Control: max-age
  • ETag для векторных тайлов
  • CDN ближе к пользователю

TransformRequest

Позволяет уменьшать задержки и оптимизировать запросы:

transformRequest: (url) => {
  return {
    url,
    headers: {
      'x-map-client': 'web'
    }
  };
}

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

Minzoom / Maxzoom

Один из самых эффективных способов снизить нагрузку.

map.addLayer({
  id: 'buildings',
  type: 'fill',
  source: 'composite',
  'source-layer': 'building',
  minzoom: 14,
  maxzoom: 18
});

Это уменьшает количество обрабатываемых объектов на ранних zoom-уровнях.


Level of Detail (LOD)

Разделение геометрии по уровням детализации:

  • упрощённые полигоны на малых zoom
  • детализированные на больших zoom

Такой подход снижает GPU-fill rate и CPU geometry processing.


Управление состоянием feature-state

Feature-state позволяет динамически менять стиль объектов без пересборки источника, но при массовом использовании создаёт нагрузку на память.

Оптимизация:

  • ограничение количества state-обновлений
  • очистка неиспользуемых state
  • использование агрегированных состояний вместо per-feature

Профилирование и диагностика

Map rendering performance metrics

Основные показатели:

  • FPS
  • style load time
  • tile load time
  • glyph load latency
  • layer render time

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

WebGL profiling позволяет выявить:

  • дорогие draw calls
  • перегруженные shaders
  • excessive overdraw

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

Типичный цикл:

  1. замер FPS
  2. изоляция слоя
  3. отключение источника
  4. выявление узкого места
  5. упрощение данных или стиля

Снижение overdraw и оптимизация пиксельного заполнения

Overdraw возникает, когда один пиксель перерисовывается несколько раз разными слоями.

Способы снижения:

  • сортировка слоёв по прозрачности
  • уменьшение overlap геометрий
  • использование mask layers вместо нескольких fill слоёв

Оптимизация взаимодействий и событий

Частые события mousemove или render могут приводить к перегрузке логики.

Подходы:

  • debounce обработчиков
  • использование queryRenderedFeatures только при необходимости
  • ограничение области запроса через bbox

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

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

  • данных (GeoJSON / vector tiles)
  • слоёв (минимизация и объединение)
  • стилей (упрощение expressions)
  • GPU-нагрузки (контроль overdraw)
  • сети (кэширование и CDN)
  • взаимодействий (throttle/debounce)

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