Оптимизация количества маркеров

В Google Maps JavaScript API каждый маркер (google.maps.Marker) является отдельным DOM-подобным объектом, который участвует в рендеринге, обработке событий и пересчёте позиций при каждом изменении карты. При увеличении количества маркеров до сотен и тысяч начинают проявляться типичные проблемы:

  • рост времени первичного рендера карты;
  • увеличение нагрузки на CPU при панорамировании и зуме;
  • деградация отклика интерфейса при обработке событий mouseover, click;
  • рост потребления памяти из-за хранения состояния каждого маркера;
  • частые перерасчёты позиций в проекции Web Mercator.

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


Базовый принцип оптимизации

Основная стратегия работы с большим количеством маркеров заключается в одном принципе:

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

Отсюда вытекают три направления оптимизации:

  • уменьшение числа одновременно отображаемых объектов;
  • замена маркеров на агрегированные структуры;
  • перенос части логики на сервер или предобработку данных.

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

Наиболее распространённый подход — кластеризация.

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

Для Google Maps JavaScript API чаще всего используется библиотека MarkerClusterer:

  • MarkerClustererPlus

Принцип работы кластеризации

Алгоритм работает в несколько этапов:

  1. Определяется текущий viewport карты.
  2. Все маркеры преобразуются в пиксельные координаты.
  3. Близкие точки группируются по радиусу кластеризации.
  4. Вместо множества маркеров создаётся один кластерный объект.
  5. При изменении zoom пересчёт выполняется заново.

Практическая структура кластера

Кластер обычно содержит:

  • количество точек внутри;
  • геометрический центр (centroid);
  • список дочерних маркеров (или их идентификаторы);
  • уровень зума, на котором кластер должен распадаться.

Эффект оптимизации

При корректной настройке:

  • 10 000 точек могут отображаться как 50–200 кластеров;
  • количество DOM-объектов уменьшается на порядок;
  • снижается нагрузка на события и перерисовку.

Ограничение количества маркеров по viewport

Альтернативный или дополнительный метод — отрисовка только видимой области карты.

Логика фильтрации

При каждом событии:

  • bounds_changed
  • idle

выполняется проверка попадания точки в текущие границы:

  • северо-восточная и юго-западная границы viewport;
  • сравнение координат маркеров с bounds.

Только попавшие в диапазон элементы создаются и отображаются.


Преимущество подхода

  • минимальное количество активных объектов;
  • отсутствие необходимости держать всю коллекцию в DOM;
  • естественная масштабируемость.

Ограничение

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


Серверная агрегация данных

При работе с десятками или сотнями тысяч точек оптимальнее переносить обработку на сервер.

Подход

  1. Клиент отправляет:

    • bounding box карты;
    • уровень зума;
  2. Сервер возвращает:

    • только точки внутри области;
    • или уже агрегированные кластеры.

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

  • минимизация трафика;
  • снижение нагрузки на браузер;
  • возможность сложной гео-агрегации (grid clustering, H3, geohash).

Типичные методы агрегации

  • geohash-группировка;
  • hexagonal binning (H3);
  • квадратная сетка (grid index).

Использование уровней детализации (LOD)

LOD (Level of Detail) позволяет менять представление данных в зависимости от масштаба.

Пример логики:

  • zoom 3–6: страны / регионы;
  • zoom 7–10: города;
  • zoom 11–14: районы;
  • zoom 15–20: отдельные объекты.

Эффект

LOD полностью заменяет тысячи маркеров несколькими слоями данных.


Замена маркеров на кастомные overlay-слои

Стандартные маркеры являются тяжёлым объектом. Альтернативой выступают:

  • OverlayView;
  • Canvas-слои;
  • WebGL-рендеринг.

Canvas-рендеринг

При использовании canvas:

  • все точки рисуются в одном холсте;
  • отсутствует создание DOM-узлов;
  • отрисовка происходит батчами.

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

  • десятки тысяч точек без падения FPS.

Недостаток:

  • сложная обработка событий (hit-testing вручную).

WebGL-подход

На больших наборах данных используется WebGL:

  • GPU-ускоренная отрисовка;
  • возможность миллиона точек;
  • минимальная нагрузка на main thread.

Снижение количества обработчиков событий

Каждый маркер в Google Maps JavaScript API может иметь собственные обработчики:

  • click
  • mouseover
  • mouseout

При тысячах маркеров это создаёт значительную нагрузку.


Оптимизация через делегирование событий

Вместо навешивания событий на каждый маркер:

  • используется один глобальный обработчик;
  • определяется объект по координатам курсора;
  • выполняется lookup в структуре данных.

Lazy rendering (ленивая отрисовка)

Подход заключается в том, что маркеры создаются только:

  • при входе в viewport;
  • при достижении определённого zoom;
  • при остановке движения карты (idle).

Техническая схема

  1. Пользователь двигает карту.
  2. Маркеры временно скрываются.
  3. После стабилизации viewport выполняется пересчёт.
  4. Отображаются только релевантные объекты.

Использование простых иконок вместо сложных DOM-структур

Производительность сильно зависит от сложности маркера.

Медленные варианты:

  • HTML-маркеры (AdvancedMarkerElement с DOM);
  • кастомные SVG с анимацией;
  • сложные React/Vue компоненты внутри маркера.

Быстрые варианты:

  • bitmap-иконки;
  • заранее отрендеренные изображения;
  • минимальные SVG без фильтров.

Батчинг обновлений карты

Частая ошибка — обновление маркеров по одному.

Неправильно:

  • добавление каждого маркера отдельно;
  • последовательные вызовы setMap.

Правильно:

  • формирование массива;
  • массовое добавление;
  • единичное обновление слоя.

Дебаунсинг событий карты

События:

  • zoom_changed
  • bounds_changed
  • drag

могут вызываться десятки раз в секунду.

Решение

Используется debounce/throttle:

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

Предварительное вычисление кластеров

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

  • предрасчёт кластеров на разных zoom уровнях;
  • хранение результатов в кеше;
  • мгновенная отрисовка без вычислений на клиенте.

Итоговая архитектура высокопроизводительной карты

Оптимизированная система обычно включает:

  • серверную агрегацию;
  • LOD-структуру данных;
  • клиентскую кластеризацию;
  • ограничение viewport;
  • canvas или WebGL слой;
  • минимальное число DOM-маркеров.

Такой подход позволяет работать с геоданными объёмом в сотни тысяч или даже миллионы точек без заметной деградации интерфейса.