Вложенные группы

В задачах картографической визуализации часто возникает необходимость объединять большое количество объектов в логические блоки. Маркеры, полигоны, линии и растровые слои начинают образовывать сложную структуру, где простое линейное управление слоями становится недостаточным. В таких случаях используется принцип вложенных групп слоёв, при котором группы могут содержать другие группы, формируя древовидную структуру.

В библиотеке Leaflet базовые инструменты группировки представлены классами L.LayerGroup и L.FeatureGroup. Они позволяют объединять слои в единые контейнеры, однако сами по себе не ограничивают вложенность, что делает возможной реализацию многоуровневых структур.


Базовая группировка слоёв

Группировка слоёв в Leaflet строится вокруг контейнера, который управляет набором объектов как единым целым.

const group = L.layerGroup();

const marker1 = L.marker([51.5, -0.09]);
const marker2 = L.marker([51.51, -0.1]);

group.addLayer(marker1);
group.addLayer(marker2);

group.addTo(map);

L.layerGroup объединяет слои без добавления дополнительной логики. Все операции добавления и удаления применяются ко всей группе одновременно.


FeatureGroup как расширенная группа

L.FeatureGroup расширяет функциональность LayerGroup, добавляя события и методы для работы с интерактивными объектами.

const featureGroup = L.featureGroup([
  L.marker([51.5, -0.09]),
  L.circle([51.51, -0.1], { radius: 200 })
]);

featureGroup.addTo(map);

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

featureGroup.on('click', function (e) {
  console.log('Клик по любому объекту группы');
});

Принцип вложенности групп

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

const subgroup = L.layerGroup([
  L.marker([51.5, -0.09]),
  L.marker([51.51, -0.1])
]);

const mainGroup = L.layerGroup();
mainGroup.addLayer(subgroup);

mainGroup.addTo(map);

В этом случае subgroup становится элементом mainGroup, а не набором отдельных маркеров с точки зрения управления.


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

При добавлении вложенной группы на карту происходит рекурсивное добавление всех её дочерних элементов. Это означает, что фактическое отображение всегда разворачивается до уровня конкретных слоёв.

mainGroup.addTo(map);

Результатом становится отображение всех маркеров из subgroup, несмотря на то, что добавлена только верхнеуровневая группа.

Удаление верхнего уровня автоматически удаляет все вложенные элементы.

map.removeLayer(mainGroup);

Управление состоянием вложенных структур

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

mainGroup.addLayer(subgroup);
map.addLayer(mainGroup);

// скрытие всей структуры
map.removeLayer(mainGroup);

Изменение структуры возможно на любом уровне вложенности:

subgroup.addLayer(L.marker([51.52, -0.12]));

Добавленный слой автоматически становится частью общей иерархии.


Комбинирование FeatureGroup и LayerGroup

На практике часто используется комбинация типов групп для разделения ответственности: FeatureGroup применяется для интерактивных объектов, а LayerGroup — для структурной организации.

const citiesGroup = L.featureGroup();
const riversGroup = L.layerGroup();

const europeGroup = L.layerGroup([
  citiesGroup,
  riversGroup
]);

europeGroup.addTo(map);

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


Передача событий через уровни вложенности

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

const cities = L.featureGroup([
  L.marker([51.5, -0.09]).bindPopup('City A'),
  L.marker([51.51, -0.1]).bindPopup('City B')
]);

const regions = L.layerGroup([cities]);

regions.on('click', function () {
  console.log('Событие из вложенной группы');
});

Событие инициируется маркером, но обрабатывается на уровне родительской группы.


Динамическое построение дерева слоёв

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

function buildGroup(data) {
  const group = L.layerGroup();

  data.forEach(item => {
    if (item.type === 'group') {
      group.addLayer(buildGroup(item.children));
    } else {
      group.addLayer(L.marker(item.coords));
    }
  });

  return group;
}

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


Ограничения вложенных групп

В Leaflet отсутствует встроенная визуализация дерева слоёв. Вложенные группы существуют только на уровне логики, без автоматического UI для управления иерархией.

Отсутствуют следующие механизмы:

  • автоматическое отображение структуры групп в контролах слоёв
  • независимое управление видимостью дочерних групп через стандартный Layers Control
  • визуальное дерево без дополнительных расширений

Поведение в Layers Control

L.control.layers не интерпретирует вложенность. Каждая группа рассматривается как единый слой.

const baseLayers = {};
const overlays = {
  "Europe": europeGroup
};

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

Вложенные элементы не отображаются отдельно, что требует ручного управления структурой при необходимости детального контроля.


Использование фабрик групп

Для упрощения построения сложных иерархий применяются фабричные функции, создающие предсказуемую структуру слоёв.

function createRegion(name, points) {
  const group = L.featureGroup();

  points.forEach(p => {
    group.addLayer(L.marker(p.coords));
  });

  return group;
}

const regionA = createRegion('A', [
  { coords: [51.5, -0.09] },
  { coords: [51.51, -0.1] }
]);

const world = L.layerGroup([regionA]);
world.addTo(map);

Рекурсивное управление удалением

Удаление верхнего уровня автоматически приводит к удалению всех дочерних элементов, так как Leaflet обходит структуру рекурсивно.

world.clearLayers();

Метод очищает всю вложенную иерархию, включая подгруппы и отдельные объекты.


Паттерн «дерево слоёв»

Иерархическая структура в Leaflet часто реализуется по принципу дерева, где каждый узел является группой, а листья — конкретными геообъектами.

World
 ├── Continent
 │    ├── Country
 │    │     ├── City
 │    │     ├── City

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

const city = L.layerGroup([L.marker([51.5, -0.09])]);
const country = L.layerGroup([city]);
const continent = L.layerGroup([country]);
const world = L.layerGroup([continent]);

world.addTo(map);

Производительность при глубокой вложенности

Глубокая иерархия увеличивает накладные расходы при добавлении и удалении слоёв, так как Leaflet рекурсивно обрабатывает каждый уровень.

Особенно заметно при:

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

Оптимизация достигается путём минимизации пересборки дерева и повторного использования существующих групп.


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

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

const data = {
  name: 'root',
  children: [
    {
      name: 'group1',
      children: [
        { coords: [51.5, -0.09] }
      ]
    }
  ]
};

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


Управление вложенными группами через ссылки

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

const registry = {
  root: L.layerGroup(),
  sub: L.layerGroup()
};

registry.root.addLayer(registry.sub);
registry.root.addTo(map);

Сохранённые ссылки позволяют модифицировать структуру без её пересоздания.