Анимация данных

В Mapbox GL JS анимация почти всегда опирается на два фундаментальных механизма: изменение состояния камеры и обновление данных слоёв. Камера определяет, что именно пользователь видит на карте, а слои определяют, как эти данные визуализируются. В результате анимация может быть либо «географической» (перемещение, масштабирование, вращение), либо «данной» (изменение геометрии, свойств и визуальных параметров объектов).

Камера реализуется через три основные операции:

  • jumpTo — мгновенное изменение состояния без интерполяции
  • easeTo — плавное изменение с заданной функцией сглаживания
  • flyTo — физически-ориентированное перемещение с эффектом полёта
map.easeTo({
  center: [55.751244, 37.618423],
  zoom: 10,
  duration: 2000,
  essential: true
});

Параметр duration задаёт длительность перехода, а встроенная кривая плавности формирует естественное ускорение и замедление движения. Внутри Mapbox GL JS используется непрерывная интерполяция состояния камеры, где каждое значение (центр, масштаб, угол наклона) вычисляется на каждом кадре.

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

Анимации в Mapbox GL JS строятся на интерполяции — вычислении промежуточных значений между начальным и конечным состоянием. Этот подход позволяет избегать дискретных скачков при обновлении карты.

Пример интерполяции угла поворота:

map.rotateTo(180, {
  duration: 3000,
  easing: (t) => t * (2 - t)
});

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

Анимация данных через GeoJSON источники

Наиболее гибкий способ создания динамики — регулярное обновление источников данных. В Mapbox GL JS GeoJSON-источник может изменяться в реальном времени, что приводит к перерисовке слоя.

const source = map.getSource('points');

setInterval(() => {
  const updated = generateRandomPoints();
  source.setData({
    type: 'FeatureCollection',
    features: updated
  });
}, 1000);

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

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

Однако частое обновление GeoJSON требует контроля производительности, поскольку каждое обновление инициирует перерасчёт рендеринга.

Анимация через requestAnimationFrame

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

let start;

function animate(timestamp) {
  if (!start) start = timestamp;
  const progress = (timestamp - start) / 2000;

  const lng = 30 + progress * 10;
  const lat = 50 + progress * 5;

  map.setCenter([lng, lat]);

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

requestAnimationFrame(animate);

Такой подход позволяет создавать полностью кастомные анимации камеры и данных, выходящие за рамки стандартных методов.

Анимация свойств слоёв

Слои в Mapbox GL JS поддерживают изменение визуальных свойств через transition-параметры. Это касается цвета, прозрачности, толщины линий и других визуальных характеристик.

map.setPaintProperty('route', 'line-opacity-transition', {
  duration: 1500,
  delay: 0
});

map.setPaintProperty('route', 'line-opacity', 1);

Transition позволяет задать плавное изменение между состояниями без ручной интерполяции.

Интерполяция в стилях через expressions

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

'circle-radius': [
  'interpolate',
  ['linear'],
  ['zoom'],
  5, 2,
  10, 10,
  15, 20
]

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

Анимация линий через dasharray

Одним из распространённых визуальных эффектов является движение пунктирной линии. Оно достигается изменением line-dasharray во времени.

let step = 0;

function animateDash() {
  step += 0.5;

  map.setPaintProperty('route', 'line-dasharray', [
    0, step, 1, step + 1
  ]);

  requestAnimationFrame(animateDash);
}

animateDash();

Этот метод создаёт иллюзию движения вдоль линии без изменения геометрии.

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

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

let i = 0;

function moveMarker() {
  const coord = route.coordinates[i];

  map.getSource('point').setData({
    type: 'Feature',
    geometry: {
      type: 'Point',
      coordinates: coord
    }
  });

  i = (i + 1) % route.coordinates.length;
  requestAnimationFrame(moveMarker);
}

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

Временные анимации и синхронизация данных

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

function filterByTime(features, time) {
  return features.filter(f => f.properties.timestamp <= time);
}

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

Heatmap и динамическая плотность

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

map.setPaintProperty('heat', 'heatmap-intensity', [
  'interpolate',
  ['linear'],
  ['zoom'],
  0, 1,
  9, 3
]);

При комбинировании с обновляемыми источниками создаётся эффект «пульсации» плотности событий.

Производительность анимаций

В Mapbox GL JS все анимации завязаны на WebGL рендеринг, что требует аккуратного управления нагрузкой. Основные ограничения связаны с:

  • количеством одновременно обновляемых источников
  • частотой вызова setData
  • сложностью выражений в стилях
  • количеством слоёв

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

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

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

Пример комбинированного подхода:

function tick(t) {
  const progress = (t % 5000) / 5000;

  map.easeTo({
    bearing: progress * 360,
    duration: 0
  });

  updateData(progress);

  requestAnimationFrame(tick);
}

requestAnimationFrame(tick);

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