Feature-state

Состояние объектов (feature-state) в Mapbox GL JS представляет собой механизм хранения динамических параметров, привязанных к конкретным геометрическим объектам слоя без изменения исходных данных источника. Такой подход позволяет разделять статические геоданные и временные состояния интерфейса: наведение, выделение, активность, загрузка и любые пользовательские флаги.


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

Feature-state хранится отдельно от:

  • GeoJSON или векторных данных источника
  • стиля слоя
  • фильтров и выражений

Состояние задаётся и читается через API карты, а затем используется внутри стилей через выражения feature-state.

Ключевая идея:

состояние не является частью данных — оно существует только на уровне отображения


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

Feature-state работает только при наличии стабильного идентификатора объекта.

Для GeoJSON:

{
  "type": "Feature",
  "id": 42,
  "properties": {
    "name": "Object A"
  },
  "geometry": {
    "type": "Point",
    "coordinates": [30, 60]
  }
}

Для vector tiles необходимо:

  • наличие feature.id в тайлах
  • или использование promoteId при добавлении источника

Пример:

map.addSource('places', {
  type: 'geojson',
  data: geojsonData,
  promoteId: 'id'
});

Без идентификатора feature-state невозможен, поскольку система не имеет ключа для привязки состояния.


Установка состояния объекта

Основной метод:

map.setFeatureState(
  { source: 'places', id: 42 },
  { hover: true }
);

Второй аргумент — объект состояния, содержащий произвольные ключи:

{
  hover: true,
  selected: false,
  loading: true
}

Состояние не ограничено фиксированной схемой и может расширяться по мере необходимости.


Чтение состояния

const state = map.getFeatureState({
  source: 'places',
  id: 42
});

Возвращается объект текущего состояния:

{
  hover: true,
  selected: false
}

Если состояние не установлено, возвращается пустой объект.


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

Удаление конкретного ключа:

map.removeFeatureState({
  source: 'places',
  id: 42
}, 'hover');

Удаление всего состояния объекта:

map.removeFeatureState({
  source: 'places',
  id: 42
});

Очистка состояния всего источника:

map.removeFeatureState({
  source: 'places'
});

Использование feature-state в стилях

Главная ценность feature-state проявляется в стилях слоя через выражение:

["feature-state", "key"]

Пример изменения цвета при наведении:

map.addLayer({
  id: 'points',
  type: 'circle',
  source: 'places',
  paint: {
    'circle-radius': 6,
    'circle-color': [
      'case',
      ['boolean', ['feature-state', 'hover'], false],
      '#ff0000',
      '#3388ff'
    ]
  }
});

Логика:

  • если hover === true → красный цвет
  • иначе → синий

Типовые сценарии применения

Наведение курсора

Сценарий основан на динамическом обновлении состояния при событиях mousemove и mouseleave.

let hoveredId = null;

map.on('mousemove', 'points', (e) => {
  if (e.features.length === 0) return;

  const id = e.features[0].id;

  if (hoveredId !== null) {
    map.setFeatureState(
      { source: 'places', id: hoveredId },
      { hover: false }
    );
  }

  hoveredId = id;

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

При уходе курсора:

map.on('mouseleave', 'points', () => {
  if (hoveredId !== null) {
    map.setFeatureState(
      { source: 'places', id: hoveredId },
      { hover: false }
    );
  }
  hoveredId = null;
});

Выделение объекта

Feature-state часто используется вместо модификации GeoJSON для хранения выбранного элемента.

map.setFeatureState(
  { source: 'places', id: selectedId },
  { selected: true }
);

В стиле:

'circle-stroke-width': [
  'case',
  ['boolean', ['feature-state', 'selected'], false],
  3,
  1
]

Динамические анимации

Feature-state может управлять визуальными параметрами, изменяющимися во времени:

map.setFeatureState(
  { source: 'places', id: 10 },
  { progress: 0.6 }
);

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

'circle-radius': [
  '*',
  10,
  ['feature-state', 'progress']
]

Состояние загрузки

Частый шаблон — индикация асинхронных операций:

map.setFeatureState(
  { source: 'places', id: 77 },
  { loading: true }
);
'circle-opacity': [
  'case',
  ['boolean', ['feature-state', 'loading'], false],
  0.5,
  1
]

Поведение при обновлении источника

Feature-state не является частью данных источника и не сохраняется при:

  • перезагрузке setData
  • пересоздании слоя
  • обновлении тайлов

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


Производительность и архитектурные преимущества

Использование feature-state снижает необходимость:

  • изменять GeoJSON на лету
  • пересоздавать источники
  • выполнять дорогостоящие setData

Система работает на уровне WebGL-стиля, что позволяет:

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

Ограничения механизма

Feature-state имеет ряд технических ограничений:

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

Практика организации ключей состояния

При увеличении сложности интерфейса состояние структурируется через именование ключей:

{
  hover: true,
  selected: false,
  popupOpen: true,
  loading: false
}

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

{
  ui_hover: true,
  ui_selected: false,
  data_loading: true
}

Это снижает конфликтность состояний при масштабировании приложения.


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

При удалении объектов или смене наборов данных важно очищать state:

map.removeFeatureState({ source: 'places' });

Либо точечно:

map.removeFeatureState({ source: 'places', id: 42 });

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


Взаимодействие с фильтрами и слоями

Feature-state не заменяет фильтрацию данных. Его задача — визуальное представление состояния, тогда как filter управляет составом отображаемых объектов.

Комбинация:

  • filter → какие объекты видим
  • feature-state → как они выглядят

Типовой паттерн архитектуры UI-состояния карты

В крупных приложениях feature-state используется как слой представления над:

  • событиями мыши
  • состоянием приложения
  • результатами API-запросов

Схема взаимодействия:

  1. пользовательское событие
  2. вычисление id объекта
  3. обновление feature-state
  4. реактивное изменение стиля слоя

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