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

Событийная модель в Mapbox GL JS основана на паттерне наблюдателя, где карта выступает центральным источником событий, а различные компоненты интерфейса и бизнес-логика подписываются на изменения состояния. Все взаимодействие с картой — от загрузки стиля до кликов по слоям — представлено через унифицированную систему событий.

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

Внутри Mapbox GL JS события строятся вокруг внутреннего event emitter. Каждый экземпляр карты поддерживает независимый список подписчиков, привязанных к конкретным типам событий.

События могут возникать из нескольких подсистем:

  • рендеринг WebGL сцены
  • управление источниками данных (sources)
  • обработка пользовательского ввода (мышь, тач, клавиатура)
  • загрузка ресурсов стиля и тайлов
  • геометрические вычисления попадания (picking)

Каждое событие содержит объект event, который передаёт контекст: координаты, целевые слои, пиксельные позиции, состояние карты и дополнительные метаданные.

Регистрация обработчиков

Основной механизм подписки реализован через методы:

map.on('load', () => {});
map.on('click', (e) => {});
map.on('mousemove', 'layer-id', (e) => {});

Метод on поддерживает перегрузку:

  • глобальные события карты
  • события конкретных слоёв
  • события источников данных

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

Удаление обработчиков выполняется через:

map.off('click', handler);
map.off('mousemove', 'layer-id', handler);

Важно учитывать, что ссылка на функцию должна совпадать, иначе удаление не произойдёт.

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

map.once('load', () => {});

После первого вызова подписка автоматически удаляется.

Жизненный цикл карты и системные события

Системные события отражают этапы инициализации и обновления состояния:

  • load — завершена загрузка стиля и ресурсов
  • idle — карта завершила все рендер-операции
  • render — произошёл новый кадр WebGL
  • remove — карта уничтожается
  • error — произошла ошибка загрузки или выполнения

Событие load критично для выполнения логики, зависящей от наличия стиля, слоёв и источников.

Событие idle используется для синхронизации состояния интерфейса с завершением рендеринга, особенно при динамическом обновлении данных.

Пользовательские взаимодействия

Интерактивные события включают обработку ввода пользователя:

  • click
  • dblclick
  • mousedown / mouseup
  • mousemove
  • mouseenter / mouseleave
  • touchstart / touchend / touchmove

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

  • event.lngLat — географические координаты
  • event.point — пиксельные координаты относительно контейнера карты
  • event.originalEvent — нативное DOM-событие

Пример структуры обработчика:

map.on('click', (e) => {
  console.log(e.lngLat);
  console.log(e.point);
});

Pick событий и взаимодействие со слоями

Особенностью событийной модели является возможность привязки к конкретному слою:

map.on('click', 'cities-layer', (e) => {
  console.log(e.features);
});

В этом случае выполняется spatial query по отрисованным объектам слоя. Результат доступен через features, содержащий массив геометрий, попавших под курсор.

Внутренне используется механизм hit-testing по WebGL-буферу, где каждый отрисованный объект имеет идентификатор, позволяющий сопоставить пиксельное попадание с геометрией источника данных.

События источников данных

События, связанные с источниками (source), позволяют отслеживать процесс загрузки и обновления данных:

  • sourcedata — обновление данных источника
  • data — общее событие изменения данных
  • styledata — изменение стиля карты
  • dataloading — начало загрузки
  • dataloaded — завершение загрузки

Эти события особенно важны при работе с динамическими GeoJSON или векторными тайлами.

Управление контекстом события

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

Основные поля:

  • type — тип события
  • target — экземпляр карты
  • features — выбранные геометрии
  • lngLat — координаты события
  • point — экранные координаты
  • originalEvent — DOM-событие

В случае событий рендеринга также доступны поля:

  • timeStamp
  • renderedFeatures
  • tiles

Приоритет и распространение событий

События обрабатываются в порядке регистрации обработчиков. Однако при слоевой привязке сначала выполняется spatial filtering, после чего вызываются только релевантные обработчики.

При совпадении нескольких слоёв порядок определяется порядком их добавления в стиль. Верхние слои получают приоритет при hit-testing.

Распространение событий не полностью аналогично DOM event bubbling. В Mapbox GL JS отсутствует классическая фаза всплытия, вместо этого используется централизованная диспетчеризация.

Динамическое управление подписками

Сценарии с частым обновлением состояния требуют управления жизненным циклом обработчиков.

Пример паттерна:

function enableInteraction(map) {
  const handler = (e) => {
    map.getCanvas().style.cursor = 'pointer';
  };

  map.on('mousemove', 'layer-id', handler);

  return () => {
    map.off('mousemove', 'layer-id', handler);
  };
}

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

Производительность событийной системы

При большом количестве слоёв и источников критическим становится количество hit-test операций. Оптимизация достигается через:

  • ограничение количества интерактивных слоёв
  • использование фильтров queryRenderedFeatures
  • сокращение числа обработчиков mousemove
  • debounce логики внутри обработчиков

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

События и фильтрация данных

Метод queryRenderedFeatures часто используется внутри обработчиков событий:

map.on('click', (e) => {
  const features = map.queryRenderedFeatures(e.point, {
    layers: ['cities-layer']
  });
});

Это позволяет дополнительно фильтровать объекты без привязки к встроенному layer-handler механизму.

Пользовательские события

Система поддерживает генерацию кастомных событий через fire:

map.fire('custom-event', { value: 42 });

Такие события интегрируются в общий event loop и могут обрабатываться через стандартный on.

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

Обработка ошибок

Событие error агрегирует ошибки разных подсистем:

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

Объект ошибки содержит:

  • error.message
  • error.status
  • error.sourceId

Централизованная обработка позволяет строить устойчивые интерфейсы поверх нестабильных сетевых условий.

Интеграция с рендерингом WebGL

Каждое событие render связано с циклом WebGL. Карта перерисовывается только при изменении состояния или анимации.

События рендера используются для:

  • синхронизации внешних анимаций
  • отслеживания FPS
  • построения кастомных слоёв поверх canvas

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

События движения камеры

Отдельную категорию составляют события камеры:

  • move
  • zoom
  • rotate
  • pitch
  • movestart
  • moveend

Они отражают изменения состояния визуализации карты и позволяют синхронизировать интерфейсы:

map.on('move', () => {
  const center = map.getCenter();
});

Эти события часто используются для построения аналитических панелей и синхронизации нескольких карт.

Координация событий между слоями и источниками

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

При обновлении GeoJSON источника инициируется цепочка:

  1. загрузка данных
  2. обновление source cache
  3. пересборка tile set
  4. invalidation слоя
  5. триггер render

Такая структура минимизирует количество полных перерисовок сцены.

Асинхронная природа событий

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

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

Применение событийной модели в архитектуре приложений

Событийная система Mapbox GL JS используется как основа для:

  • построения интерактивных GIS-интерфейсов
  • реализации реактивных панелей данных
  • синхронизации нескольких карт
  • обработки пользовательской аналитики взаимодействий

События выступают связующим слоем между WebGL-рендерингом и бизнес-логикой приложения, обеспечивая изоляцию визуального слоя от прикладной логики.