OverlappingMarkerSpiderfier

Перекрывающиеся маркеры на карте становятся одной из первых практических проблем при работе с Google Maps JavaScript API, когда плотность объектов на ограниченной территории возрастает. При масштабировании карты несколько маркеров оказываются в одной и той же точке экрана или настолько близко, что пользователь не может различить их и взаимодействовать с каждым отдельно. Стандартные средства API решают задачу частично (кластеризация), но не покрывают сценарии, где требуется сохранить индивидуальные маркеры и обеспечить доступ ко всем объектам одновременно.

OverlappingMarkerSpiderfier представляет собой клиентскую библиотеку, предназначенную для «разворачивания» перекрывающихся маркеров в паукообразную структуру (spiderfy), позволяя пользователю увидеть и выбрать каждый объект по отдельности без потери контекста.


Основная идея заключается в динамическом изменении позиции маркеров, находящихся в одной географической точке или слишком близко друг к другу. Вместо наложения они временно распределяются по окружности вокруг исходной точки.

Ключевые этапы:

  • обнаружение группы маркеров в пределах одного пиксельного радиуса
  • вычисление позиций по окружности (или спирали при большом количестве элементов)
  • смещение маркеров с сохранением их географической привязки
  • возврат в исходное положение при закрытии состояния spiderfy

Важный момент: библиотека работает поверх существующих маркеров, не заменяя их и не изменяя данные слоя.


Подключение OverlappingMarkerSpiderfier

Библиотека не входит в стандартный набор API и подключается отдельно:

<script src="https://unpkg.com/overlapping-marker-spiderfier@1.0.3/lib/oms.min.js"></script>

При использовании модульной сборки:

npm install overlapping-marker-spiderfier

Далее импорт:

import OverlappingMarkerSpiderfier from 'overlapping-marker-spiderfier';

Инициализация на карте

Работа начинается после создания экземпляра карты:

const map = new google.maps.Map(document.getElementById("map"), {
  center: { lat: 50.45, lng: 30.52 },
  zoom: 10
});

Далее создаётся экземпляр OMS:

const oms = new OverlappingMarkerSpiderfier(map, {
  markersWontMove: true,
  markersWontHide: true,
  basicFormatEvents: true
});

Параметры конфигурации

  • markersWontMove — оптимизация, если маркеры статичны
  • markersWontHide — ускорение обработки при отсутствии скрытия маркеров
  • basicFormatEvents — упрощённая модель событий

Эти флаги напрямую влияют на производительность при большом количестве объектов.


Добавление маркеров в OMS

Каждый маркер должен быть зарегистрирован в системе spiderfy:

const marker = new google.maps.Marker({
  position: { lat: 50.451, lng: 30.523 },
  map: map
});

oms.addMarker(marker);

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


Обработка событий

OMS расширяет стандартную модель событий маркеров, добавляя специфические состояния.

Клик по отдельному маркеру

oms.addListener('click', function(marker) {
  console.log('Маркер выбран:', marker);
});

Spiderfy-событие

oms.addListener('spiderfy', function(markers) {
  console.log('Развёрнуто маркеров:', markers.length);
});

Unspiderfy-событие

oms.addListener('unspiderfy', function(markers) {
  console.log('Схлопывание обратно:', markers.length);
});

Алгоритм размещения маркеров

При активации spiderfy используется геометрическое распределение:

  • базовая точка берётся из координат первого маркера
  • вокруг неё строится окружность радиуса, зависящего от масштаба
  • каждый следующий маркер получает угол θ = 2π / n
  • при большом n используется спираль Архимеда

Упрощённая модель:

x = centerX + radius * cos(angle)
y = centerY + radius * sin(angle)

Такое распределение гарантирует отсутствие наложений даже при высокой плотности точек.


Поведение при изменении масштаба карты

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

Интеграция с событиями карты:

google.maps.event.addListener(map, 'zoom_changed', function() {
  console.log('Изменён масштаб карты');
});

Сочетание с кластеризацией

OverlappingMarkerSpiderfier и MarkerClusterer решают разные задачи и могут конфликтовать при неправильной архитектуре.

  • кластеризация группирует маркеры до клика
  • spiderfy раскрывает уже видимую группу

Типовой подход:

  • при низком zoom использовать кластеризацию
  • при клике на кластер — переход к spiderfy логике

В таких системах OMS применяется на уровне конечных маркеров внутри кластера.


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

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

Предварительная фильтрация

Не стоит добавлять все маркеры без необходимости:

if (shouldRender(point)) {
  oms.addMarker(marker);
}

Пул объектов

Повторное использование маркеров снижает нагрузку на GC.

Минимизация DOM-обработчиков

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


Работа с кастомными маркерами

OMS не ограничивает тип отображения маркера:

const marker = new google.maps.Marker({
  position: { lat: 50.45, lng: 30.52 },
  icon: {
    url: '/custom-icon.png',
    scaledSize: new google.maps.Size(32, 32)
  }
});

При spiderfy сохраняются все визуальные свойства.


Ограничения модели

Несмотря на универсальность, существуют ограничения:

  • высокая плотность (сотни маркеров в одной точке) может снижать читаемость spiderfy
  • сложные кастомные overlay могут требовать дополнительной синхронизации
  • не поддерживается автоматическая семантическая группировка (только геометрическая)

Интеграционные сценарии

Типовые области применения:

  • карты недвижимости с множеством объектов в одном районе
  • транспортные системы с остановками на одной линии
  • сервисы доставки с высокой концентрацией точек заказов
  • аналитические панели с геоданными

В таких сценариях OMS выступает как слой взаимодействия между пользователем и геометрической плотностью данных.


Взаимодействие с пользовательскими событиями карты

OMS не перехватывает стандартные события карты, но расширяет модель взаимодействия с маркерами:

oms.addListener('click', function(marker, event) {
  map.panTo(marker.getPosition());
});

Это позволяет строить кастомную логику навигации при выборе объекта.


Геометрическая интерпретация spiderfy

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

  • вход: множество точек P на сфере (карте)
  • условие: расстояние между проекциями < ε
  • выход: преобразование f(P) → P’, где ∀i≠j: d(P’i, P’j) > ε’

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