Фильтрация объектов

Работа с фильтрацией объектов в Leaflet строится вокруг управления слоями (layers), группами объектов и функций отображения данных на основе их свойств. Архитектура библиотеки Leaflet предполагает, что каждый визуальный элемент карты существует как слой, что позволяет гибко включать, исключать и модифицировать отображаемые данные без перерисовки всей карты.

Фильтрация в Leaflet не является отдельным встроенным механизмом уровня «query», как в базах данных. Вместо этого используется управление наборами слоёв:

  • добавление слоя на карту (addTo(map))
  • удаление слоя (removeLayer)
  • включение/выключение групп слоёв
  • условное создание слоёв при загрузке данных

Каждый объект карты (маркер, полилиния, полигон) может быть помещён в отдельную логическую группу, что позволяет управлять видимостью через бизнес-логику приложения.

Фильтрация маркеров через условное создание слоёв

Наиболее простой способ фильтрации — не добавлять объекты, не соответствующие условию.

const points = [
  { lat: 48.8566, lng: 2.3522, type: "museum" },
  { lat: 48.8606, lng: 2.3376, type: "park" },
  { lat: 48.8529, lng: 2.3499, type: "museum" }
];

const museumsLayer = L.layerGroup().addTo(map);

points.forEach(p => {
  if (p.type === "museum") {
    L.marker([p.lat, p.lng]).addTo(museumsLayer);
  }
});

Такой подход формирует статическую фильтрацию на этапе построения слоя. Изменение фильтра требует пересоздания группы.

Использование LayerGroup для динамической фильтрации

Для динамической фильтрации удобно хранить все объекты в одной группе и управлять их видимостью:

const allMarkers = L.layerGroup().addTo(map);

const markerA = L.marker([48.85, 2.35], { category: "museum" });
const markerB = L.marker([48.86, 2.33], { category: "park" });

allMarkers.addLayer(markerA);
allMarkers.addLayer(markerB);

Фильтрация выполняется обходом слоёв:

function filterByCategory(category) {
  allMarkers.eachLayer(layer => {
    const match = layer.options.category === category;

    if (match) {
      if (!map.hasLayer(layer)) layer.addTo(map);
    } else {
      if (map.hasLayer(layer)) map.removeLayer(layer);
    }
  });
}

Такой метод обеспечивает мгновенное переключение видимости без пересоздания объектов.

Фильтрация GeoJSON через параметр filter

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

const geojsonData = {
  type: "FeatureCollection",
  features: [
    {
      type: "Feature",
      properties: { type: "museum" },
      geometry: {
        type: "Point",
        coordinates: [2.3522, 48.8566]
      }
    },
    {
      type: "Feature",
      properties: { type: "park" },
      geometry: {
        type: "Point",
        coordinates: [2.3376, 48.8606]
      }
    }
  ]
};

Применение фильтра:

const layer = L.geoJSON(geojsonData, {
  filter: function(feature) {
    return feature.properties.type === "museum";
  },
  pointToLayer: function(feature, latlng) {
    return L.marker(latlng);
  }
}).addTo(map);

Функция filter возвращает true или false, определяя включение объекта в слой.

Переключаемая фильтрация GeoJSON

Для динамического изменения фильтра GeoJSON слой обычно пересоздаётся:

let currentLayer;

function render(filterType) {
  if (currentLayer) {
    map.removeLayer(currentLayer);
  }

  currentLayer = L.geoJSON(geojsonData, {
    filter: f => f.properties.type === filterType
  }).addTo(map);
}

Этот подход проще логически, но менее эффективен при больших объёмах данных.

Использование FeatureGroup для сложной фильтрации

FeatureGroup позволяет работать с коллекцией объектов как с единым контейнером:

const group = L.featureGroup().addTo(map);

group.addLayer(L.marker([48.85, 2.35], { tags: ["culture"] }));
group.addLayer(L.marker([48.86, 2.33], { tags: ["nature"] }));

Фильтрация по массиву тегов:

function filterByTag(tag) {
  group.eachLayer(layer => {
    const tags = layer.options.tags || [];
    const visible = tags.includes(tag);

    if (visible) {
      layer.addTo(map);
    } else {
      map.removeLayer(layer);
    }
  });
}

Комбинированная фильтрация по нескольким параметрам

Реальные сценарии часто требуют фильтрации по нескольким условиям одновременно:

function complexFilter({ type, minYear }) {
  allMarkers.eachLayer(layer => {
    const data = layer.options;

    const matchType = !type || data.type === type;
    const matchYear = !minYear || data.year >= minYear;

    const visible = matchType && matchYear;

    if (visible) {
      layer.addTo(map);
    } else {
      map.removeLayer(layer);
    }
  });
}

Такой подход позволяет реализовать логические комбинации AND/OR прямо на клиенте.

Фильтрация с использованием стилей (для полигонов и линий)

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

function filterPolygons(type) {
  geojsonLayer.eachLayer(layer => {
    const match = layer.feature.properties.type === type;

    layer.setStyle({
      opacity: match ? 1 : 0.1,
      fillOpacity: match ? 0.5 : 0.05
    });
  });
}

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

Группировка слоёв для категорийной фильтрации

Эффективный подход — заранее разделить данные на категории:

const museums = L.layerGroup();
const parks = L.layerGroup();

L.marker([48.85, 2.35]).addTo(museums);
L.marker([48.86, 2.33]).addTo(parks);

museums.addTo(map);
parks.addTo(map);

Переключение:

function toggle(category, enabled) {
  const layer = category === "museum" ? museums : parks;

  if (enabled) {
    layer.addTo(map);
  } else {
    map.removeLayer(layer);
  }
}

Использование Layer Control как интерфейса фильтрации

Встроенный компонент управления слоями часто применяется как визуальный фильтр:

const baseMaps = {};
const overlays = {
  Museums: museums,
  Parks: parks
};

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

Каждый overlay фактически представляет фильтр отображения категории.

Оптимизация фильтрации при большом объёме данных

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

  • минимизация пересоздания слоёв
  • использование LayerGroup вместо удаления/создания маркеров
  • отказ от частых setStyle на больших GeoJSON
  • батчинг операций через временное отключение карты (map.remove() / map.add() при необходимости)

Пример батчинга:

function batchFilter(predicate) {
  map.eachLayer(layer => {
    if (layer instanceof L.Marker) {
      const visible = predicate(layer.options);

      layer._icon.style.display = visible ? "block" : "none";
    }
  });
}

Фильтрация через пользовательские индексы

Для сложных сценариев используется предварительное индексирование данных:

const index = {
  museum: [],
  park: []
};

markers.forEach(m => {
  index[m.options.type].push(m);
});

function show(type) {
  Object.keys(index).forEach(key => {
    index[key].forEach(marker => {
      if (key === type) {
        marker.addTo(map);
      } else {
        map.removeLayer(marker);
      }
    });
  });
}

Такой подход снижает стоимость фильтрации при повторных операциях.

Итоговая модель фильтрации в Leaflet

Фильтрация объектов в Leaflet формируется через несколько уровней управления:

  • создание слоёв с заранее заданными условиями
  • динамическое добавление и удаление объектов
  • использование GeoJSON filter для декларативной логики
  • изменение стилей как альтернатива скрытию
  • группировка слоёв по категориям
  • управление видимостью через контроль слоёв
  • оптимизация через индексацию и кэширование структуры данных