Control.Layers: переключатель слоев

Leaflet предоставляет модуль управления слоями через Control.Layers, предназначенный для переключения между базовыми картографическими подложками и дополнительными слоями (оверлеями). Этот компонент решает задачу динамического включения и отключения визуальных слоёв карты без необходимости пересоздавать сам объект карты.

L.control.layers представляет собой UI-контрол, который отображается поверх карты и содержит:

  • список базовых слоёв (base layers), где активен только один слой одновременно;
  • список оверлеев (overlays), которые могут включаться одновременно в любом количестве;
  • переключатели (radio buttons для base layers и checkbox для overlays).

Базовая идея заключается в разделении всех слоёв на две категории:

Base Layers

  • взаимоисключающие;
  • используются как подложка карты;
  • обычно это разные тайловые сервисы (OpenStreetMap, satellite, terrain).

Overlays

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

Создание Control.Layers

Базовая инициализация выполняется через передачу объектов:

const osm = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '© OpenStreetMap contributors'
});

const satellite = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
  attribution: '© OpenTopoMap contributors'
});

const cities = L.layerGroup();
const rivers = L.layerGroup();

const baseMaps = {
  "OpenStreetMap": osm,
  "Satellite": satellite
};

const overlays = {
  "Города": cities,
  "Реки": rivers
};

const map = L.map('map', {
  center: [51.1694, 71.4491],
  zoom: 5,
  layers: [osm, cities]
});

L.control.layers(baseMaps, overlays).addTo(map);

Логика работы base layers

Base layers реализуются как взаимоисключающие переключатели. При выборе нового слоя происходит:

  • отключение текущего активного слоя;
  • удаление его из карты;
  • добавление выбранного слоя.

Особенность заключается в том, что Control.Layers не хранит копии слоёв, а управляет ссылками на уже созданные объекты.

Работа с overlays

Overlays функционируют независимо друг от друга. Каждый слой:

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

Пример добавления слоя GeoJSON:

const geojsonLayer = L.geoJSON(data, {
  style: {
    color: "blue",
    weight: 2
  }
});

geojsonLayer.addTo(map);

L.control.layers(baseMaps, {
  "Границы": geojsonLayer
}).addTo(map);

Добавление слоёв динамически

Control.Layers поддерживает добавление новых слоёв после создания:

const control = L.control.layers(baseMaps, overlays).addTo(map);

const parks = L.layerGroup();

control.addOverlay(parks, "Парки");

Также возможно добавление базового слоя:

const terrain = L.tileLayer('https://{s}.tile.stamen.com/terrain/{z}/{x}/{y}.jpg');

control.addBaseLayer(terrain, "Terrain");

Удаление слоёв из контроллера

Удаление выполняется через соответствующие методы:

control.removeLayer(parks);
control.removeLayer(terrain);

Важно: удаление из Control.Layers не удаляет слой с карты автоматически. Управление отображением остаётся отдельной задачей:

map.removeLayer(parks);

Опции Control.Layers

Конфигурация контроллера включает ряд параметров:

L.control.layers(baseMaps, overlays, {
  collapsed: true,
  position: 'topright',
  autoZIndex: false
}).addTo(map);

collapsed

  • true — контрол свернут по умолчанию;
  • false — список всегда раскрыт.

Используется для управления плотностью интерфейса.

position

Определяет расположение контроллера:

  • topleft
  • topright
  • bottomleft
  • bottomright

autoZIndex

Если включено (true), Leaflet автоматически управляет z-index слоёв при переключении base layers. Это полезно при использовании тайловых слоёв, но может мешать при сложной кастомной композиции.

Управление порядком слоёв

Control.Layers влияет только на отображение элементов интерфейса, но не на порядок отрисовки напрямую. Порядок слоёв контролируется:

  • порядком добавления на карту;
  • pane-механизмом;
  • bringToFront() и bringToBack().

Пример:

markers.addTo(map).bringToFront();

Группировка слоёв

Для сложных проектов используется L.layerGroup или L.featureGroup:

const citiesGroup = L.layerGroup([city1, city2, city3]);
const roadsGroup = L.layerGroup([road1, road2]);

L.control.layers(baseMaps, {
  "Города": citiesGroup,
  "Дороги": roadsGroup
}).addTo(map);

FeatureGroup дополнительно поддерживает события и вычисления границ:

const group = L.featureGroup([marker1, marker2]);

map.fitBounds(group.getBounds());

События Control.Layers

Контроллер генерирует события при взаимодействии:

map.on('overlayadd', function (e) {
  console.log('Добавлен слой:', e.name);
});

map.on('overlayremove', function (e) {
  console.log('Удалён слой:', e.name);
});

map.on('baselayerchange', function (e) {
  console.log('Активный базовый слой:', e.name);
});

Структура объекта события:

  • name — имя слоя;
  • layer — объект слоя.

Кастомизация интерфейса

Контрол можно модифицировать через CSS:

.leaflet-control-layers {
  font-size: 13px;
  background: #fff;
  border-radius: 6px;
}

.leaflet-control-layers-expanded {
  padding: 10px;
}

Дополнительно можно создавать кастомные контролы на основе L.Control, если стандартный интерфейс недостаточен.

Доступ к внутренним структурам

Control.Layers хранит ссылки на слои в приватных структурах, но доступ к ним возможен косвенно через добавление и удаление:

const control = L.control.layers(baseMaps, overlays);

control.addTo(map);

control.addOverlay(L.marker([0, 0]), "Новая точка");

Прямое чтение внутренних списков не предусмотрено API.

Использование с динамическими данными

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

function updateGeoJSON(data) {
  if (map.hasLayer(geojsonLayer)) {
    map.removeLayer(geojsonLayer);
    control.removeLayer(geojsonLayer);
  }

  geojsonLayer = L.geoJSON(data);
  control.addOverlay(geojsonLayer, "Данные");
  geojsonLayer.addTo(map);
}

Особенности поведения при множественных базовых слоях

Хотя интерфейс допускает несколько base layers в объекте, активным всегда остаётся только один. Leaflet гарантирует:

  • деактивацию предыдущего слоя;
  • атомарное переключение;
  • отсутствие одновременного отображения базовых тайлов (если не реализована кастомная логика).

Интеграция с TileLayer и кастомными провайдерами

Control.Layers одинаково работает с:

  • L.tileLayer
  • L.imageOverlay
  • L.geoJSON
  • L.layerGroup

Пример image overlay:

const image = L.imageOverlay('map.png', bounds);

L.control.layers(baseMaps, {
  "Изображение": image
}).addTo(map);

Производительность при большом количестве слоёв

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

  • группировка слоёв в категории;
  • динамическая подгрузка контролов;
  • фильтрация отображаемых элементов;
  • ленивое добавление слоёв.
const categories = {
  "Инфраструктура": infraGroup,
  "Природа": natureGroup
};

Поведение при удалении карты

При вызове map.remove() контрол также уничтожается, но слои остаются в памяти, если не удалены явно. Это важно при SPA-приложениях, где карта пересоздаётся многократно.

Связь с состоянием карты

Control.Layers не хранит состояние UI как источник истины. Источник состояния — сама карта:

  • map.hasLayer(layer)
  • map.addLayer(layer)
  • map.removeLayer(layer)

Контрол лишь отражает текущее состояние.

if (!map.hasLayer(parks)) {
  map.addLayer(parks);
}