Динамическое изменение фильтров

В MapLibre GL JS фильтрация применяется на уровне слоя и определяет, какие объекты из источника данных будут визуализированы. Каждый слой может содержать выражение фильтра, которое вычисляется для каждого фичи и возвращает логическое значение. Если результат истинный — объект отображается, иначе исключается из рендеринга.

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

Функция установки фильтра:

map.setFilter(layerId, filterExpression);

Удаление фильтра:

map.setFilter(layerId, null);

Фильтрация применяется мгновенно и пересчитывается на GPU-пайплайне рендеринга.


Синтаксис выражений фильтрации

Фильтры в MapLibre GL JS представляют собой массивы в стиле expression syntax.

Базовый формат:

["==", ["get", "propertyName"], value]

Пример фильтра по типу объекта:

map.setFilter("points-layer", ["==", ["get", "type"], "restaurant"]);

Фильтр по числовому диапазону:

map.setFilter("points-layer", [
  ">=",
  ["get", "population"],
  100000
]);

Фильтр по наличию значения:

map.setFilter("points-layer", ["has", "name"]);

Логические операторы в фильтрах

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

AND

["all",
  ["==", ["get", "type"], "restaurant"],
  [">", ["get", "rating"], 4]
]

OR

["any",
  ["==", ["get", "type"], "cafe"],
  ["==", ["get", "type"], "bar"]
]

NOT

["!", ["==", ["get", "status"], "closed"]]

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


Динамическое изменение фильтров на событиях

Фильтры часто изменяются в ответ на пользовательские действия или события интерфейса. Типичный сценарий — переключение категорий через UI.

const categories = ["restaurant", "cafe", "bar"];

function setCategory(category) {
  map.setFilter("places-layer", [
    "==",
    ["get", "type"],
    category
  ]);
}

Изменение может происходить при клике:

button.addEventListener("click", () => {
  setCategory("cafe");
});

При каждом вызове setFilter слой пересчитывает видимость объектов без перезагрузки источника.


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

Для выбора нескольких категорий используется оператор in или any.

Вариант через any

map.setFilter("places-layer", [
  "any",
  ["==", ["get", "type"], "restaurant"],
  ["==", ["get", "type"], "bar"]
]);

Вариант через in

map.setFilter("places-layer", [
  "in",
  ["get", "type"],
  "restaurant",
  "bar",
  "cafe"
]);

Второй вариант предпочтительнее при динамическом формировании списка категорий.


Динамическая фильтрация через состояние интерфейса

Часто фильтры зависят от набора параметров: чекбоксов, слайдеров, диапазонов дат.

Пример с чекбоксами категорий

const activeTypes = new Set(["restaurant", "cafe"]);

function updateFilter() {
  map.setFilter("places-layer", [
    "in",
    ["get", "type"],
    ...Array.from(activeTypes)
  ]);
}

Изменение состояния:

checkbox.addEventListener("change", (e) => {
  if (e.target.checked) {
    activeTypes.add(e.target.value);
  } else {
    activeTypes.delete(e.target.value);
  }
  updateFilter();
});

Фильтрация по диапазонам значений

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

function setRatingFilter(minRating) {
  map.setFilter("places-layer", [
    ">=",
    ["get", "rating"],
    minRating
  ]);
}

Двусторонний диапазон:

function setRange(min, max) {
  map.setFilter("places-layer", [
    "all",
    [">=", ["get", "rating"], min],
    ["<=", ["get", "rating"], max]
  ]);
}

Динамическая фильтрация по зуму

Фильтрация может зависеть от масштаба карты.

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

  if (zoom < 10) {
    map.setFilter("places-layer", ["<", ["get", "importance"], 5]);
  } else {
    map.setFilter("places-layer", ["has", "id"]);
  }
});

Такой подход позволяет снижать плотность объектов на малых масштабах.


Фильтрация через queryRenderedFeatures

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

const features = map.queryRenderedFeatures({
  layers: ["places-layer"],
  filter: ["==", ["get", "type"], "restaurant"]
});

Это позволяет синхронизировать UI-логику с текущей визуализацией.


Обновление источника вместо фильтрации

При сложных сценариях фильтрация слоя заменяется обновлением данных источника.

const source = map.getSource("places");

source.setData(filteredGeojson);

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


Feature-state и динамические фильтры

Feature-state используется для динамических визуальных изменений без изменения данных и фильтров слоя.

map.setFeatureState(
  { source: "places", id: 123 },
  { selected: true }
);

В фильтре можно учитывать состояние:

map.setFilter("places-layer", [
  "==",
  ["feature-state", "selected"],
  true
]);

Этот подход позволяет реализовывать интерактивное выделение объектов.


Производительность динамических фильтров

Частое обновление фильтров может приводить к нагрузке на рендеринг, особенно при больших GeoJSON-источниках.

Ключевые принципы оптимизации:

  • минимизация вызовов setFilter
  • использование debounce при работе с UI
  • избегание сложных вложенных выражений без необходимости
  • предпочтение in вместо множества ==
  • перенос вычислений в подготовку данных

Пример debounce:

let timeout;

function updateDebounced(filter) {
  clearTimeout(timeout);
  timeout = setTimeout(() => {
    map.setFilter("places-layer", filter);
  }, 100);
}

Частые сценарии динамической фильтрации

Категорийные фильтры

Использование набора переключателей для типов объектов:

["in", ["get", "category"], "park", "museum", "theater"]

Фильтрация по времени

map.setFilter("events-layer", [
  ">=",
  ["get", "timestamp"],
  currentTimestamp
]);

Комбинированные фильтры UI + данные

map.setFilter("places-layer", [
  "all",
  ["in", ["get", "type"], ...selectedTypes],
  [">=", ["get", "rating"], minRating],
  ["!=", ["get", "status"], "closed"]
]);

Ошибки при динамической фильтрации

  • использование JavaScript-логики внутри filter вместо expression syntax
  • попытка фильтровать данные вне слоя через setFilter
  • частое пересоздание идентичных фильтров
  • передача некорректных типов значений (строка вместо числа)
  • отсутствие проверки наличия свойства в данных
  • чрезмерно сложные вложенные выражения без необходимости оптимизации