Обновление GeoJSON

Источник GeoJSON-данных и его роль в архитектуре карты

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

Источник типа geojson в Mapbox GL JS хранит структуру FeatureCollection и может быть изменён в любой момент через API, без необходимости пересоздания слоя или всей карты.

Базовая структура источника:

map.addSource('points-source', {
  type: 'geojson',
  data: {
    type: 'FeatureCollection',
    features: []
  }
});

Источник становится реактивной точкой данных: любое обновление содержимого data может быть отражено на карте.


Инициализация слоя и привязка к GeoJSON-источнику

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

map.addLayer({
  id: 'points-layer',
  type: 'circle',
  source: 'points-source',
  paint: {
    'circle-radius': 6,
    'circle-color': '#3b82f6'
  }
});

Слой не содержит собственных данных — он лишь визуализирует содержимое GeoJSON-источника. Это ключевой принцип архитектуры Mapbox GL JS: разделение данных и представления.


Основной механизм обновления данных: setData

Для обновления GeoJSON используется метод:

source.setData(newGeojson);

Где source — объект источника, полученный через:

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

Пример полного обновления данных:

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

source.setData({
  type: 'FeatureCollection',
  features: [
    {
      type: 'Feature',
      geometry: {
        type: 'Point',
        coordinates: [69.589, 42.315]
      },
      properties: {
        name: 'Object A'
      }
    }
  ]
});

При вызове setData происходит полная перерисовка всех слоёв, использующих данный источник.


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

Полная замена FeatureCollection

Наиболее простой подход — полная замена данных. Он используется, когда структура данных формируется заново:

source.setData(geojson);

Преимущества:

  • Простота реализации
  • Отсутствие необходимости трекать состояние объектов

Недостатки:

  • Пересоздание всех объектов
  • Повышенная нагрузка при больших наборах данных

Локальное обновление объектов

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

const data = source._data; // внутреннее состояние (не рекомендуется напрямую использовать)

data.features[0].properties.status = 'upd ated';

source.setData(data);

Корректный подход предполагает хранение копии данных вне Mapbox и обновление её перед передачей в setData.


Управление состоянием объектов

При работе с динамическими данными ключевым становится внешний стор состояния:

let geojsonState = {
  type: 'FeatureCollection',
  features: []
};

function updateFeature(id, newCoords) {
  geojsonState.features = geojsonState.features.map(f => {
    if (f.properties.id === id) {
      return {
        ...f,
        geometry: {
          type: 'Point',
          coordinates: newCoords
        }
      };
    }
    return f;
  });

  map.getSource('points-source').setData(geojsonState);
}

Такой подход исключает мутации внутренних структур Mapbox GL JS и обеспечивает предсказуемость обновлений.


Динамическое добавление объектов

Добавление новых элементов осуществляется через модификацию массива features:

function addPoint(coords, props) {
  geojsonState.features.push({
    type: 'Feature',
    geometry: {
      type: 'Point',
      coordinates: coords
    },
    properties: props
  });

  map.getSource('points-source').setData(geojsonState);
}

Каждое добавление приводит к перерасчёту слоя.


Удаление объектов

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

function removePoint(id) {
  geojsonState.features = geojsonState.features.filter(
    f => f.properties.id !== id
  );

  map.getSource('points-source').setData(geojsonState);
}

Обновление данных в реальном времени

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

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

socket.onmess age = (event) => {
  const message = JSON.parse(event.data);

  geojsonState.features = message.features;

  map.getSource('points-source').setData(geojsonState);
};

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


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

1. Ограничение частоты setData

Частые обновления могут перегружать рендеринг:

let pending = false;

function scheduleUpdate(data) {
  geojsonState = data;

  if (!pending) {
    pending = true;

    requestAnimationFrame(() => {
      map.getSource('points-source').setData(geojsonState);
      pending = false;
    });
  }
}

2. Минимизация размера GeoJSON

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


3. Кластеризация точек

Для больших наборов данных используется кластеризация:

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

Это снижает количество рендеримых объектов при масштабировании.


Изменение координат в анимации

Одним из частых сценариев является перемещение объектов по карте:

function animatePoint() {
  const feature = geojsonState.features[0];

  feature.geometry.coordinates[0] += 0.001;

  map.getSource('points-source').setData(geojsonState);

  requestAnimationFrame(animatePoint);
}

animatePoint();

Каждый кадр обновляет GeoJSON, создавая эффект движения.


Сравнение стратегий обновления

Метод Производительность Сложность Применение
Полный setData Средняя Низкая Небольшие наборы данных
Внешнее состояние Высокая Средняя Динамические приложения
Частые обновления Низкая Низкая Потоки реального времени

Внутренние особенности работы setData

При вызове setData Mapbox GL JS:

  • данные сериализуются
  • передаются в Web Worker
  • пересчитываются тайлы и стили
  • обновляются буферы WebGL

Это объясняет, почему обновление GeoJSON является относительно дорогой операцией.


Типовые ошибки при обновлении GeoJSON

Мутация исходных данных

const data = geojsonState;
data.features.push(newFeature);
map.getSource('points').setData(data);

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


Передача неполного FeatureCollection

map.getSource('points').setData({
  type: 'Feature'
});

Структура должна строго соответствовать спецификации GeoJSON.


Слишком частые обновления

Высокочастотные вызовы setData приводят к:

  • просадкам FPS
  • блокировке рендеринга
  • росту памяти

Архитектура корректного обновления данных

Наиболее устойчивый подход строится на разделении слоёв ответственности:

  • внешний стор (модель данных)
  • слой синхронизации (адаптер)
  • Mapbox GeoJSON source (рендер)
class GeojsonController {
  constructor(map, sourceId) {
    this.map = map;
    this.sourceId = sourceId;
    this.state = { type: 'FeatureCollection', features: [] };
  }

  se t(features) {
    this.state.features = features;
    this.sync();
  }

  sync() {
    this.map.getSource(this.sourceId).setData(this.state);
  }
}

Итоговая модель поведения GeoJSON-обновлений

В Mapbox GL JS обновление GeoJSON строится вокруг одного механизма — полной перезаписи источника данных через setData. Все оптимизации и архитектурные решения направлены не на частичное обновление, а на минимизацию количества полных пересчётов и контроль частоты вызовов этого метода.