Фильтрация данных

Работа с данными в Google Maps Platform в JavaScript почти всегда сводится к управлению множеством объектов, отображаемых на карте: маркерами, полигонами, линиями, тепловыми картами и объектами слоя данных. При увеличении количества элементов возникает необходимость динамической фильтрации — изменения набора отображаемых объектов без перезагрузки карты и без повторных запросов к серверу.

Фильтрация в контексте Google Maps JavaScript API реализуется на нескольких уровнях: в памяти браузера, через Data Layer API, через обновление видимой области карты, а также на стороне источника данных (backend).


Базовая фильтрация маркеров через управление видимостью

Наиболее простой и распространённый способ фильтрации — управление свойством setVisible() у маркеров.

markers.forEach(marker => {
  const shouldShow = marker.category === "restaurant";
  marker.setVisible(shouldShow);
});

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

Часто используется комбинированная фильтрация:

  • по категории
  • по диапазону цен
  • по состоянию (активен/неактивен)
  • по географическому признаку
function applyFilter(category) {
  markers.forEach(marker => {
    marker.setVisible(category === "all" || marker.category === category);
  });
}

Фильтрация по области карты (viewport filtering)

Часто необходимо показывать только те объекты, которые находятся в текущей видимой области карты.

const bounds = map.getBounds();

markers.forEach(marker => {
  const position = marker.getPosition();
  const visible = bounds.contains(position);
  marker.setVisible(visible);
});

Этот метод используется в сценариях:

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

Событие bounds_changed позволяет автоматически запускать фильтрацию:

map.addListener("bounds_changed", () => {
  const bounds = map.getBounds();

  markers.forEach(marker => {
    marker.setVisible(bounds.contains(marker.getPosition()));
  });
});

Оптимизация фильтрации через кластеризацию

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

const clusterer = new markerClusterer.MarkerClusterer({ map, markers });

function filterCluster(category) {
  const filtered = markers.filter(m => category === "all" || m.category === category);
  clusterer.clearMarkers();
  clusterer.addMarkers(filtered);
}

Кластеризация снижает нагрузку на DOM и ускоряет перерисовку карты.


Фильтрация через Data Layer (структурированные данные)

Data Layer в API предназначен для работы с GeoJSON-подобными структурами и предоставляет более декларативный способ фильтрации.

map.data.setStyle(feature => {
  const category = feature.getProperty("category");

  return {
    visible: category === "restaurant",
    icon: category === "restaurant" ? "restaurant.png" : "default.png"
  };
});

Фильтрация может быть основана на свойствах объектов:

map.data.forEach(feature => {
  const isVisible = feature.getProperty("rating") > 4;
  map.data.overrideStyle(feature, { visible: isVisible });
});

Data Layer удобен тем, что позволяет централизованно управлять стилями и логикой отображения.


Географическая фильтрация через полигоны

Фильтрация может выполняться не только по bounds, но и по принадлежности точки к произвольной геометрии.

const polygon = new google.maps.Polygon({ paths: coordinates });

markers.forEach(marker => {
  const inside = google.maps.geometry.poly.containsLocation(
    marker.getPosition(),
    polygon
  );

  marker.setVisible(inside);
});

Этот подход используется для:

  • выделения районов города
  • построения зон доставки
  • анализа покрытия

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

Интерактивные фильтры часто связаны с элементами UI: input, select, autocomplete.

document.getElementById("search").addEventListener("input", (e) => {
  const query = e.target.value.toLowerCase();

  markers.forEach(marker => {
    const match = marker.title.toLowerCase().includes(query);
    marker.setVisible(match);
  });
});

При использовании Autocomplete фильтрация может быть связана с выбранным place:

autocomplete.addListener("place_changed", () => {
  const place = autocomplete.getPlace();
  map.setCenter(place.geometry.location);
});

Комбинированная фильтрация (многокритериальная логика)

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

function filterMarkers({ category, minRating, bounds }) {
  markers.forEach(marker => {
    const matchCategory = category === "all" || marker.category === category;
    const matchRating = marker.rating >= minRating;
    const matchBounds = bounds.contains(marker.getPosition());

    marker.setVisible(matchCategory && matchRating && matchBounds);
  });
}

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


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

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

  • Map по категориям
  • spatial index (R-tree)
  • grid indexing

Пример индексирования по категориям:

const index = new Map();

markers.forEach(marker => {
  if (!index.has(marker.category)) {
    index.set(marker.category, []);
  }
  index.get(marker.category).push(marker);
});

function showCategory(category) {
  markers.forEach(m => m.setVisible(false));

  (index.get(category) || []).forEach(m => m.setVisible(true));
}

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

В некоторых архитектурах данные не фильтруются на клиенте, а запрашиваются заново:

async function loadFiltered(category) {
  const response = await fetch(`/api/places?category=${category}`);
  const data = await response.json();

  clearMarkers();

  data.forEach(item => {
    const marker = new google.maps.Marker({
      position: item.position,
      map,
      title: item.name
    });

    markers.push(marker);
  });
}

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


Управление производительностью при частой фильтрации

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

  • группировка изменений
  • debounce на события input
  • временное отключение карты (setMap(null))
  • повторное включение после обновления
let timeout;

input.addEventListener("input", (e) => {
  clearTimeout(timeout);

  timeout = setTimeout(() => {
    applyFilter(e.target.value);
  }, 150);
});

Фильтрация тепловых карт (Heatmap)

Для тепловых слоёв используется отдельный объект:

heatmap.setData(
  allPoints.filter(p => p.weight > 5)
);

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


Связь фильтрации с состоянием приложения

В сложных приложениях фильтрация становится частью общего состояния UI:

  • выбранная категория
  • текущие границы карты
  • активные слои
  • параметры пользователя

Рекомендуется хранить фильтры в централизованном объекте состояния:

const state = {
  category: "all",
  minRating: 0
};

function updateState(newState) {
  Object.assign(state, newState);
  applyFilters();
}

Фильтрация событийных слоёв и динамических объектов

Некоторые объекты могут изменяться в реальном времени (например, транспорт или трекинг). В таких случаях фильтрация применяется поверх потоковых обновлений:

socket.on("update", data => {
  const marker = markers.find(m => m.id === data.id);

  if (marker) {
    marker.setPosition(data.position);
    marker.setVisible(state.category === data.category);
  }
});

Итеративная фильтрация и многослойные карты

При использовании нескольких слоёв (маршруты, точки интереса, пользовательские объекты) фильтрация становится слоистой:

  • фильтр слоя A не влияет на слой B
  • каждый слой имеет собственные правила отображения
  • итоговая видимость — пересечение условий слоёв
function isVisible(marker) {
  return layerAFilter(marker) && layerBFilter(marker);
}

Поведение при изменении масштаба

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

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

  markers.forEach(marker => {
    const visible = zoom > 10 || marker.importance === "high";
    marker.setVisible(visible);
  });
});

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