Видимость слоев

В системе рендеринга Mapbox GL JS каждый слой управляется через объект описания слоя, где ключевую роль в управлении отображением играет поле layout.visibility. Оно определяет, будет ли слой участвовать в отрисовке карты или временно исключён из визуализации без удаления из стиля.

Основные допустимые значения:

  • visible — слой отображается
  • none — слой скрыт
map.addLayer({
  id: 'roads-layer',
  type: 'line',
  source: 'composite',
  'source-layer': 'road',
  layout: {
    visibility: 'visible'
  },
  paint: {
    'line-color': '#ff0000',
    'line-width': 2
  }
});

Скрытие слоя без удаления из стиля:

map.setLayoutProperty('roads-layer', 'visibility', 'none');

Возврат отображения:

map.setLayoutProperty('roads-layer', 'visibility', 'visible');

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


Внутренняя модель отображения слоёв

Mapbox GL JS использует декларативную модель стиля, где каждый слой участвует в графе рендеринга. При этом visibility — это не просто CSS-подобное свойство, а часть layout-стадии, определяющей участие слоя в композиции кадра.

Рендеринг проходит несколько этапов:

  1. Загрузка данных источников
  2. Преобразование данных в векторные тайлы
  3. Применение фильтров слоя
  4. Вычисление layout-параметров
  5. Проверка visibility
  6. Растеризация и отрисовка

Если visibility = none, слой исключается на шаге композиции, что делает его невидимым без удаления вычислительного контекста.


Управление видимостью через API

Основной способ динамического управления слоями — метод setLayoutProperty.

map.setLayoutProperty('buildings-layer', 'visibility', 'none');
map.setLayoutProperty('buildings-layer', 'visibility', 'visible');

Перед изменением свойства важно убедиться, что слой уже добавлен в стиль:

if (map.getLayer('buildings-layer')) {
  map.setLayoutProperty('buildings-layer', 'visibility', 'none');
}

Для получения текущего состояния используется getLayoutProperty:

const state = map.getLayoutProperty('buildings-layer', 'visibility');

Отличие visibility от удаления слоя

Удаление слоя через removeLayer полностью исключает его из графа стиля:

map.removeLayer('roads-layer');

В отличие от этого:

  • visibility: none сохраняет слой в памяти
  • фильтры остаются активными
  • источник продолжает использоваться другими слоями
  • переключение происходит мгновенно без пересборки стиля

Удаление слоя оправдано только при полной очистке конфигурации, тогда как visibility используется для переключения режимов отображения.


Влияние на производительность

Скрытие слоя через visibility: none снижает нагрузку на GPU только на стадии отрисовки. Однако:

  • векторные тайлы продолжают загружаться
  • стиль продолжает учитывать слой при пересчёте состояния
  • фильтры и выражения остаются частью вычислительного графа

Поэтому большое количество скрытых слоёв может незначительно влиять на производительность, особенно при сложных выражениях paint и filter.


Иерархия слоёв и влияние на видимость

Порядок слоёв в стиле влияет на итоговое отображение. Даже если слой видим, он может быть перекрыт другим слоем.

map.addLayer({
  id: 'water',
  type: 'fill',
  source: 'composite',
  'source-layer': 'water',
  paint: {
    'fill-color': '#00bfff'
  }
});

map.addLayer({
  id: 'labels',
  type: 'symbol',
  source: 'composite',
  'source-layer': 'place_label'
});

В данном случае labels будет отрисован поверх water, если не изменить порядок добавления или использовать beforeId.

map.addLayer(layerConfig, 'labels');

Таким образом, визуальная «видимость» слоя складывается из двух факторов:

  • layout.visibility
  • порядок рендеринга (z-index в терминах Mapbox GL JS)

Зависимость видимости от масштаба карты

Хотя visibility управляется напрямую, часто используется косвенное управление через масштаб:

  • minzoom
  • maxzoom
map.addLayer({
  id: 'poi-layer',
  type: 'symbol',
  source: 'composite',
  'source-layer': 'poi',
  minzoom: 12,
  maxzoom: 18,
  layout: {
    visibility: 'visible'
  }
});

Такой слой не исчезает через visibility, но перестаёт отображаться вне диапазона масштабов.

Комбинация этих механизмов позволяет создавать сложные стратегии отображения:

  • visibility — ручное переключение
  • zoom-ограничения — автоматическое управление

Переключение слоёв как режимов отображения

Типичная архитектура картографических приложений использует слои как взаимно исключающие режимы:

function setTheme(theme) {
  map.setLayoutProperty('light-map', 'visibility', theme === 'light' ? 'visible' : 'none');
  map.setLayoutProperty('dark-map', 'visibility', theme === 'dark' ? 'visible' : 'none');
}

Такой подход позволяет:

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

Условная видимость через выражения

Хотя visibility принимает только строковые значения, более гибкая логика реализуется через фильтры и выражения case внутри paint/layout свойств.

Пример имитации скрытия по условию:

map.setPaintProperty('roads-layer', 'line-opacity', [
  'case',
  ['==', ['get', 'class'], 'secondary'],
  0,
  1
]);

Хотя слой остаётся visible, визуально часть данных скрывается.

Это позволяет обходить ограничение бинарности visibility.


Работа с несколькими источниками и пересекающимися слоями

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

map.setLayoutProperty('roads-line', 'visibility', 'none');

При этом:

  • roads-label остаётся активным
  • roads-halo может продолжать отображаться
  • тайлы источника не выгружаются

Это важно при построении слоёв-композиций, где геометрия и подписи разделены.


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

Часто переключение слоёв привязывается к событиям карты:

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

  if (zoom < 10) {
    map.setLayoutProperty('detail-layer', 'visibility', 'none');
  } else {
    map.setLayoutProperty('detail-layer', 'visibility', 'visible');
  }
});

Такой подход позволяет реализовать адаптивные карты, где детализация изменяется в зависимости от масштаба.

Однако частое изменение visibility в обработчиках событий может приводить к лишним пересчётам стиля, поэтому предпочтительнее использовать minzoom и maxzoom, если логика линейна.


Влияние фильтров на восприятие видимости

Фильтры слоя могут создавать эффект «пустого слоя», который технически остаётся видимым:

map.setFilter('cities-layer', ['==', 'name', 'NonExistingCity']);

В этом случае:

  • visibility = visible
  • слой активен
  • но геометрия не проходит фильтр

Такой слой визуально эквивалентен скрытому, но сохраняет вычислительный контекст фильтрации.


Синхронизация состояния видимости в интерфейсе

В сложных приложениях состояние visibility часто синхронизируется с UI:

function toggleLayer(id, state) {
  map.setLayoutProperty(id, 'visibility', state ? 'visible' : 'none');
}

При этом важно учитывать:

  • состояние стиля может измениться извне
  • несколько контроллеров могут влиять на один слой
  • требуется централизованное хранение состояния

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

При первом рендере карты слои с visibility: none:

  • загружаются в структуру стиля
  • участвуют в инициализации
  • но не попадают в финальный кадр

Изменение visibility до события load может не дать эффекта, если слой ещё не добавлен:

map.on('load', () => {
  map.setLayoutProperty('roads-layer', 'visibility', 'none');
});

Комбинирование visibility с layer groups

Хотя Mapbox GL JS не имеет встроенных групп слоёв, логика группировки реализуется через соглашения об именовании:

  • roads-*
  • buildings-*
  • labels-*

Пример массового переключения:

const layers = ['roads-main', 'roads-secondary', 'roads-minor'];

layers.forEach(id => {
  map.setLayoutProperty(id, 'visibility', 'none');
});

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


Поведение при пересоздании стиля

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

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

После этого:

  • слои пересоздаются
  • пользовательские состояния теряются
  • требуется повторное применение логики отображения

Поэтому состояние видимости часто хранится вне Mapbox и восстанавливается после загрузки стиля через style.load.