Как получить границы видимой области карты в Yandex Maps API (ymaps3)
При разработке картографических приложений на React с Yandex Maps API часто требуется отслеживать текущие границы видимой области. Это необходимо для подгрузки маркеров, кластеризации или синхронизации с другими компонентами. Рассмотрим корректные способы получения bounds в ymaps3.
Встроенный метод getBounds()
В Yandex Maps API третьей версии (ymaps3) метод getBounds() доступен, но его вызов зависит от состояния карты. Если карта ещё не инициализирована или произошла ошибка, метод может вернуть null. Используйте проверку:
if (typeof mapInstance.getBounds === 'function') {
const bounds = mapInstance.getBounds();
if (bounds) {
// bounds - массив [[minLat, minLon], [maxLat, maxLon]]
}
}Этот метод возвращает границы в системе координат WGS-84 (широта и долгота). Рекомендуется использовать его как основной.
Ручной расчет через center и zoom
Если getBounds() недоступен или возвращает null, можно вычислить границы вручную. Для этого нужны:
- center - координаты центра карты (широта, долгота)
- zoom - уровень масштабирования
- size - размер контейнера карты в пикселях
Формула расчета:
const zoomFactor = Math.pow(2, zoom);
const latDelta = (size.y / 256) / zoomFactor;
const lonDelta = (size.x / 256) / zoomFactor;
const bounds = {
minLat: center[0] - latDelta,
maxLat: center[0] + latDelta,
minLon: center[1] - lonDelta,
maxLon: center[1] + lonDelta
};Этот метод даёт приблизительное значение, так как не учитывает проекцию Меркатора и искажения на полюсах. Для большинства приложений точности достаточно.
Обработка изменений границ
Чтобы реагировать на изменение видимой области, подпишитесь на событие update карты:
mapInstance.events.add('update', () => {
const bounds = getVisibleBounds();
// обновление состояния
});Для оптимизации используйте debounce (например, из lodash) с задержкой 300-500 мс. Если событие update не срабатывает (редкий случай), можно применить setInterval с проверкой каждую секунду.
Пример хука для React
Ниже представлен универсальный хук, который сначала пытается получить bounds через getBounds(), а при неудаче вычисляет вручную:
import { useEffect, useRef } from 'react';
import debounce from 'lodash/debounce';
export const useMapBounds = (mapInstance) => {
const prevBounds = useRef(null);
useEffect(() => {
if (!mapInstance) return;
const getVisibleBounds = () => {
try {
if (typeof mapInstance.getBounds === 'function') {
const bounds = mapInstance.getBounds();
if (bounds) {
return {
minLat: bounds[0][0],
maxLat: bounds[1][0],
minLon: bounds[0][1],
maxLon: bounds[1][1]
};
}
}
if (mapInstance.center && mapInstance.zoom) {
const center = mapInstance.center;
const zoom = mapInstance.zoom;
const size = mapInstance.size || { x: 943, y: 954 };
const zoomFactor = Math.pow(2, zoom);
const latDelta = size.y / 256 / zoomFactor;
const lonDelta = size.x / 256 / zoomFactor;
return {
minLat: center[0] - latDelta,
maxLat: center[0] + latDelta,
minLon: center[1] - lonDelta,
maxLon: center[1] + lonDelta
};
}
return null;
} catch (error) {
console.error('Error getting bounds:', error);
return null;
}
};
const handleBoundsChange = debounce(() => {
const bounds = getVisibleBounds();
if (bounds) {
console.log('Bounds updated:', bounds);
}
}, 500);
if (mapInstance.events?.add) {
mapInstance.events.add('update', handleBoundsChange);
} else {
setInterval(handleBoundsChange, 1000);
}
setTimeout(handleBoundsChange, 1000);
}, [mapInstance]);
};Рекомендации
- Всегда проверяйте наличие метода
getBounds()перед вызовом. - Для точного расчета используйте
getBounds(), ручной метод - как fallback. - Не забывайте очищать подписки на события при размонтировании компонента.
- Учитывайте, что
mapInstance.sizeможет отсутствовать - задавайте значение по умолчанию.