Тултипы и информационные панели

В OpenLayers отображение всплывающих элементов поверх карты строится на механизме ol/Overlay. Overlay привязывает HTML-элемент к координатам карты и синхронизирует его положение при перемещении и масштабировании.

Ключевая особенность:

  • overlay существует отдельно от canvas/renderer карты
  • позиционирование выполняется через географические координаты
  • содержимое полностью управляется DOM

Базовая структура overlay:

import Overlay from 'ol/Overlay.js';

const tooltipElement = document.createElement('div');
tooltipElement.className = 'ol-tooltip';

const tooltipOverlay = new Overlay({
  element: tooltipElement,
  offset: [10, 0],
  positioning: 'bottom-left',
  stopEvent: false
});

map.addOverlay(tooltipOverlay);

Параметр stopEvent: false позволяет событиям карты проходить сквозь тултип, что важно для интерактивных сценариев.


Динамические тултипы при наведении курсора

Тултипы часто используются для отображения атрибутов объектов при движении мыши над картой. Основной механизм — обработка события pointermove.

map.on('pointermove', function (event) {
  const feature = map.forEachFeatureAtPixel(event.pixel, function (feature) {
    return feature;
  });

  if (feature) {
    const coordinate = event.coordinate;
    const name = feature.get('name');

    tooltipElement.innerHTML = name;
    tooltipOverlay.setPosition(coordinate);
  } else {
    tooltipOverlay.setPosition(undefined);
  }
});

Метод forEachFeatureAtPixel выполняет hit-detection по слоям и возвращает первый найденный объект.

Особенности поведения:

  • при отсутствии объекта overlay скрывается через setPosition(undefined)
  • координаты берутся из события карты, а не из геометрии объекта
  • содержимое обновляется без пересоздания DOM

Стилизация тултипов через CSS

Внешний вид полностью определяется стилями:

.ol-tooltip {
  background: rgba(20, 20, 20, 0.85);
  color: #fff;
  padding: 6px 10px;
  border-radius: 4px;
  font-size: 12px;
  white-space: nowrap;
}

Часто применяется визуальная привязка стрелкой:

.ol-tooltip::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 10px;
  border-width: 6px;
  border-style: solid;
  border-color: rgba(20, 20, 20, 0.85) transparent transparent transparent;
}

Клики по объектам и закрепляемые popups

Тултипы при наведении обычно кратковременные, тогда как информационные окна фиксируются по клику. Для этого используется тот же Overlay, но с обработкой события click.

map.on('click', function (event) {
  const feature = map.forEachFeatureAtPixel(event.pixel, f => f);

  if (feature) {
    const coord = event.coordinate;

    popupElement.innerHTML = `
      <div class="popup-title">${feature.get('name')}</div>
      <div class="popup-body">${feature.get('description')}</div>
    `;

    popupOverlay.setPosition(coord);
  } else {
    popupOverlay.setPosition(undefined);
  }
});

Поведение отличается:

  • позиция фиксируется до следующего клика
  • содержимое может быть структурированным HTML
  • часто используется кнопка закрытия

Автопозиционирование и предотвращение выхода за экран

Overlay поддерживает автоматическое смещение при приближении к краям карты:

const popupOverlay = new Overlay({
  element: popupElement,
  autoPan: {
    animation: {
      duration: 250
    }
  }
});

Механизм autoPan сдвигает центр карты так, чтобы popup оставался видимым.

Дополнительные параметры:

  • autoPanMargin — отступ от края
  • autoPanAnimation — управление плавностью

Работа с несколькими слоями и приоритетом объектов

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

map.on('pointermove', function (event) {
  let featureFound = null;

  map.forEachFeatureAtPixel(event.pixel, function (feature, layer) {
    if (layer && layer.get('selectable')) {
      featureFound = feature;
      return true;
    }
  });

  if (featureFound) {
    tooltipElement.innerHTML = featureFound.get('title');
    tooltipOverlay.setPosition(event.coordinate);
  }
});

Фильтрация по слою позволяет исключать служебные или фоновые данные.


Tooltip для кластеров

При использовании кластеризации (ol/source/Cluster) объект содержит массив оригинальных features.

map.on('pointermove', function (event) {
  const feature = map.forEachFeatureAtPixel(event.pixel, f => f);

  if (feature) {
    const features = feature.get('features');

    if (features && features.length > 1) {
      tooltipElement.innerHTML = `Объектов: ${features.length}`;
    } else {
      tooltipElement.innerHTML = features[0].get('name');
    }

    tooltipOverlay.setPosition(event.coordinate);
  }
});

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

  • кластер — это виртуальный feature
  • доступ к данным через get('features')
  • различается поведение одиночных и групповых объектов

Информационные панели вне карты

Информационные панели часто реализуются как отдельные DOM-блоки, синхронизированные с выбором объектов карты.

Базовая модель взаимодействия:

  • карта генерирует событие выбора
  • состояние хранится в переменной или сторе
  • панель рендерится на основе выбранного feature
let selectedFeature = null;

map.on('click', function (event) {
  selectedFeature = map.forEachFeatureAtPixel(event.pixel, f => f);

  renderSidebar(selectedFeature);
});

Функция рендера:

function renderSidebar(feature) {
  const panel = document.getElementById('sidebar');

  if (!feature) {
    panel.innerHTML = '';
    return;
  }

  panel.innerHTML = `
    <h3>${feature.get('name')}</h3>
    <p>${feature.get('description')}</p>
  `;
}

Синхронизация панели с изменениями источников данных

При динамических данных (WebSocket, API обновления) требуется реактивное обновление панели:

vectorSource.on('change', function () {
  if (selectedFeature) {
    renderSidebar(selectedFeature);
  }
});

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

  • feature может быть обновлён или пересоздан
  • требуется проверка валидности ссылки на объект
  • иногда применяется идентификатор вместо прямой ссылки

Связывание тултипов и панели состояния

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

  • tooltip: краткая информация при наведении
  • popup: расширенная карточка при клике
  • sidebar: постоянная панель состояния

Пример приоритета:

map.on('pointermove', handleTooltip);
map.on('click', handlePopupAndSidebar);

Такое разделение снижает нагрузку на DOM и упрощает управление состоянием.


Работа с WMS GetFeatureInfo для информационных окон

Для серверных слоёв применяется запрос GetFeatureInfo.

map.on('click', function (event) {
  const viewResolution = map.getView().getResolution();
  const url = wmsLayer.getSource().getFeatureInfoUrl(
    event.coordinate,
    viewResolution,
    'EPSG:3857',
    { INFO_FORMAT: 'application/json' }
  );

  fetch(url)
    .then(res => res.json())
    .then(data => {
      popupElement.innerHTML = JSON.stringify(data, null, 2);
      popupOverlay.setPosition(event.coordinate);
    });
});

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

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

Управление состоянием overlay при масштабировании

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

map.getView().on('change:resolution', function () {
  tooltipOverlay.setPosition(undefined);
});

Причина:

  • изменение масштаба делает ранее найденные пиксельные попадания неактуальными
  • предотвращается «залипание» подсказок

Производительность при частых pointermove событиях

Событие pointermove генерируется часто, поэтому требуется оптимизация:

  • ограничение обновлений через requestAnimationFrame
  • кеширование последнего feature
  • фильтрация одинаковых значений
let lastFeature = null;

map.on('pointermove', function (event) {
  const feature = map.forEachFeatureAtPixel(event.pixel, f => f);

  if (feature === lastFeature) return;

  lastFeature = feature;

  if (feature) {
    tooltipElement.innerHTML = feature.get('name');
    tooltipOverlay.setPosition(event.coordinate);
  } else {
    tooltipOverlay.setPosition(undefined);
  }
});

Компоновка интерфейса с несколькими overlay

Сложные интерфейсы используют несколько overlay одновременно:

  • tooltipOverlay — hover-информация
  • popupOverlay — детальная карточка
  • highlightOverlay — визуальная подсветка

Каждый overlay управляется независимо:

map.addOverlay(tooltipOverlay);
map.addOverlay(popupOverlay);
map.addOverlay(highlightOverlay);

Согласованность достигается через единый источник состояния выбранного объекта и текущего hover-элемента.