Обновление данных источника

В MapLibre GL JS динамическое обновление данных строится вокруг управления источниками (sources), подключёнными к стилю карты. Наиболее гибкий и часто используемый вариант — источник типа geojson, который поддерживает прямую замену данных в рантайме без пересоздания слоя или переинициализации карты.

Базовый механизм обновления через setData

Для GeoJSON-источника предусмотрен метод setData, доступный через объект источника, полученный из карты:

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

source.setData({
  type: 'FeatureCollection',
  features: [
    {
      type: 'Feature',
      geometry: {
        type: 'Point',
        coordinates: [30.31413, 59.93863]
      },
      properties: {
        id: 1,
        value: 42
      }
    }
  ]
});

При вызове setData происходит полная замена внутреннего набора данных источника. Это означает:

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

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

Требования к структуре данных

Источник типа geojson ожидает строго валидный GeoJSON:

  • объект верхнего уровня — FeatureCollection;
  • каждая геометрия должна соответствовать спецификации GeoJSON;
  • координаты должны быть в порядке [долгота, широта];
  • свойства (properties) могут содержать любые сериализуемые данные.

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


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

Обновление данных происходит асинхронно относительно рендер-цикла карты.

После вызова setData выполняется цепочка этапов:

  1. Приём нового GeoJSON объектом источника
  2. Валидация структуры данных
  3. Пересборка внутренних буферов вершин
  4. Обновление пространственных индексов
  5. Инвалидация текущего кадра рендеринга
  6. Перерисовка затронутых слоёв

Если источник используется в нескольких слоях (fill, circle, line), все они получают обновлённые данные одновременно.


Обновление больших объёмов данных

При работе с тысячами или миллионами объектов вызов setData становится потенциально тяжёлой операцией.

Ключевые особенности:

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

Практика пакетного обновления

Для потоковых данных применяется стратегия батчинга:

let buffer = [];

function pushUpdate(feature) {
  buffer.push(feature);

  if (buffer.length > 500) {
    flush();
  }
}

function flush() {
  const source = map.getSource('stream');

  source.setData({
    type: 'FeatureCollection',
    features: buffer
  });

  buffer = [];
}

Такой подход снижает количество пересборок геопространственного индекса.


Инкрементальные обновления через замену FeatureCollection

MapLibre GL JS не поддерживает частичное обновление отдельных фич внутри GeoJSON-источника. Любое изменение требует пересборки всего объекта.

Поэтому используется паттерн иммутабельности:

  • создаётся новый массив features;
  • изменяется только необходимый элемент;
  • вызывается setData с новым объектом.
const source = map.getSource('objects');

const updated = currentFeatures.map(f =>
  f.properties.id === 10
    ? {
        ...f,
        properties: {
          ...f.properties,
          value: 99
        }
      }
    : f
);

source.setData({
  type: 'FeatureCollection',
  features: updated
});

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


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

При включённой кластеризации (cluster: true) каждое обновление данных инициирует перерасчёт кластеров.

Пример конфигурации:

map.addSource('clusters', {
  type: 'geojson',
  data: geojsonData,
  cluster: true,
  clusterMaxZoom: 14,
  clusterRadius: 50
});

После вызова setData:

  • пересчитываются кластерные группы;
  • обновляются cluster-фичи;
  • пересобираются источники для слоёв кластеров и одиночных точек.

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


Обновление источников разных типов

Raster и Vector источники

Для raster и vector источников прямой замены данных через setData не предусмотрено. Эти источники опираются на тайлы, поэтому обновление происходит через:

  • изменение URL шаблонов;
  • обновление tiles параметров через setStyle или пересоздание источника.
map.removeSource('raster-source');

map.addSource('raster-source', {
  type: 'raster',
  tiles: ['https://example.com/tiles/{z}/{x}/{y}.png']
});

Image source

Для image источников обновление выполняется через изменение URL или координат:

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

source.updateImage({
  url: 'https://example.com/new.png',
  coordinates: [
    [30, 60],
    [31, 60],
    [31, 59],
    [30, 59]
  ]
});

События, связанные с обновлением данных

MapLibre GL JS предоставляет набор событий для отслеживания состояния загрузки и обновления источников:

  • sourcedata — изменения в данных источника;
  • data — общие изменения данных карты;
  • dataloading — начало загрузки;
  • idle — завершение всех активных операций.

Пример отслеживания обновлений:

map.on('sourcedata', (e) => {
  if (e.sourceId === 'points-source') {
    // обработка обновления источника
  }
});

Эти события используются для синхронизации UI и внешних систем с состоянием карты.


Проверка готовности источника

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

Типовой подход:

if (map.isSourceLoaded('points-source')) {
  map.getSource('points-source').setData(newData);
}

Если источник ещё не загружен, обновление может быть проигнорировано или вызвать ошибку доступа к объекту.

Альтернативный подход — ожидание события load:

map.on('load', () => {
  const source = map.getSource('points-source');
  source.setData(initialData);
});

Потоковое обновление данных

При работе с WebSocket или другими потоковыми источниками данных применяется модель непрерывного обновления:

  • входящие события агрегируются;
  • данные нормализуются в GeoJSON;
  • обновления отправляются с ограничением частоты (throttling).
const update = throttle((data) => {
  map.getSource('stream').setData({
    type: 'FeatureCollection',
    features: data
  });
}, 200);

Такая схема предотвращает перегрузку рендер-цикла и снижает количество перерасчётов геометрии.


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

При проектировании систем с частыми обновлениями источников учитываются следующие факторы:

  • стоимость пересборки spatial index;
  • нагрузка на main thread;
  • размер GeoJSON payload;
  • частота вызова setData.

Оптимизационные стратегии:

  • уменьшение частоты обновлений;
  • агрегация данных на сервере;
  • использование упрощённых геометрий;
  • разделение источников по зонам обновления;
  • применение Web Worker для подготовки GeoJSON.

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

При высокочастотных обновлениях возможны эффекты:

  • пропуски промежуточных состояний (карта рендерит только последний кадр);
  • накопление задержки между данными и отображением;
  • увеличение времени idle-фазы карты.

MapLibre GL JS оптимизирует рендеринг, поэтому промежуточные вызовы setData могут быть объединены в один кадр отрисовки, что снижает нагрузку, но повышает важность правильного батчинга на уровне приложения.