MapLibre GL JS строится вокруг событийной модели, в которой практически любое изменение состояния карты, источников данных, слоёв или пользовательского взаимодействия может быть зафиксировано через систему событий. Логирование этих событий используется для отладки, мониторинга производительности, диагностики ошибок и построения аналитики поведения карты в реальном времени.
События в MapLibre GL JS реализованы через паттерн EventEmitter: объект карты подписывается на события, а обработчики получают структурированные данные о происходящем.
Основной механизм регистрации событий — метод map.on. Он
позволяет привязать обработчик к конкретному типу события.
map.on('load', (e) => {
console.log('Карта загружена', e);
});
Каждое событие передаёт объект события, содержащий контекст: время, источник, координаты (если применимо), состояние карты.
Типовая структура логирования строится вокруг универсальной функции:
function logEvent(type, payload) {
console.log(`[Map Event] ${type}`, {
time: new Date().toISOString(),
payload
});
}
И применение:
map.on('load', (e) => logEvent('load', e));
map.on('error', (e) => logEvent('error', e));
Жизненный цикл карты формирует основу наблюдаемого поведения.
loadСрабатывает после полной инициализации стиля и ресурсов.
map.on('load', () => {
logEvent('load');
});
Используется для фиксации момента готовности карты к работе.
idleСигнализирует о завершении всех загрузок и рендеринга.
map.on('idle', () => {
logEvent('idle');
});
Часто применяется для измерения времени полной стабилизации карты.
removeФиксирует уничтожение экземпляра карты.
map.on('remove', () => {
logEvent('remove');
});
Критично для отслеживания утечек памяти.
renderСрабатывает при каждом кадре отрисовки.
map.on('render', () => {
logEvent('render');
});
Используется осторожно из-за высокой частоты вызовов.
dataФиксирует изменения данных источников или стиля.
map.on('data', (e) => {
logEvent('data', {
dataType: e.dataType,
sourceId: e.sourceId,
isSourceLoaded: e.isSourceLoaded
});
});
Позволяет отслеживать загрузку тайлов и обновления GeoJSON.
errorКлючевое событие для диагностики.
map.on('error', (e) => {
logEvent('error', e.error || e);
});
В продакшене часто перенаправляется в систему мониторинга.
MapLibre GL JS предоставляет события взаимодействия с картой.
map.on('move', () => logEvent('move'));
map.on('movestart', () => logEvent('movestart'));
map.on('moveend', () => logEvent('moveend'));
map.on('zoom', () => logEvent('zoom', { zoom: map.getZoom() }));
map.on('zoomstart', () => logEvent('zoomstart'));
map.on('zoomend', () => logEvent('zoomend'));
map.on('rotate', () => logEvent('rotate', { bearing: map.getBearing() }));
map.on('pitch', () => logEvent('pitch', { pitch: map.getPitch() }));
Слои могут генерировать события при взаимодействии или изменении состояния.
map.on('click', 'points-layer', (e) => {
logEvent('layer-click', {
features: e.features,
coordinates: e.lngLat
});
});
Регистрация событий наведения:
map.on('mouseenter', 'points-layer', () => {
logEvent('mouseenter-layer');
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'points-layer', () => {
logEvent('mouseleave-layer');
map.getCanvas().style.cursor = '';
});
Источники данных (sources) являются ключевым объектом наблюдения.
map.on('sourcedata', (e) => {
logEvent('sourcedata', {
sourceId: e.sourceId,
sourceDataType: e.sourceDataType,
isSourceLoaded: e.isSourceLoaded
});
});
Отдельный контроль загрузки тайлов:
map.on('data', (e) => {
if (e.dataType === 'source' && e.sourceId === 'cities') {
logEvent('cities-source-update', e);
}
});
При сложных системах обычный console.log заменяется на
структурированные журналы.
const logger = {
info: (event, data) => {
console.info(JSON.stringify({
level: 'info',
event,
data,
time: Date.now()
}));
},
error: (event, data) => {
console.error(JSON.stringify({
level: 'error',
event,
data,
time: Date.now()
}));
}
};
Применение:
map.on('load', () => logger.info('map_load'));
map.on('error', (e) => logger.error('map_error', e));
Комбинация событий позволяет вычислять метрики.
const start = performance.now();
map.on('load', () => {
const duration = performance.now() - start;
logger.info('map_load_time', { duration });
});
const startIdle = performance.now();
map.on('idle', () => {
const duration = performance.now() - startIdle;
logger.info('map_idle_time', { duration });
});
Долгоживущие приложения требуют управления подписками.
function onMove() {
logEvent('move');
}
map.on('move', onMove);
Удаление обработчика:
map.off('move', onMove);
Глобальная очистка:
map.remove();
При высокой частоте событий используется фильтрация.
let lastLog = 0;
map.on('render', () => {
const now = Date.now();
if (now - lastLog > 1000) {
logEvent('render_throttled');
lastLog = now;
}
});
Для анализа последовательностей событий применяется группировка.
console.group('Map interaction');
map.on('movestart', () => console.log('start'));
map.on('moveend', () => console.log('end'));
console.groupEnd();
События карты часто отправляются в внешние системы.
map.on('click', (e) => {
fetch('/analytics', {
method: 'POST',
body: JSON.stringify({
event: 'map_click',
coordinates: e.lngLat,
zoom: map.getZoom()
})
});
});
Ошибки рендеринга часто связаны с отсутствующими ресурсами или неверной конфигурацией стиля.
map.on('error', (e) => {
if (e.error && e.error.status === 404) {
logEvent('missing_resource', e);
}
});
Комбинация событий позволяет восстанавливать поведение пользователя.
map.on('movestart', () => logEvent('user_navigation_start'));
map.on('moveend', () => logEvent('user_navigation_end'));
map.on('zoomstart', () => logEvent('user_zoom_start'));
map.on('zoomend', () => logEvent('user_zoom_end'));
Высокочастотные события требуют ограничения:
const debounce = (fn, delay) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
};
map.on('move', debounce(() => {
logEvent('move_debounced');
}, 200));
Условное логирование позволяет разделять dev и prod окружения.
const DEBUG = true;
function debugLog(event, data) {
if (!DEBUG) return;
console.log(event, data);
}
map.on('load', () => debugLog('load'));
Регулярный опрос состояния карты дополняет событийную модель.
setInterval(() => {
logEvent('state_snapshot', {
zoom: map.getZoom(),
center: map.getCenter(),
bearing: map.getBearing(),
pitch: map.getPitch()
});
}, 5000);
map.on('data', (e) => {
if (e.tile && e.tile.state === 'errored') {
logEvent('tile_error', {
source: e.sourceId,
tile: e.tile
});
}
});
Полный аудит взаимодействия включает захват всех ключевых точек:
const events = [
'load',
'idle',
'error',
'move',
'zoom',
'rotate',
'pitch',
'data',
'render'
];
events.forEach((event) => {
map.on(event, (e) => {
logEvent(`audit_${event}`, e);
});
});
Для анализа последовательностей вводится идентификатор сессии:
const sessionId = crypto.randomUUID();
function log(event, data) {
console.log({
sessionId,
event,
data,
timestamp: performance.now()
});
}
map.on('data', (e) => {
if (e.sourceId === 'geojson-points') {
logEvent('geojson_update', {
loaded: e.isSourceLoaded,
type: e.dataType
});
}
});
map.on('styledata', () => {
logEvent('style_updated');
});
class MapLogger {
constructor(map) {
this.map = map;
this.bindEvents();
}
bindEvents() {
this.map.on('load', (e) => this.log('load', e));
this.map.on('error', (e) => this.log('error', e));
this.map.on('move', () => this.log('move'));
this.map.on('zoom', () => this.log('zoom', this.map.getZoom()));
}
log(event, data) {
console.log(JSON.stringify({
event,
data,
time: Date.now()
}));
}
}