Слежение за перемещением в веб-картах опирается на непрерывное получение координат устройства через 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 предусмотрен упрощённый механизм через метод
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);
});
Более низкоуровневый подход обеспечивает полный контроль над потоком координат:
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]);
});
Чрезмерно частые обновления приводят к перегрузке рендеринга карты. Используются:
Пример ограничения частоты:
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]);
}