Google Maps JavaScript API предоставляет доступ к модели отображения карты, где видимая область (viewport) определяется текущими параметрами центра, масштаба и проекции. Работа с маркерами в контексте видимой области требует понимания границ карты, событий изменения viewport и способов эффективной фильтрации объектов, привязанных к географическим координатам.
Видимая область карты представляется объектом
LatLngBounds, который описывает прямоугольник в
географических координатах. Он определяется юго-западной и
северо-восточной точками.
const bounds = map.getBounds();
Объект границ становится доступным только после полной инициализации
рендеринга карты. В ранних стадиях жизненного цикла он может возвращать
null, что требует привязки логики к событиям изменения
состояния карты.
Ключевые характеристики viewport:
zoom)center)Каждый маркер имеет географическую позицию, получаемую через
getPosition():
const position = marker.getPosition();
Проверка видимости выполняется через принадлежность координаты границам viewport:
function isMarkerVisible(marker, map) {
const bounds = map.getBounds();
if (!bounds) return false;
return bounds.contains(marker.getPosition());
}
Метод contains реализует географическую проверку
попадания точки внутрь прямоугольника текущего viewport.
Для динамической обработки видимости маркеров используется событие
bounds_changed. Оно вызывается при любом изменении видимой
области.
map.addListener("bounds_changed", () => {
updateMarkersVisibility();
});
Дополнительно часто используется событие idle, которое
возникает после завершения всех анимаций и инерционных перемещений
карты:
map.addListener("idle", () => {
updateMarkersVisibility();
});
Событие idle предпочтительно для тяжелых вычислений, так
как вызывается реже и после стабилизации состояния карты.
При большом количестве маркеров прямое управление их отображением становится критичным для производительности. Базовая стратегия заключается в переключении видимости:
function updateMarkersVisibility() {
const bounds = map.getBounds();
if (!bounds) return;
markers.forEach(marker => {
const visible = bounds.contains(marker.getPosition());
marker.setVisible(visible);
});
}
Метод setVisible(false) скрывает маркер без удаления из
памяти, что позволяет быстро переключать отображение при изменении
viewport.
При тысячах маркеров линейная проверка становится узким местом. Сложность O(n) приводит к задержкам при каждом изменении viewport. Используются следующие подходы:
Данные предварительно индексируются в структуру, позволяющую быстро выбирать кандидатов:
Пример концептуальной фильтрации через геохеш:
const visibleCells = getVisibleGeoHashCells(map.getBounds());
const candidates = markersByCell.filter(cell =>
visibleCells.includes(cell.hash)
);
Для предотвращения избыточных вычислений применяется throttle:
function throttle(fn, delay) {
let lastCall = 0;
return function() {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
fn();
}
};
}
map.addListener("bounds_changed", throttle(updateMarkersVisibility, 100));
LatLngBounds для массовой фильтрации данныхФильтрация данных до создания маркеров часто эффективнее, чем управление уже созданными объектами:
function filterDataWithinBounds(data, map) {
const bounds = map.getBounds();
if (!bounds) return [];
return data.filter(item =>
bounds.contains(new google.maps.LatLng(item.lat, item.lng))
);
}
Такой подход снижает нагрузку на DOM и рендеринг, особенно при
использовании стандартных Marker объектов.
Уровень zoom напрямую влияет на плотность отображения объектов. На низком zoom большое количество маркеров перекрывается, что делает фильтрацию по viewport недостаточной без дополнительной логики.
Пример адаптивной фильтрации:
function updateMarkersVisibility() {
const bounds = map.getBounds();
const zoom = map.getZoom();
if (!bounds) return;
markers.forEach(marker => {
const position = marker.getPosition();
const inViewport = bounds.contains(position);
const visible = inViewport && zoom > 6;
marker.setVisible(visible);
});
}
При плотном распределении точек используется кластеризация. Библиотека @googlemaps/markerclusterer объединяет маркеры в группы в зависимости от текущего zoom и viewport.
import { MarkerClusterer } from "@googlemaps/markerclusterer";
const clusterer = new MarkerClusterer({ map, markers });
Кластеризация автоматически пересчитывается при изменении viewport, что снимает необходимость ручной фильтрации видимости.
Карты используют проекцию Mercator, при которой координаты преобразуются в пиксельное пространство. Это позволяет вычислять видимую область не только в географических координатах, но и в экранных пикселях:
const projection = map.getProjection();
const point = projection.fromLatLngToPoint(marker.getPosition());
Этот механизм используется при создании кастомных оверлеев и сложных визуализаций, где требуется точная синхронизация с экранной системой координат.
При работе с динамическими слоями данных часто применяется предрасчёт:
Пример структуры:
const grid = new Map();
function addToGrid(item) {
const key = getCellKey(item.lat, item.lng);
if (!grid.has(key)) grid.set(key, []);
grid.get(key).push(item);
}
Далее фильтрация выполняется только по активным ячейкам viewport.
При изменении долготы карта может циклически повторяться (world wrap). Это влияет на проверку видимости, так как один и тот же объект может быть видим в нескольких экземплярах координатной системы.
В таких случаях проверка bounds.contains может давать
неожиданные результаты, и требуется нормализация координат или работа в
пиксельной системе проекции.
Для масштабируемых решений важно отделять:
Такое разделение позволяет:
При сложных интерактивных сценариях обновление видимости синхронизируется с циклом рендеринга браузера:
let scheduled = false;
function scheduleUpdate() {
if (scheduled) return;
scheduled = true;
requestAnimationFrame(() => {
updateMarkersVisibility();
scheduled = false;
});
}
map.addListener("bounds_changed", scheduleUpdate);
Это снижает нагрузку при частых событиях перемещения карты.
При переходе от стандартных маркеров к кастомным оверлеям контроль видимости переносится на уровень DOM-элементов. В этом случае viewport становится основным источником истины для отображения.
function updateOverlayVisibility(overlay, map) {
const bounds = map.getBounds();
if (!bounds) return;
const visible = bounds.contains(overlay.position);
overlay.element.style.display = visible ? "block" : "none";
}
В системах с высокой плотностью геоданных применяется комбинированный подход:
Такой подход обеспечивает стабильную производительность при десятках тысяч объектов и динамическом изменении viewport без деградации взаимодействия с картой