В MapLibre GL JS отсутствует встроенный инструмент рисования геометрий, однако функциональность редактирования и создания объектов на карте реализуется через совместимые расширения, прежде всего через API-подобные плагины уровня Mapbox GL Draw. В результате события категории draw становятся частью событийной модели карты и позволяют отслеживать полный жизненный цикл геометрий: создание, изменение, удаление и взаимодействие с режимами редактирования.
Эти события интегрируются в стандартную систему событий карты и
подписываются через map.on(...), где map —
экземпляр MapLibre GL JS.
События draw строятся вокруг внутреннего состояния коллекции GeoJSON-объектов. Каждый объект проходит несколько стадий:
Эта модель позволяет синхронизировать пользовательские действия с внешними системами: хранилищами данных, редакторами, аналитикой и серверными API.
События регистрируются стандартным способом через экземпляр карты:
map.on('draw.create', (e) => {
console.log('Создан объект:', e.features);
});
map.on('draw.update', (e) => {
console.log('Обновлён объект:', e.features);
});
map.on('draw.delete', (e) => {
console.log('Удалён объект:', e.features);
});
Каждое событие передаёт объект события, содержащий массив
features, представленный в формате GeoJSON.
Общий формат события draw:
{
type: 'draw.create' | 'draw.update' | 'draw.delete',
features: [
{
id: 'string | number',
type: 'Feature',
geometry: {
type: 'Point' | 'LineString' | 'Polygon',
coordinates: [...]
},
properties: {
... пользовательские данные
}
}
]
}
Идентификатор id используется для связи между
состояниями одного объекта в разных событиях.
Событие draw.create возникает при завершении создания
геометрии пользователем. Это ключевая точка фиксации новых данных.
Типичный сценарий:
Пример обработки:
map.on('draw.create', (e) => {
const feature = e.features[0];
fetch('/api/features', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(feature)
});
});
Особенность события заключается в том, что оно срабатывает только после завершения интерактивного действия, а не в процессе рисования.
Событие draw.update возникает при модификации уже
существующих объектов:
map.on('draw.update', (e) => {
e.features.forEach(feature => {
console.log('Обновление:', feature.id);
});
});
При массовых изменениях событие может содержать несколько объектов одновременно, что важно учитывать при синхронизации с сервером.
Событие draw.delete фиксирует удаление одного или
нескольких объектов из слоя редактирования.
map.on('draw.delete', (e) => {
const removedIds = e.features.map(f => f.id);
console.log('Удалены объекты:', removedIds);
});
Удаление происходит без сохранения геометрии в текущем состоянии, поэтому для восстановления требуется внешнее хранилище.
Событие draw.selectionchange отражает изменение
активного набора объектов, находящихся в состоянии выделения.
map.on('draw.selectionchange', (e) => {
console.log('Выделенные объекты:', e.features);
});
Это событие критично для интерфейсов редактирования, где действия зависят от текущего выбора:
Инструмент рисования работает в различных режимах:
Событие draw.modechange фиксирует переход между
ними.
map.on('draw.modechange', (e) => {
console.log('Новый режим:', e.mode);
});
Это позволяет адаптировать интерфейс под текущую операцию пользователя, включая:
Событие draw.render возникает при каждой перерисовке
состояния draw-слоя.
Особенности:
map.on('draw.render', () => {
console.log('Перерисовка draw слоя');
});
Применение ограничивается оптимизированными задачами, поскольку частота вызова может быть высокой.
При использовании draw-событий часто возникает необходимость поддерживать актуальное состояние данных вне карты.
Типовой подход:
const store = new Map();
map.on('draw.create', sync);
map.on('draw.update', sync);
map.on('draw.delete', sync);
function sync(e) {
e.features.forEach(f => {
store.set(f.id, f);
});
}
При удалении объектов требуется явное удаление из хранилища.
Несмотря на наличие внутреннего состояния draw, часто требуется
синхронизация с внешним GeoJSON source:
map.getSource('features-source').setData({
type: 'FeatureCollection',
features: Array.from(store.values())
});
Такая схема позволяет:
События draw.update и draw.render могут
вызываться часто, особенно при сложных полигонах.
Ключевые оптимизации:
Пример debounce:
let timeout;
map.on('draw.update', (e) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
persist(e.features);
}, 200);
});
1. Двойная синхронизация Одновременное обновление
состояния и через draw.update, и через render
приводит к дублированию логики.
2. Игнорирование массива features Даже при изменении одного объекта события часто передают массив.
3. Отсутствие контроля режима Логика обработки без
учёта modechange приводит к неконсистентному UI.
4. Хранение состояния только в памяти Без внешнего persistence данные теряются при перезагрузке карты.
При комбинированных операциях (например, редактирование нескольких объектов):
selectionchange может предшествовать
updateupdate может вызываться несколько раз за один жестrender вызывается независимо от логики данныхЭто требует разделения:
Так как MapLibre GL JS имеет собственный render loop, события draw вплетаются в общий цикл отрисовки карты. Это означает:
Оптимальная практика — минимизировать вычисления внутри событий и переносить их в отдельные слои логики приложения.