Временные данные

Временные данные (temporal data) представляют собой информацию, изменяющуюся во времени. В геоинформационных системах такие данные используются для отображения движения объектов, изменения состояний территорий, динамики погодных явлений, транспортных потоков, маршрутов доставки, экологических показателей и множества других процессов.

Mapbox GL JS предоставляет инструменты для визуализации и анимации временных данных на интерактивных картах с использованием JavaScript, GeoJSON, выражений (Expressions), пользовательских источников данных и механизмов обновления слоёв в реальном времени.


Особенности временных геоданных

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

Пример GeoJSON с временной меткой:

{
  "type": "Feature",
  "properties": {
    "timestamp": "2025-05-15T10:30:00Z",
    "speed": 60
  },
  "geometry": {
    "type": "Point",
    "coordinates": [37.6176, 55.7558]
  }
}

Основные типы временных данных:

Тип Описание
Мгновенные события Происходят в конкретный момент времени
Интервалы Имеют начало и конец
Траектории Последовательность точек движения
Потоки данных Непрерывное поступление информации
Исторические данные Архив событий за длительный период

Хранение времени в GeoJSON

Mapbox GL JS не имеет специального поля для времени. Временные значения обычно размещаются в объекте properties.

Пример:

{
  "type": "Feature",
  "properties": {
    "time": 1715745600000,
    "temperature": 24
  },
  "geometry": {
    "type": "Point",
    "coordinates": [30.3158, 59.9391]
  }
}

Время может храниться в различных форматах:

ISO 8601

"2025-05-15T12:00:00Z"

Unix Timestamp

1715745600

Timestamp в миллисекундах

1715745600000

Пользовательский формат

"15.05.2025 12:00"

Для дальнейшей фильтрации и анимации наиболее удобен формат Unix Timestamp.


Временные ряды на карте

Часто требуется отобразить набор измерений, собранных в разные моменты времени.

Пример массива данных:

const points = [
  {
    time: 1715745600,
    coordinates: [37.6176, 55.7558]
  },
  {
    time: 1715745660,
    coordinates: [37.6181, 55.7562]
  },
  {
    time: 1715745720,
    coordinates: [37.6190, 55.7570]
  }
];

Преобразование в GeoJSON:

const geojson = {
  type: 'FeatureCollection',
  features: points.map(item => ({
    type: 'Feature',
    properties: {
      time: item.time
    },
    geometry: {
      type: 'Point',
      coordinates: item.coordinates
    }
  }))
};

Добавление источника:

map.addSource('history', {
  type: 'geojson',
  data: geojson
});

Фильтрация по времени

Один из самых распространённых сценариев — отображение только тех объектов, которые относятся к определённому временному диапазону.

Добавление слоя:

map.addLayer({
  id: 'events',
  type: 'circle',
  source: 'history',
  paint: {
    'circle-radius': 6,
    'circle-color': '#007cbf'
  }
});

Фильтрация:

const currentTime = 1715745660;

map.setFilter('events', [
  '<=',
  ['get', 'time'],
  currentTime
]);

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


Создание временной шкалы

Для интерактивного управления временем обычно используется HTML-слайдер.

HTML:

<input
  id="timeline"
  type="range"
  min="0"
  max="100"
  value="0"
/>

Jav * aScript:

const slider = document.getElementById('timeline');

slider.addEventListener('input', e => {
  const value = Number(e.target.value);

  map.setFilter('events', [
    '<=',
    ['get', 'time'],
    value
  ]);
});

При перемещении ползунка карта мгновенно обновляется.


Анимация временных данных

Вместо ручного управления временная шкала может изменяться автоматически.

Пример:

let currentTime = 0;

function animate() {

  map.setFilter('events', [
    '<=',
    ['get', 'time'],
    currentTime
  ]);

  currentTime += 1;

  requestAnimationFrame(animate);
}

animate();

Функция requestAnimationFrame() обеспечивает плавную анимацию и синхронизируется с частотой обновления браузера.


Анимация движения объекта

Одной из наиболее популярных задач является отображение движения транспорта.

Подготовка маршрута:

const route = [
  [37.6176, 55.7558],
  [37.6182, 55.7565],
  [37.6191, 55.7574],
  [37.6200, 55.7581]
];

Источник:

map.addSource('vehicle', {
  type: 'geojson',
  data: {
    type: 'Feature',
    geometry: {
      type: 'Point',
      coordinates: route[0]
    }
  }
});

Слой:

map.addLayer({
  id: 'vehicle',
  type: 'circle',
  source: 'vehicle',
  paint: {
    'circle-radius': 8,
    'circle-color': '#ff0000'
  }
});

Анимация:

let index = 0;

function moveVehicle() {

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

  source.setData({
    type: 'Feature',
    geometry: {
      type: 'Point',
      coordinates: route[index]
    }
  });

  index = (index + 1) % route.length;

  setTimeout(moveVehicle, 500);
}

moveVehicle();

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

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

Источник:

map.addSource('track', {
  type: 'geojson',
  data: {
    type: 'Feature',
    geometry: {
      type: 'LineString',
      coordinates: []
    }
  }
});

Обновление маршрута:

const passedPoints = [];

function updateTrack(point) {

  passedPoints.push(point);

  map.getSource('track').setData({
    type: 'Feature',
    geometry: {
      type: 'LineString',
      coordinates: passedPoints
    }
  });
}

Линия будет постепенно увеличиваться по мере движения объекта.


Плавная интерполяция координат

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

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

Функция:

function interpolate(start, end, factor) {

  return [
    start[0] + (end[0] - start[0]) * factor,
    start[1] + (end[1] - start[1]) * factor
  ];
}

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

const position = interpolate(
  route[0],
  route[1],
  0.5
);

Результатом станет точка посередине между двумя координатами.


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

При большом количестве объектов частое обновление GeoJSON может вызывать снижение производительности.

В таких случаях полезно использовать механизм состояний объектов.

Установка состояния:

map.setFeatureState(
  {
    source: 'vehicles',
    id: 10
  },
  {
    active: true
  }
);

Получение состояния в стиле:

'circle-color': [
  'case',
  ['feature-state', 'active'],
  '#ff0000',
  '#00ff00'
]

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


Реальное время и потоковые данные

Многие приложения получают информацию через WebSocket.

Подключение:

const socket = new WebSocket(
  'wss://example.com/tracking'
);

Получение данных:

socket.onmess age = event => {

  const position = JSON.parse(event.data);

  map.getSource('vehicle').setData({
    type: 'Feature',
    geometry: {
      type: 'Point',
      coordinates: [
        position.lng,
        position.lat
      ]
    }
  });
};

Карта обновляется сразу после поступления новых данных.


Временные тепловые карты

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

Источник:

map.addSource('events', {
  type: 'geojson',
  data: eventsData
});

Слой:

map.addLayer({
  id: 'heatmap',
  type: 'heatmap',
  source: 'events'
});

Фильтрация по времени:

map.setFilter('heatmap', [
  'all',
  ['>=', ['get', 'time'], startTime],
  ['<=', ['get', 'time'], endTime]
]);

В результате отображаются только события выбранного интервала.


Временные интервалы

Некоторые объекты существуют в течение определённого периода.

Пример:

{
  "start": 1715745600,
  "end": 1715752800
}

Фильтрация:

map.setFilter('objects', [
  'all',
  ['<=', ['get', 'start'], currentTime],
  ['>=', ['get', 'end'], currentTime]
]);

Объект будет отображаться только тогда, когда текущее время находится внутри заданного интервала.


Управление скоростью воспроизведения

Скорость анимации может изменяться динамически.

Пример:

let playbackSpeed = 1;

Изменение:

playbackSpeed = 4;

Обновление времени:

currentTime += playbackSpeed;

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


Кэширование временных наборов данных

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

Пример:

const cache = new Map();

Сохранение:

cache.set(timeRange, geojson);

Получение:

const data = cache.get(timeRange);

Кэширование существенно уменьшает количество сетевых запросов.


Оптимизация производительности

При работе с временными данными объём информации быстро возрастает. Для поддержания высокой производительности используются следующие подходы:

Ограничение количества отображаемых объектов

features.slice(-1000);

Упрощение геометрии

LineString → меньшее количество вершин

Использование векторных тайлов

type: 'vector'

Обновление только изменившихся объектов

setFeatureState()

Пакетная обработка данных

requestAnimationFrame()

Кластеризация временных точек

cluster: true

Типовая архитектура временной визуализации

Полноценная система работы с временными данными в Mapbox GL JS обычно состоит из нескольких уровней:

  1. Источник данных (API, WebSocket, файл).
  2. Хранилище временных записей.
  3. Преобразование данных в GeoJSON.
  4. Временной контроллер.
  5. Механизм фильтрации.
  6. Анимационный движок.
  7. Слои визуализации.
  8. Пользовательские элементы управления.

Схема потока данных:

Сервер
   ↓
Получение данных
   ↓
GeoJSON
   ↓
Map Source
   ↓
Filter
   ↓
Layer
   ↓
Отображение на карте

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