Векторные данные в Mapbox GL JS чаще всего представлены в формате
GeoJSON, где каждый объект содержит геометрию и набор пользовательских
атрибутов в поле properties. Эти свойства используются для
стилизации, фильтрации, а также реализации интерактивного поведения
карты. Однако архитектура библиотеки предполагает важное ограничение:
геометрия и properties источника данных являются иммутабельными
на уровне отдельных фич, что напрямую влияет на подход к их
редактированию.
Любые изменения properties требуют либо перезаписи источника данных,
либо использования механизма feature-state, который
предназначен для динамического состояния объектов без модификации
исходного GeoJSON.
Каждая фича в GeoJSON имеет стандартную структуру:
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [30.5, 50.5]
},
"properties": {
"name": "Объект A",
"status": "active",
"value": 42
}
}
В контексте Mapbox GL JS эти свойства используются:
paint и layout)Наиболее прямой способ изменить properties — полная замена GeoJSON-источника.
const source = map.getSource('points-source');
source.setData({
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [30.52, 50.45]
},
properties: {
name: 'Обновлённый объект',
status: 'inactive',
value: 100
}
}
]
});
В Mapbox GL JS нельзя напрямую изменить property отдельной фичи внутри источника:
// такого метода не существует
source.updateFeatureProperty(featureId, 'status', 'active');
Это архитектурное ограничение связано с тем, что GeoJSON рассматривается как неизменяемый поток данных.
Для решения задачи изменения состояния отдельных объектов
используется API feature-state. Оно позволяет хранить
временные свойства, не изменяя GeoJSON.
map.setFeatureState(
{
source: 'points-source',
id: 123
},
{
selected: true,
hover: false,
intensity: 0.8
}
);
Для получения состояния используется:
const state = map.getFeatureState({
source: 'points-source',
id: 123
});
Очистка состояния:
map.removeFeatureState({
source: 'points-source',
id: 123
});
Главное преимущество feature-state проявляется в выражениях стилей:
map.addLayer({
id: 'points-layer',
type: 'circle',
source: 'points-source',
paint: {
'circle-radius': [
'case',
['boolean', ['feature-state', 'selected'], false],
10,
5
],
'circle-color': [
'case',
['boolean', ['feature-state', 'hover'], false],
'#ff0000',
'#3388ff'
]
}
});
Здесь свойства selected и hover не являются
частью GeoJSON, но влияют на визуализацию.
id у фичДля использования feature-state каждая фича должна иметь
уникальный идентификатор:
{
"type": "Feature",
"id": 42,
"geometry": {
"type": "Point",
"coordinates": [30.5, 50.5]
},
"properties": {
"name": "Объект"
}
}
Без id работа feature-state невозможна, поскольку Mapbox
GL JS не может сопоставить состояние с конкретной сущностью.
В типичных SPA-приложениях GeoJSON часто хранится во внешнем состоянии (например, Redux, Zustand, reactive store). В этом случае изменение properties реализуется через создание нового массива features.
const updatedFeatures = features.map(f => {
if (f.properties.id === 10) {
return {
...f,
properties: {
...f.properties,
status: 'updated'
}
};
}
return f;
});
map.getSource('points').setData({
type: 'FeatureCollection',
features: updatedFeatures
});
Этот подход сохраняет чистую функциональную модель данных, но требует повторной отправки всего набора в Mapbox.
При взаимодействии с картой часто используется метод:
const features = map.queryRenderedFeatures(point, {
layers: ['points-layer']
});
Важно учитывать, что:
getFeatureStateЭто разделение часто требует объединения данных вручную:
const enriched = features.map(f => ({
...f,
state: map.getFeatureState({
source: 'points-source',
id: f.id
})
}));
Properties активно применяются в фильтрах слоёв:
map.addLayer({
id: 'filtered-points',
type: 'circle',
source: 'points-source',
filter: ['==', ['get', 'status'], 'active']
});
После изменения GeoJSON через setData фильтры
автоматически пересчитываются.
Feature-state в фильтрах напрямую не используется, поэтому для динамических фильтров требуется либо обновление источника, либо использование выражений через paint/layout.
При работе с большим количеством объектов критично выбирать правильную стратегию:
setDatafeature-stateПрактический паттерн:
В сложных интерфейсах часто комбинируются оба механизма:
Пример архитектуры взаимодействия:
// базовое обновление данных
map.getSource('objects').setData(updatedGeoJSON);
// динамическое состояние
map.setFeatureState(
{ source: 'objects', id: selectedId },
{ selected: true }
);
Такой подход обеспечивает разделение ответственности между данными и интерфейсным состоянием, что особенно важно при работе с интерактивными картами высокой плотности объектов.