Работа с данными в 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);
});
}
Часто необходимо показывать только те объекты, которые находятся в текущей видимой области карты.
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 в 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);
});
}
Такой подход требует аккуратного управления состоянием фильтров, особенно при их динамическом изменении.
При большом количестве объектов линейный перебор становится узким местом. Применяются вспомогательные структуры:
Пример индексирования по категориям:
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);
});
}
Этот подход снижает нагрузку на клиент, но увеличивает зависимость от сети.
При частых обновлениях фильтров важно минимизировать количество операций перерисовки:
setMap(null))let timeout;
input.addEventListener("input", (e) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
applyFilter(e.target.value);
}, 150);
});
Для тепловых слоёв используется отдельный объект:
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);
}
});
При использовании нескольких слоёв (маршруты, точки интереса, пользовательские объекты) фильтрация становится слоистой:
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);
});
});
Этот метод позволяет создавать адаптивные карты с разной плотностью данных в зависимости от уровня детализации.