В Mapbox GL JS стиль карты представляет собой полностью описанную
визуальную конфигурацию: набор источников данных (sources), слоёв
(layers), спрайтов, шрифтов и правил отрисовки. При вызове
map.setStyle() происходит полная замена текущего стиля
новым JSON-описанием.
Ключевой момент: все пользовательские слои и источники,
добавленные через addLayer и addSource,
удаляются при смене стиля, если не предусмотрена их повторная
инициализация.
Это поведение обусловлено архитектурой рендеринга: стиль рассматривается как атомарная единица, которая перезагружается целиком.
При смене стиля последовательность событий выглядит следующим образом:
styledatastyle.load (в зависимости от версии
API)Именно на этапе 3 происходит потеря пользовательских слоёв.
При наличии пользовательских визуализаций (heatmap, clusters, route layers, overlays) возникает типичная проблема:
setStyle() они исчезаютЭта проблема усиливается при частых переключениях стилей (например, светлый/тёмный режим или разные тематические карты).
Практика устойчивого восстановления слоёв начинается с отделения описания слоёв от самого Mapbox-стиля.
Обычно используется структура:
beforeId)const customLayers = [
{
id: 'route-line',
type: 'line',
source: 'route-source',
paint: {
'line-color': '#ff0000',
'line-width': 4
}
},
{
id: 'stations',
type: 'circle',
source: 'stations-source',
paint: {
'circle-radius': 6,
'circle-color': '#0066ff'
}
}
];
Наиболее устойчивый механизм — восстановление состояния после полной загрузки стиля.
Событие styledata срабатывает многократно, поэтому
требуется фильтрация по состоянию загрузки:
map.on('styledata', () => {
if (!map.isStyleLoaded()) return;
restoreSources();
restoreLayers();
});
Функция восстановления источников:
function restoreSources() {
const existingSources = map.getStyle().sources;
if (!existingSources['route-source']) {
map.addSource('route-source', {
type: 'geojson',
data: routeGeojson
});
}
if (!existingSources['stations-source']) {
map.addSource('stations-source', {
type: 'geojson',
data: stationsGeojson
});
}
}
Функция восстановления слоёв:
function restoreLayers() {
const style = map.getStyle();
const existingLayers = style.layers.map(l => l.id);
customLayers.forEach(layer => {
if (existingLayers.includes(layer.id)) return;
map.addLayer(layer);
});
}
В Mapbox GL JS порядок отрисовки определяется порядком слоёв в стиле. При восстановлении важно учитывать контекст базового стиля.
map.addLayer(layer, 'waterway-label');
В этом примере слой вставляется ниже подписи водных объектов. Однако после смены стиля идентификаторы слоёв базовой карты могут отличаться, поэтому требуется адаптивная стратегия.
Разные стили Mapbox (streets, dark, satellite) содержат разные наборы внутренних слоёв:
Поэтому фиксированный beforeId часто становится
невалидным.
Решение — динамический поиск подходящей позиции:
function getLabelLayerId() {
const layers = map.getStyle().layers;
const labelLayer = layers.find(
l => l.type === 'symbol' && l.layout && l.layout['text-field']
);
return labelLayer ? labelLayer.id : undefined;
}
Наиболее надёжная схема строится вокруг явного контроля смены стиля.
function changeStyle(styleUrl) {
map.setStyle(styleUrl);
map.once('style.load', () => {
restoreSources();
restoreLayers();
});
}
Альтернативный вариант с защитой от гонок:
map.on('style.load', () => {
requestAnimationFrame(() => {
restoreSources();
restoreLayers();
});
});
При сложных интерфейсах используется централизованный реестр состояния:
const mapState = {
sources: {},
layers: []
};
При добавлении объектов:
mapState.sources['route-source'] = {
type: 'geojson',
data: routeGeojson
};
mapState.layers.push({
id: 'route-line',
type: 'line',
source: 'route-source',
paint: {
'line-color': '#ff0000'
}
});
Такой подход позволяет полностью пересобирать визуальный слой при любом стиле.
Хотя setStyle() не сохраняет пользовательские слои, он
может использоваться как триггер пересборки интерфейса.
const currentState = {
sources: JSON.parse(JSON.stringify(mapState.sources)),
layers: JSON.parse(JSON.stringify(mapState.layers))
};
map.setStyle(newStyle);
map.once('style.load', () => {
Object.entries(currentState.sources).forEach(([id, source]) => {
map.addSource(id, source);
});
currentState.layers.forEach(layer => {
map.addLayer(layer);
});
});
Разные типы источников требуют разной стратегии восстановления:
tileSizemap.addLayer(layer); // ошибка, если стиль не загружен
Корректно:
if (map.isStyleLoaded()) {
map.addLayer(layer);
}
При повторном вызове восстановления без проверки существования возникает ошибка:
Error: Layer with id already exists
Решение — проверка через getStyle().layers.
Без указания beforeId пользовательские слои оказываются
поверх всех элементов или под всеми элементами, нарушая визуальную
структуру.
В сложных приложениях используется разделение:
Такой подход позволяет воспринимать Mapbox GL JS как реактивную систему, где стиль — лишь контейнер для отрисовки, а не источник состояния.
При частой смене стилей важно учитывать:
Для предотвращения конфликтов применяется блокировка перехода:
let styleSwitching = false;
function safeSetStyle(style) {
if (styleSwitching) return;
styleSwitching = true;
map.setStyle(style);
map.once('style.load', () => {
restoreLayers();
styleSwitching = false;
});
}