Управление состоянием карты

Управление состоянием карты в OpenLayers опирается на связку объектов Map, View, слоёв, источников данных и событийной модели Observable. Состояние карты в данном контексте — это совокупность параметров, определяющих текущее отображение и поведение: координаты центра, масштаб, поворот, видимость слоёв, параметры источников, активные взаимодействия и пользовательские настройки интерфейса.

Основной носитель визуального состояния — объект ol/View. Он инкапсулирует параметры, которые напрямую влияют на отображение:

  • центр карты (center)
  • масштаб (zoom)
  • разрешение (resolution)
  • проекция (projection)
  • поворот (rotation)

Изменение любого из этих параметров приводит к перерасчёту отображения слоя и перерисовке карты.

Ключевые методы управления состоянием:

  • setCenter(coordinate) — установка центра
  • setZoom(level) — изменение масштаба
  • setRotation(angle) — поворот карты
  • setResolution(resolution) — прямое управление разрешением

Пример программного изменения состояния:

view.setCenter([4180000, 7500000]);
view.setZoom(6);
view.setRotation(0);

Получение текущего состояния осуществляется через соответствующие геттеры:

const center = view.getCenter();
const zoom = view.getZoom();
const rotation = view.getRotation();

Важной особенностью является то, что View является реактивным объектом: изменения немедленно транслируются в систему рендеринга карты.

Событийная модель состояния

Состояние карты в OpenLayers тесно связано с системой событий. Все ключевые объекты наследуют ol/Observable, что позволяет отслеживать изменения.

Основные события, влияющие на состояние:

  • moveend — завершение перемещения карты
  • change:center — изменение центра
  • change:resolution — изменение масштаба/разрешения
  • change:rotation — изменение поворота
  • propertychange — изменение любого свойства

Пример отслеживания изменения положения карты:

view.on('change:center', () => {
  const center = view.getCenter();
});

Для отслеживания завершения интерактивных изменений используется:

map.on('moveend', () => {
  const view = map.getView();
  const state = {
    center: view.getCenter(),
    zoom: view.getZoom(),
    rotation: view.getRotation()
  };
});

Событие moveend особенно важно при синхронизации состояния с внешними системами хранения или URL.

Состояние карты (Map state)

Объект ol/Map агрегирует состояние представления, слоёв и взаимодействий. Он не хранит географическое состояние напрямую, но управляет связями между компонентами.

Ключевые аспекты состояния Map:

  • список слоёв (layer collection)
  • текущий View
  • взаимодействия (interactions)
  • элементы управления (controls)
  • размер контейнера (size)

Слои хранят собственное состояние видимости и порядка отображения:

layer.setVisible(true);
layer.setZIndex(10);

Состояние коллекции слоёв является реактивным:

map.getLayers().on('add', (event) => {
  const layer = event.element;
});

Состояние слоёв и источников данных

Каждый слой (ol/layer/Tile, ol/layer/Vector) содержит внутреннее состояние, влияющее на отображение:

  • видимость (visible)
  • прозрачность (opacity)
  • порядок (zIndex)
  • источник данных (source)

Источники данных (ol/source) также имеют состояние загрузки и кэширования:

  • состояние загрузки тайлов
  • кэшированные объекты
  • актуальность данных

Пример управления состоянием слоя:

layer.setVisible(false);
layer.setOpacity(0.5);
layer.setZIndex(5);

Для векторных источников:

vectorSource.clear();
vectorSource.addFeature(feature);

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

Реактивное состояние через события слоёв

Слои и источники поддерживают событийную модель:

layer.on('change:visible', () => {
  const visible = layer.getVisible();
});

Для источников:

vectorSource.on('addfeature', (event) => {
  const feature = event.feature;
});

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

Синхронизация состояния карты

Одним из ключевых аспектов является сохранение и восстановление состояния. Типичная структура состояния включает:

  • центр
  • масштаб
  • поворот
  • активные слои
  • параметры отображения

Сериализация состояния:

const state = {
  center: view.getCenter(),
  zoom: view.getZoom(),
  rotation: view.getRotation(),
  layers: map.getLayers().getArray().map(l => ({
    visible: l.getVisible(),
    opacity: l.getOpacity()
  }))
};

Восстановление состояния:

view.setCenter(state.center);
view.setZoom(state.zoom);
view.setRotation(state.rotation);

state.layers.forEach((lState, i) => {
  const layer = map.getLayers().item(i);
  layer.setVisible(lState.visible);
  layer.setOpacity(lState.opacity);
});

Подобный подход формирует базу для клиентского persistence без привязки к серверу.

Состояние через URL (deep linking)

Распространённый подход — кодирование состояния в URL. Это позволяет сохранять:

  • позицию карты
  • масштаб
  • активные слои

Пример формирования URL-параметров:

const params = new URLSearchParams();

params.set('c', view.getCenter().join(','));
params.set('z', view.getZoom());
params.set('r', view.getRotation());

window.location.hash = params.toString();

Восстановление:

const params = new URLSearchParams(window.location.hash.slice(1));

const center = params.get('c')?.split(',').map(Number);
const zoom = Number(params.get('z'));
const rotation = Number(params.get('r'));

if (center) view.setCenter(center);
if (!isNaN(zoom)) view.setZoom(zoom);
if (!isNaN(rotation)) view.setRotation(rotation);

Такой подход превращает карту в адресуемое состояние, пригодное для шаринга.

Иммутабельные паттерны управления состоянием

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

Схема:

  • внешний store (Redux-подобный)
  • синхронизация с View
  • подписка на события карты

Пример логики синхронизации:

store.subscribe((state) => {
  const view = map.getView();

  view.setCenter(state.center);
  view.setZoom(state.zoom);
});

И обратная синхронизация:

view.on('moveend', () => {
  store.dispatch({
    type: 'MAP_UPDATED',
    center: view.getCenter(),
    zoom: view.getZoom()
  });
});

Подобный подход предотвращает расхождение состояния между UI и картой.

Управление состоянием взаимодействий

Взаимодействия (ol/interaction) также являются частью состояния карты:

  • панорамирование
  • масштабирование
  • выделение объектов
  • рисование

Каждое взаимодействие можно включать и выключать:

selectInteraction.setActive(false);
drawInteraction.setActive(true);

Состояние взаимодействий часто зависит от режима приложения (просмотр, редактирование, анализ).

Коллекция взаимодействий также реактивна:

map.getInteractions().forEach((interaction) => {
  interaction.setActive(false);
});

Состояние рендеринга и жизненного цикла

Карта имеет внутреннее состояние рендеринга, которое отслеживается через события:

  • precompose
  • postcompose
  • rendercomplete

Пример отслеживания завершения отрисовки:

map.on('rendercomplete', () => {
  const canvas = map.getViewport().querySelector('canvas');
});

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

Инвалидация состояния и перерисовка

Изменение большинства параметров автоматически вызывает перерасчёт отображения. Однако иногда требуется принудительная инвалидация:

map.render();

или

layer.changed();

Это переводит объект в состояние, требующее повторного рендеринга.

Иерархия состояния

Состояние OpenLayers формируется на нескольких уровнях:

  • уровень View: геометрия отображения
  • уровень Map: композиция объектов
  • уровень Layer: визуализация данных
  • уровень Source: данные и кэш
  • уровень Interaction: поведение пользователя

Эта иерархия создаёт многослойную модель, в которой изменения на нижнем уровне могут каскадно влиять на верхние уровни.

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

При динамических данных (например, потоковые обновления) состояние карты становится непрерывно изменяющимся процессом.

Типичная схема:

  • обновление источника (addFeature, removeFeature)
  • реакция слоя
  • перерисовка карты
  • обновление View при необходимости

Пример обновления:

setInterval(() => {
  const feature = new Feature({
    geometry: new Point([Math.random() * 1e6, Math.random() * 1e6])
  });

  vectorSource.addFeature(feature);
}, 1000);

Такой режим формирует потоковое состояние, в котором карта выступает визуализатором изменяющейся модели данных.