Слежение за перемещением

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

Ключевая функция браузерного API:

navigator.geolocation.watchPosition(success, error, options);

Параметр watchPosition возвращает идентификатор наблюдения, позволяющий в дальнейшем остановить слежение через clearWatch.

Структура данных позиции включает:

  • latitude — широта
  • longitude — долгота
  • accuracy — радиус погрешности в метрах
  • altitude — высота (при наличии)
  • speed — скорость движения
  • heading — направление движения
  • timestamp — время фиксации координаты

Инициализация карты и базовой точки отслеживания

Перед началом обновления координат создаётся карта и слой для отображения текущего положения:

const map = L.map('map').setView([0, 0], 15);

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '© OpenStreetMap'
}).addTo(map);

let marker = null;
let accuracyCircle = null;

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

Использование встроенного слежения Leaflet

В Leaflet предусмотрен упрощённый механизм через метод map.locate:

map.locate({
  watch: true,
  setView: true,
  enableHighAccuracy: true,
  maximumAge: 1000,
  timeout: 10000
});

Параметры:

  • watch: true — непрерывное отслеживание
  • setView: true — автоматическое центрирование карты
  • enableHighAccuracy — использование GPS вместо менее точных источников
  • maximumAge — допустимый возраст кэшированных координат
  • timeout — максимальное время ожидания ответа

События, генерируемые картой:

map.on('locationfound', (e) => {
  const lat = e.latitude;
  const lng = e.longitude;

  if (!marker) {
    marker = L.marker([lat, lng]).addTo(map);
  } else {
    marker.setLatLng([lat, lng]);
  }

  if (!accuracyCircle) {
    accuracyCircle = L.circle([lat, lng], e.accuracy).addTo(map);
  } else {
    accuracyCircle.setLatLng([lat, lng]);
    accuracyCircle.setRadius(e.accuracy);
  }
});

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

map.on('locationerror', (e) => {
  console.log('Ошибка геолокации:', e.message);
});

Обновление позиции через watchPosition

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

const watchId = navigator.geolocation.watchPosition((pos) => {
  const lat = pos.coords.latitude;
  const lng = pos.coords.longitude;
  const acc = pos.coords.accuracy;

  if (!marker) {
    marker = L.marker([lat, lng]).addTo(map);
  } else {
    marker.setLatLng([lat, lng]);
  }

  if (!accuracyCircle) {
    accuracyCircle = L.circle([lat, lng], acc).addTo(map);
  } else {
    accuracyCircle.setLatLng([lat, lng]);
    accuracyCircle.setRadius(acc);
  }

  map.panTo([lat, lng]);
}, (err) => {
  console.error(err);
}, {
  enableHighAccuracy: true,
  maximumAge: 1000,
  timeout: 10000
});

Остановка слежения:

navigator.geolocation.clearWatch(watchId);

Отрисовка траектории движения

Для визуализации маршрута используется полилиния, накапливающая координаты:

const path = [];
const polyline = L.polyline(path, { color: 'blue' }).addTo(map);

navigator.geolocation.watchPosition((pos) => {
  const latlng = [pos.coords.latitude, pos.coords.longitude];

  path.push(latlng);
  polyline.setLatLngs(path);

  marker.setLatLng(latlng);
});

Такая модель позволяет анализировать маршрут, скорость изменения направления и плотность перемещения.

Сглаживание движения и интерполяция

Реальные GPS-данные содержат шум, вызывающий рывки маркера. Для стабилизации применяется:

  • усреднение координат
  • фильтр скользящего среднего
  • интерполяция между точками

Простейшее сглаживание:

function smooth(prev, next, alpha) {
  return [
    prev[0] + (next[0] - prev[0]) * alpha,
    prev[1] + (next[1] - prev[1]) * alpha
  ];
}

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

let last = null;

navigator.geolocation.watchPosition((pos) => {
  const next = [pos.coords.latitude, pos.coords.longitude];

  if (last) {
    last = smooth(last, next, 0.2);
  } else {
    last = next;
  }

  marker.setLatLng(last);
});

Контроль точности и фильтрация шумов

Параметр accuracy позволяет отбрасывать неточные координаты:

if (pos.coords.accuracy > 50) return;

Дополнительная фильтрация включает:

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

Пример проверки скорости:

let lastTime = null;
let lastPoint = null;

function distance(a, b) {
  return map.distance(a, b);
}

navigator.geolocation.watchPosition((pos) => {
  const current = [pos.coords.latitude, pos.coords.longitude];
  const now = pos.timestamp;

  if (lastPoint && lastTime) {
    const dt = (now - lastTime) / 1000;
    const d = distance(lastPoint, current);
    const speed = d / dt;

    if (speed > 40) return;
  }

  lastPoint = current;
  lastTime = now;
});

Центрирование карты при движении

Автоматическое удержание объекта в центре карты реализуется через panTo или setView:

map.panTo(latlng, {
  animate: true,
  duration: 0.5
});

Для плавного следования часто используется комбинация панорамирования и обновления масштаба только при необходимости:

map.setView(latlng, map.getZoom(), {
  animate: true
});

Работа с направлением движения

При наличии heading возможно визуальное ориентирование маркера:

const icon = L.divIcon({
  className: 'arrow-icon'
});

marker.setIcon(icon);

И поворот через CSS:

.arrow-icon {
  transform: rotate(calc(var(--heading) * 1deg));
}

Обновление:

marker.getElement().style.setProperty('--heading', pos.coords.heading);

Поведение при потере сигнала

При отсутствии новых координат реализуются стратегии:

  • удержание последней позиции
  • постепенное снижение точности
  • отображение индикатора “паузы”

Проверка актуальности данных:

const stale = Date.now() - pos.timestamp > 5000;
if (stale) return;

Комбинирование с внешними источниками данных

Слежение может усиливаться серверными потоками (WebSocket, MQTT), где координаты приходят не только от устройства, но и от внешней системы. В таком случае Leaflet используется как визуальный слой, обновляющий состояние маркеров в реальном времени без привязки к локальному GPS.

socket.on('position', (data) => {
  marker.setLatLng([data.lat, data.lng]);
});

Оптимизация частоты обновлений

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

  • throttling обновлений
  • requestAnimationFrame для синхронизации с рендером
  • батчинг координат

Пример ограничения частоты:

let lastUpdate = 0;

function update(pos) {
  const now = Date.now();
  if (now - lastUpdate < 200) return;

  lastUpdate = now;
  marker.setLatLng([pos.coords.latitude, pos.coords.longitude]);
}