Логирование событий

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

События в Google Maps JavaScript API делятся на несколько уровней:

  • события карты (map events)
  • события объектов (marker, polyline, polygon, etc.)
  • события пользовательского интерфейса (controls, overlays)
  • события состояния (zoom, center, bounds)

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

map.addListener('click', (event) => {
  console.log(event.latLng.toString());
});

Альтернативный вариант через глобальный обработчик:

google.maps.event.addListener(map, 'click', function(event) {
  console.log(event.latLng.lat(), event.latLng.lng());
});

Несмотря на наличие устаревающего подхода через google.maps.event, современная практика ориентируется на addListener, как более читаемый и контекстно привязанный способ.

Ключевые события карты для логирования

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

Перемещение и изменение области видимости

События:

  • dragstart
  • drag
  • dragend
  • bounds_changed
  • center_changed

Логирование этих событий позволяет отслеживать навигационные паттерны пользователя:

map.addListener('dragend', () => {
  const center = map.getCenter();
  logEvent('map_dragend', {
    lat: center.lat(),
    lng: center.lng()
  });
});

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

Изменение масштаба

Событие zoom_changed фиксирует изменение уровня приближения:

map.addListener('zoom_changed', () => {
  logEvent('zoom_changed', {
    zoom: map.getZoom()
  });
});

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

Событие готовности карты

map.addListener('idle', () => {
  logEvent('map_idle', {
    center: map.getCenter().toJSON(),
    zoom: map.getZoom()
  });
});

idle полезно для фиксации стабильного состояния карты после серии изменений.

Логирование событий маркеров

Маркерные события являются одними из наиболее информативных для аналитики взаимодействия.

Клик по маркеру

marker.addListener('click', () => {
  logEvent('marker_click', {
    id: marker.id,
    position: marker.getPosition().toJSON()
  });
});

Перетаскивание маркера

marker.addListener('dragend', () => {
  const pos = marker.getPosition();
  logEvent('marker_dragend', {
    lat: pos.lat(),
    lng: pos.lng()
  });
});

Наведение и взаимодействие

При необходимости можно фиксировать дополнительные события:

  • mouseover
  • mouseout
  • rightclick

Однако их логирование должно быть ограничено из-за высокой частоты вызовов.

Структура системы логирования

Эффективная система логирования в картографических приложениях обычно строится по следующему принципу:

  1. сбор события
  2. нормализация данных
  3. буферизация
  4. отправка на сервер

Базовый слой логирования

function logEvent(type, payload) {
  console.log('[MAP_EVENT]', type, payload);
}

В реальных системах вместо console.log используется отправка на сервер:

function logEvent(type, payload) {
  fetch('/api/logs', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      type,
      payload,
      timestamp: Date.now()
    })
  });
}

Буферизация событий

При высокой активности пользователя прямые запросы на сервер создают нагрузку. Используется буферизация:

const eventBuffer = [];

function logEvent(type, payload) {
  eventBuffer.push({
    type,
    payload,
    timestamp: Date.now()
  });
}

Отправка по таймеру:

setInterval(() => {
  if (eventBuffer.length === 0) return;

  const batch = eventBuffer.splice(0, eventBuffer.length);

  fetch('/api/logs/batch', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(batch)
  });
}, 5000);

Ограничение частоты (throttling и debouncing)

Некоторые события требуют ограничения частоты вызовов.

Throttling для bounds_changed

let lastLog = 0;

map.addListener('bounds_changed', () => {
  const now = Date.now();
  if (now - lastLog < 1000) return;

  lastLog = now;

  logEvent('bounds_changed', {
    bounds: map.getBounds().toJSON()
  });
});

Debouncing для стабильных состояний

let timeout;

map.addListener('center_changed', () => {
  clearTimeout(timeout);

  timeout = setTimeout(() => {
    logEvent('center_changed_stable', {
      center: map.getCenter().toJSON()
    });
  }, 800);
});

Контекстные данные в логах

Эффективное логирование включает расширение событий дополнительным контекстом:

  • текущий zoom
  • bounding box
  • тип устройства
  • размер экрана
  • активные слои
function getContext(map) {
  return {
    zoom: map.getZoom(),
    center: map.getCenter().toJSON(),
    bounds: map.getBounds()?.toJSON() || null,
    devicePixelRatio: window.devicePixelRatio,
    screen: {
      width: window.innerWidth,
      height: window.innerHeight
    }
  };
}

Использование:

map.addListener('idle', () => {
  logEvent('map_idle', getContext(map));
});

Логирование пользовательских сценариев

События карты редко рассматриваются изолированно. В аналитике важно фиксировать сценарии:

Сценарий поиска и выбора объекта

  1. изменение области карты
  2. появление маркеров
  3. клик по маркеру
  4. открытие InfoWindow
infoWindow.addListener('domready', () => {
  logEvent('info_window_open', {
    markerId: currentMarkerId
  });
});

Интеграция с аналитическими системами

Логи часто отправляются в внешние системы:

  • Google Analytics
  • серверные аналитические платформы
  • BI-системы

Пример отправки в аналитическую систему:

function logEvent(type, payload) {
  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push({
    event: type,
    ...payload
  });
}

Управление объёмом данных

Картографические события генерируют большие объёмы данных. Для оптимизации применяются:

  • фильтрация незначимых изменений
  • агрегация событий
  • хранение только значимых точек (например, финальные состояния drag)

Пример фильтрации:

function shouldLogZoom(oldZoom, newZoom) {
  return oldZoom !== newZoom;
}

События пользовательских слоёв

При использовании слоёв (Heatmap, GeoJSON, TileLayer) логируются:

  • загрузка слоя
  • изменение видимости
  • взаимодействие с объектами слоя
geoJsonLayer.addListener('click', (event) => {
  logEvent('geojson_click', {
    feature: event.feature.getProperty('id')
  });
});

Производительность логирования

Ошибочная реализация логирования может приводить к деградации интерфейса:

  • частые синхронные запросы
  • отсутствие debounce/throttle
  • утечка памяти из-за неотписанных слушателей

Корректный подход включает:

const listener = map.addListener('click', handler);

// при уничтожении карты
google.maps.event.removeListener(listener);

Безопасность и приватность

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

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

Поэтому применяются:

  • анонимизация координат
  • округление значений
  • отказ от хранения точных траекторий

Пример округления:

function roundCoord(value) {
  return Math.round(value * 1000) / 1000;
}

Использование:

map.addListener('click', (event) => {
  logEvent('map_click', {
    lat: roundCoord(event.latLng.lat()),
    lng: roundCoord(event.latLng.lng())
  });
});