События Draw

Инструмент редактирования геометрии Mapbox GL Draw построен вокруг событийной модели, через которую происходит взаимодействие с создаваемыми, изменяемыми и удаляемыми географическими объектами. Каждый пользовательский жест — добавление точки, изменение полигона, завершение редактирования — транслируется в набор событий, позволяющих синхронизировать состояние карты с внешним приложением, хранилищем данных или бизнес-логикой.

События Draw интегрируются в стандартную систему событий Mapbox GL JS и регистрируются через метод map.on(...), где map — экземпляр карты. Все события имеют предсказуемую структуру и передают актуальный набор GeoJSON-объектов, находящихся под управлением Draw.


Архитектура событийной модели Draw

Mapbox GL Draw использует внутренний слой управления состоянием, где каждая геометрия представлена в формате GeoJSON Feature. При каждом изменении состояния происходит:

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

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

  • массив всех объектов или изменённых объектов
  • метаданные режима (mode)
  • идентификаторы выбранных элементов
  • контекст действия пользователя

Структура событий обеспечивает предсказуемую реакцию на изменения без необходимости ручного опроса состояния.


Регистрация событий

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

map.on('draw.create', function (e) {
  console.log('Создан объект:', e.features);
});

Общая форма:

map.on('<event-name>', callback);

Удаление обработчиков:

map.off('<event-name>', callback);

draw.create

Событие draw.create возникает при завершении создания нового объекта. Это может быть точка, линия или полигон.

Срабатывание происходит:

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

Структура события:

{
  features: [
    {
      id: 'feature-id',
      type: 'Feature',
      geometry: { type: 'Polygon', coordinates: [...] },
      properties: {}
    }
  ]
}

Пример обработки:

map.on('draw.create', (e) => {
  const createdFeatures = e.features;
  createdFeatures.forEach((feature) => {
    if (feature.geometry.type === 'Polygon') {
      console.log('Новый полигон создан');
    }
  });
});

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

  • сохранения новых объектов в базе данных
  • запуска валидации геометрии
  • построения зависимых объектов (например, буферных зон)

draw.update

Событие draw.update вызывается при изменении существующего объекта. Изменения включают:

  • перемещение вершин
  • редактирование формы полигона
  • изменение линии
  • редактирование группы объектов

Событие содержит массив обновлённых features:

map.on('draw.update', (e) => {
  e.features.forEach((feature) => {
    console.log('Обновлён объект:', feature.id);
  });
});

Типичные сценарии использования:

  • автосохранение изменений
  • пересчёт геометрических характеристик
  • обновление UI-индикаторов

Особенность заключается в высокой частоте вызова при перетаскивании вершин, что требует оптимизации обработки (debounce/throttle).


draw.delete

Событие draw.delete срабатывает при удалении объектов из слоя редактирования.

Удаление может происходить:

  • через кнопку удаления
  • через клавишу Delete/Backspace
  • программным вызовом API

Структура события:

map.on('draw.delete', (e) => {
  const deletedIds = e.features.map(f => f.id);
  console.log('Удалены объекты:', deletedIds);
});

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

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

draw.selectionchange

Событие draw.selectionchange фиксирует изменение текущего выделения объектов.

Срабатывает при:

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

Пример:

map.on('draw.selectionchange', (e) => {
  const selected = e.features;
  console.log('Текущее выделение:', selected.length);
});

Полезно для:

  • отображения панели редактирования
  • обновления информации о выбранном объекте
  • управления состоянием интерфейса

draw.modechange

Событие draw.modechange фиксирует переход между режимами Draw.

Основные режимы:

  • simple_select
  • direct_select
  • draw_point
  • draw_polygon
  • draw_line_string

Пример обработки:

map.on('draw.modechange', (e) => {
  console.log('Новый режим:', e.mode);
});

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

  • переключения UI-инструментов
  • блокировки интерфейсных элементов
  • изменения поведения карты

draw.render

Событие draw.render вызывается при каждом обновлении отрисовки Draw слоя.

Характеристики:

  • высокая частота вызовов
  • синхронизация с render cycle карты
  • не содержит изменений геометрии как таковых

Пример:

map.on('draw.render', () => {
  console.log('Перерисовка Draw слоя');
});

Применение:

  • синхронизация внешних анимаций
  • отладка производительности
  • отслеживание частоты перерисовки

Использование требует осторожности из-за потенциальной нагрузки.


draw.combine и draw.uncombine

События draw.combine и draw.uncombine связаны с объединением и разбиением объектов.

draw.combine

Возникает при объединении нескольких объектов в коллекцию.

map.on('draw.combine', (e) => {
  console.log('Объединены объекты:', e.createdFeature);
});

Применение:

  • создание MultiPolygon или MultiLineString
  • группировка объектов
  • оптимизация отображения

draw.uncombine

Срабатывает при разделении комбинированного объекта.

map.on('draw.uncombine', (e) => {
  console.log('Разделён объект:', e.features);
});

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

  • декомпозиции сложных геометрий
  • восстановления исходных элементов

Структура GeoJSON в событиях

Во всех событиях ключевым элементом является массив features. Каждый элемент соответствует спецификации GeoJSON Feature:

{
  type: 'Feature',
  id: 'unique-id',
  geometry: {
    type: 'Point' | 'LineString' | 'Polygon',
    coordinates: [...]
  },
  properties: {
    // пользовательские данные
  }
}

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


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

События Draw часто используются совместно с API управления:

draw.add(feature);
draw.delete(featureId);
draw.changeMode('simple_select');
draw.setFeatureProperty(id, 'name', 'Объект');

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


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

Некоторые события, особенно draw.update и draw.render, могут генерироваться десятки раз в секунду.

Рекомендуемые практики:

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

Особенно критично это при работе с большим количеством вершин.


Типовые ошибки при работе с событиями

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

  • дублирование обработчиков при повторной инициализации карты
  • отсутствие удаления слушателей при уничтожении компонента
  • выполнение тяжёлых операций внутри draw.render
  • некорректная обработка массива features при множественных изменениях

Корректная архитектура требует строгого контроля жизненного цикла событий.


Синхронизация состояния приложения

События Draw выступают основным механизмом синхронизации между картой и внешним состоянием:

  • store (Redux, MobX, Zustand)
  • серверное API
  • локальное хранилище
  • аналитические системы

Типичный поток:

  1. пользователь изменяет геометрию
  2. генерируется draw.update
  3. данные сериализуются в GeoJSON
  4. обновляется внешний store
  5. интерфейс реагирует на изменения

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