В задачах картографической визуализации часто возникает необходимость объединять большое количество объектов в логические блоки. Маркеры, полигоны, линии и растровые слои начинают образовывать сложную структуру, где простое линейное управление слоями становится недостаточным. В таких случаях используется принцип вложенных групп слоёв, при котором группы могут содержать другие группы, формируя древовидную структуру.
В библиотеке 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 объединяет слои без добавления
дополнительной логики. Все операции добавления и удаления применяются ко
всей группе одновременно.
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 — для структурной организации.
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 ControlL.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);
Сохранённые ссылки позволяют модифицировать структуру без её пересоздания.