Bounds и их форматы

Работа с картой в геоинформационных интерфейсах неизбежно связана с понятием ограниченной области отображения. В контексте MapLibre GL JS эта область представляется объектом границ (bounds), который описывает прямоугольник на поверхности Земли в координатах долготы и широты. Границы используются для позиционирования камеры, ограничения перемещения карты, вычисления видимой области и анализа геоданных.


Система координат и базовая структура bounds

Bounds в MapLibre GL JS опираются на географическую систему координат WGS84:

  • longitude (lng) — долгота, ось X
  • latitude (lat) — широта, ось Y

Границы всегда задаются парой углов:

  • юго-западный угол (south-west)
  • северо-восточный угол (north-east)

Стандартная структура:

[[west, south], [east, north]]

где:

  • west — минимальная долгота
  • south — минимальная широта
  • east — максимальная долгота
  • north — максимальная широта

Основные формы представления bounds

Массив координат

Наиболее распространённый формат — вложенный массив:

const bounds = [
  [37.62, 55.73], // south-west (lng, lat)
  [37.70, 55.80]  // north-east (lng, lat)
];

Этот формат используется при передаче параметров в методы камеры и утилиты карты.


Объект LngLatBounds

Внутренний тип bounds в MapLibre GL JS представлен классом LngLatBounds, который инкапсулирует логику работы с прямоугольной географической областью.

Создание:

const bounds = new maplibregl.LngLatBounds(
  [37.62, 55.73],
  [37.70, 55.80]
);

Особенности:

  • хранит два угла (SW и NE)
  • автоматически нормализует координаты
  • поддерживает расширение области
  • учитывает особенности пересечения 180-го меридиана

GeoJSON bbox

В геопространственных форматах bounds часто представляются как bbox:

[west, south, east, north]

Пример:

const bbox = [37.62, 55.73, 37.70, 55.80];

Этот формат используется в GeoJSON-спецификациях и часто конвертируется в LngLatBounds.


Методы работы с LngLatBounds

set()

Полностью переопределяет границы:

bounds.set(
  [10, 10],
  [20, 20]
);

Используется при полной замене области.


extend()

Расширяет текущие границы, включая новую точку или другой bounds:

bounds.extend([30, 40]);
bounds.extend(otherBounds);

Поведение:

  • пересчитывает min/max значения
  • увеличивает прямоугольник
  • не уменьшает уже заданную область

getNorthEast() и getSouthWest()

Возвращают угловые точки:

const ne = bounds.getNorthEast();
const sw = bounds.getSouthWest();

Результат — объекты LngLat с методами lng() и lat().


toArray()

Возвращает массивный формат:

bounds.toArray();

Результат:

[[west, south], [east, north]]

Использование bounds в камере карты

fitBounds()

Одна из ключевых операций — автоматическое масштабирование карты под заданные границы:

map.fitBounds(bounds, {
  padding: 20,
  duration: 1000
});

Параметры:

  • padding — отступ от краёв экрана
  • duration — длительность анимации
  • maxZoom — ограничение приближения

Алгоритм:

  1. вычисляется оптимальный zoom
  2. центрируется камера
  3. учитываются размеры viewport

setMaxBounds()

Ограничивает перемещение карты:

map.setMaxBounds(bounds);

Поведение:

  • камера не может выйти за пределы области
  • инерция движения также ограничивается
  • при попытке выхода происходит “упругое” возвращение

Нормализация координат

Bounds автоматически приводятся к корректному диапазону:

  • широта: [-90, 90]
  • долгота: [-180, 180]

Пример нормализации:

new maplibregl.LngLatBounds(
  [200, 100],
  [400, 200]
);

Результат будет приведён к допустимым значениям.


Пересечение антимеридиана

Особый случай — пересечение 180° долготы.

Пример:

west = 170
east = -170

Это означает, что область пересекает линию смены дат.

Поведение:

  • bounds считается “завёрнутым”
  • внутренняя логика учитывает wrap-around
  • операции extend и contains работают в циклическом пространстве

Проверка включения точек

Bounds поддерживает проверку принадлежности:

bounds.contains([lng, lat]);

Возвращает:

  • true — точка внутри
  • false — вне области

Логика учитывает:

  • нормализацию координат
  • пересечение антимеридиана
  • границы включительно

Пустые и неинициализированные bounds

Создание без параметров:

const bounds = new maplibregl.LngLatBounds();

Состояние:

  • отсутствуют валидные границы
  • первая операция extend() инициализирует область

Типичное применение:

const b = new maplibregl.LngLatBounds();
data.forEach(p => b.extend(p));

Комбинирование bounds

Объединение двух областей

bounds.extend(otherBounds);

Результат:

  • минимальный охватывающий прямоугольник
  • объединение всех экстремумов

Построение bounds из набора точек

const b = new maplibregl.LngLatBounds();

points.forEach(p => b.extend(p));

Используется для:

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

Оптимизация работы с bounds

При большом количестве операций важно учитывать:

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

Паттерн:

const b = new maplibregl.LngLatBounds();
for (const feature of features) {
  b.extend(feature.geometry.coordinates);
}
map.fitBounds(b);

Частые ошибки при работе

Перепутанный порядок координат

Неверно:

[lat, lng]

Правильно:

[lng, lat]

Инвертированные границы

Если west > east или south > north без учёта wrap-around, bounds становится некорректным.


Игнорирование антимеридиана

Обычное сравнение чисел ломает логику глобальных bounds.


Взаимодействие с экранной системой координат

Bounds в геокоординатах часто конвертируются в pixel-space через:

  • проектирование (projection)
  • viewport transformation
  • zoom-dependent scaling

Это используется внутри:

  • fitBounds
  • hit-testing
  • rendering viewport

Практическая роль bounds в архитектуре карт

Bounds выступают связующим звеном между:

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

Типичные сценарии:

  • загрузка тайлов по области
  • запрос объектов в bbox
  • динамическое центрирование карты
  • ограничение пользовательской навигации