State management

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

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

  • Camera state — положение и вид карты (центр, масштаб, наклон, поворот)
  • Style state — описание визуального оформления слоёв
  • Source state — данные, которые отображаются на карте
  • Feature state — динамическое состояние отдельных геометрий
  • UI/external state — состояние приложения вне карты (React/Vue/store)

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


Состояние камеры (Camera State)

Камера определяет, какую часть мира видит пользователь. В Mapbox GL JS это набор параметров, которые постоянно изменяются при взаимодействии.

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

  • center — координаты центра карты
  • zoom — уровень приближения
  • bearing — угол поворота
  • pitch — наклон перспективы

Изменение состояния камеры:

map.setCenter([30.5, 50.45]);
map.setZoom(10);
map.setBearing(45);
map.setPitch(30);

Комплексные изменения обычно выполняются через единый вызов:

map.jumpTo({
  center: [30.5, 50.45],
  zoom: 10,
  bearing: 20,
  pitch: 0
});

Анимационные переходы:

map.flyTo({
  center: [30.5, 50.45],
  zoom: 12,
  speed: 1.2,
  curve: 1.4
});

Состояние камеры часто синхронизируется с внешним состоянием приложения, URL или store.


Состояние стиля (Style State)

Стиль определяет, как данные визуализируются. Он включает источники (sources) и слои (layers), каждый из которых имеет собственные свойства.

Изменение стиля:

map.setStyle('mapbox://styles/mapbox/streets-v11');

После смены стиля все динамически добавленные источники и слои удаляются, что делает управление стилем критическим элементом состояния.

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

map.addLayer({
  id: 'cities',
  type: 'circle',
  source: 'cities-data',
  paint: {
    'circle-radius': 6,
    'circle-color': '#ff5200'
  }
});

Изменение свойств слоя:

map.setPaintProperty('cities', 'circle-radius', 10);
map.setLayoutProperty('cities', 'visibility', 'none');

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


Состояние источников данных (Source State)

Источники данных определяют, какие данные доступны для рендеринга. Основные типы источников:

  • GeoJSON
  • Vector tiles
  • Raster tiles
  • Image sources

Пример GeoJSON источника:

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

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

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

source.setData({
  type: 'FeatureCollection',
  features: [
    {
      type: 'Feature',
      geometry: {
        type: 'Point',
        coordinates: [30.5, 50.45]
      },
      properties: {}
    }
  ]
});

Состояние источника напрямую влияет на перерисовку слоёв, использующих его.


Feature State: динамическое состояние объектов

Feature State — один из наиболее мощных механизмов управления состоянием в Mapbox GL JS. Он позволяет хранить временные данные, связанные с конкретными географическими объектами, без изменения исходного GeoJSON.

Назначение:

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

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

map.setFeatureState(
  {
    source: 'points',
    id: 1
  },
  {
    hover: true
  }
);

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

map.getFeatureState({
  source: 'points',
  id: 1
});

Удаление состояния:

map.removeFeatureState({
  source: 'points',
  id: 1
});

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

'circle-color': [
  'case',
  ['boolean', ['feature-state', 'hover'], false],
  '#ff0000',
  '#3388ff'
]

Feature State отделяет визуальное состояние от данных, что снижает необходимость пересоздания источников при каждом изменении UI.


Управление состоянием через события

Mapbox GL JS предоставляет событийную модель, которая позволяет синхронизировать внутреннее состояние карты с внешними системами.

Основные события:

  • move
  • zoom
  • rotate
  • pitch
  • load
  • render

Пример синхронизации:

map.on('move', () => {
  const center = map.getCenter();
  const zoom = map.getZoom();

  externalStore.update({
    center,
    zoom
  });
});

Обратная синхронизация:

store.subscribe((state) => {
  map.jumpTo({
    center: state.center,
    zoom: state.zoom
  });
});

Такая модель часто используется в архитектуре Flux/Redux.


URL как источник состояния

Часто состояние карты сохраняется в URL, что позволяет воспроизводить конкретный вид карты.

Пример синхронизации:

function updateUrl() {
  const center = map.getCenter();
  const zoom = map.getZoom();

  const hash = `#${zoom}/${center.lat}/${center.lng}`;
  history.replaceState(null, '', hash);
}

Загрузка состояния из URL:

const hash = window.location.hash.replace('#', '');
const [zoom, lat, lng] = hash.split('/');

map.setView({
  center: [parseFloat(lng), parseFloat(lat)],
  zoom: parseFloat(zoom)
});

URL становится внешним хранилищем состояния, что полезно для шаринга и восстановления сессий.


Иммутабельность и управление обновлениями

Несмотря на императивный API, управление состоянием в Mapbox GL JS часто строится по принципу частичных обновлений. Вместо полной перерисовки карты изменяются только конкретные части состояния.

Примеры точечных изменений:

  • setLayoutProperty вместо пересоздания слоя
  • setFeatureState вместо изменения GeoJSON
  • setData вместо пересборки карты

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


Интеграция с внешними state-менеджерами

В приложениях с React или другими UI-фреймворками карта рассматривается как побочный эффект состояния.

Типичная схема:

  • глобальный store хранит состояние UI
  • Mapbox синхронизируется через эффекты
  • события карты обновляют store

Пример логики:

useEffect(() => {
  map.setCenter(store.center);
  map.setZoom(store.zoom);
}, [store.center, store.zoom]);

И обратный поток:

map.on('move', () => {
  dispatch(updateView({
    center: map.getCenter(),
    zoom: map.getZoom()
  }));
});

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


Конфликты состояний и стратегии разрешения

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

  • камера управляется пользователем и программой одновременно
  • Feature State конкурирует с обновлением источника
  • стиль перезаписывается динамически

Типовые стратегии:

  • приоритет пользовательского ввода
  • debounce обновлений камеры
  • изоляция Feature State от данных источника
  • версионирование состояния

Пример debounce:

let timeout;

map.on('move', () => {
  clearTimeout(timeout);
  timeout = setTimeout(() => {
    syncState();
  }, 100);
});

Детерминированность состояния и повторяемость

Одним из ключевых требований к сложным картографическим приложениям является возможность восстановления состояния 1:1.

Для этого сохраняются:

  • center
  • zoom
  • bearing
  • pitch
  • active layers visibility
  • feature states (частично)

Пример сериализации:

const state = {
  center: map.getCenter(),
  zoom: map.getZoom(),
  bearing: map.getBearing(),
  pitch: map.getPitch()
};

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


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

При интенсивных изменениях состояния важна минимизация перерисовок.

Подходы:

  • группировка вызовов (jumpTo вместо отдельных set*)
  • использование requestAnimationFrame
  • ограничение частоты событий
  • минимизация setData

Пример:

map.on('mousemove', (e) => {
  requestAnimationFrame(() => {
    map.setFeatureState(
      { source: 'points', id: hoveredId },
      { hover: true }
    );
  });
});

Архитектурная модель состояния

Состояние Mapbox GL JS можно представить как многослойную систему:

  • Верхний слой: UI state (React/Vue/store)
  • Средний слой: camera + style control
  • Нижний слой: sources + features + GPU rendering state

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