Анимация маркеров

Работа с маркерами в интерактивных картах HERE Technologies в контексте HERE Maps API for JavaScript строится вокруг объектов слоя отображения карты, которые можно динамически изменять во времени. Анимация маркеров в этом API не является отдельным высокоуровневым модулем, а реализуется через комбинацию изменения географических координат, управления сценой рендеринга и использования браузерных механизмов перерисовки.

Маркер в JavaScript API HERE представляет собой объект, связанный с координатой H.geo.Point. При каждом изменении координаты карта перерасчитывает позицию объекта на экране и перерисовывает соответствующий слой.

Ключевая особенность заключается в том, что:

  • маркеры не «анимируются» автоматически;
  • любое движение — это последовательность дискретных обновлений координат;
  • визуальная плавность достигается частотой обновления и интерполяцией.

Объект маркера:

const marker = new H.map.Marker({ lat: 52.5, lng: 13.4 });
map.addObject(marker);

Изменение позиции выполняется через setGeometry:

marker.setGeometry({ lat: 52.51, lng: 13.41 });

Каждый вызов setGeometry инициирует перерасчёт сцены, поэтому частота обновлений становится критически важной для производительности.

Интерполяция координат как основа анимации

Плавное движение достигается вычислением промежуточных точек между начальной и конечной координатой. Линейная интерполяция применяется отдельно к широте и долготе:

function lerp(start, end, t) {
  return start + (end - start) * t;
}

Для географических координат:

const lat = lerp(startLat, endLat, t);
const lng = lerp(startLng, endLng, t);

Где t изменяется от 0 до 1.

Такой подход создаёт прямолинейную траекторию, которая в большинстве случаев используется для упрощённых анимаций объектов, например, транспортных маркеров или симуляции движения.

Анимационный цикл и requestAnimationFrame

Основной механизм синхронизации анимации с рендерингом браузера строится на requestAnimationFrame. Это позволяет избежать лишних перерисовок и обеспечивает стабильную частоту кадров.

function animateMarker(marker, start, end, duration) {
  const startTime = performance.now();

  function frame(time) {
    const progress = Math.min((time - startTime) / duration, 1);

    const lat = lerp(start.lat, end.lat, progress);
    const lng = lerp(start.lng, end.lng, progress);

    marker.setGeometry({ lat, lng });

    if (progress < 1) {
      requestAnimationFrame(frame);
    }
  }

  requestAnimationFrame(frame);
}

Такой цикл обеспечивает:

  • синхронизацию с refresh rate дисплея;
  • минимизацию лагов при перемещении объектов;
  • предсказуемое время выполнения анимации.

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

При работе с большим количеством маркеров критическим становится вопрос производительности. Каждый вызов setGeometry приводит к:

  • перерасчёту позиции в проекционной системе;
  • обновлению слоя отображения;
  • возможному перерасчёту кластеризации.

Практика оптимизации включает:

1. Снижение частоты обновлений Не обязательно обновлять координаты на каждом кадре — допустимо обновление каждые 2–3 кадра при высокой нагрузке.

2. Батчинг обновлений Групповое обновление нескольких маркеров в одном цикле анимации снижает overhead рендеринга.

3. Использование лёгких DOM-структур При необходимости визуальных эффектов предпочтение отдаётся H.map.DomMarker, но он дороже в рендеринге по сравнению с H.map.Marker.

Анимация по маршруту

Более сложный сценарий — движение маркера по полилинии маршрута. В этом случае используется набор точек LineString, а движение интерполируется по сегментам.

const lineString = new H.geo.LineString();
lineString.pushPoint({ lat: 52.5, lng: 13.4 });
lineString.pushPoint({ lat: 52.6, lng: 13.5 });
lineString.pushPoint({ lat: 52.7, lng: 13.6 });

Далее рассчитывается позиция вдоль линии:

  • определяется текущий сегмент;
  • вычисляется прогресс внутри сегмента;
  • обновляется позиция маркера.

Такой подход используется в системах:

  • отслеживания транспорта;
  • визуализации логистики;
  • симуляции движения объектов.

Поворот маркера по направлению движения

Для повышения реалистичности часто требуется синхронизация ориентации маркера с направлением движения. В API это достигается через CSS-трансформации (для DOM-маркеров) или через иконки с угловыми состояниями.

Расчёт угла:

function getBearing(start, end) {
  const dLng = end.lng - start.lng;
  const dLat = end.lat - start.lat;
  return Math.atan2(dLng, dLat) * 180 / Math.PI;
}

Применение:

const element = marker.getElement();
element.style.transform = `rotate(${bearing}deg)`;

Такой метод применим только к DomMarker, поскольку стандартные маркеры используют растровые или SVG-иконки без динамической трансформации.

Использование easing-функций

Линейная интерполяция создаёт равномерное движение, которое часто выглядит механически. Для улучшения визуального восприятия применяются easing-функции.

Пример ускорения и замедления:

function easeInOut(t) {
  return t < 0.5
    ? 2 * t * t
    : -1 + (4 - 2 * t) * t;
}

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

const eased = easeInOut(progress);
const lat = lerp(start.lat, end.lat, eased);
const lng = lerp(start.lng, end.lng, eased);

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

Работа с несколькими маркерами одновременно

При одновременной анимации множества объектов возникает проблема синхронизации обновлений. Основной подход — централизованный менеджер анимаций.

Логика включает:

  • единый animation loop;
  • массив активных объектов;
  • единый расчёт времени кадра.
const animations = [];

function tick(time) {
  animations.forEach(anim => anim.update(time));
  requestAnimationFrame(tick);
}

requestAnimationFrame(tick);

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

Использование временных меток и синхронизация данных

В реальных сценариях данные часто приходят из внешних источников (GPS-трекинг, телеметрия). В таких случаях важно использовать серверное время или timestamp события.

Это позволяет:

  • корректно восстанавливать траекторию;
  • компенсировать задержки сети;
  • воспроизводить движение постфактум.

Формат данных:

{
  lat: 52.5,
  lng: 13.4,
  timestamp: 1710000000000
}

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

Комбинирование анимации маркеров и камеры

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

map.getViewModel().setLookAtData({
  position: { lat, lng },
  zoom: 14
});

При этом важно избегать конфликтов между:

  • пользовательским управлением картой;
  • программной анимацией;
  • инерцией камеры.

Обычно вводится режим «следования за объектом», при котором управление блокируется или ограничивается.

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

При высокой частоте обновления данных (например, каждые 100–200 мс) основным ограничением становится не математика интерполяции, а рендеринг DOM/WebGL слоя.

Ключевые ограничения:

  • количество одновременно анимируемых объектов;
  • сложность иконок;
  • частота пересчёта сцены;
  • загрузка CPU при частых setGeometry.

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

Альтернативные подходы к анимации

Помимо стандартного обновления координат, применяются:

  • WebGL-слои для массовых объектов;
  • canvas-overlay для кастомной отрисовки;
  • предрасчёт траекторий с последующим воспроизведением.

Каждый из подходов снижает нагрузку на API объектов карты, но увеличивает сложность реализации логики синхронизации.