Viewport querying

В Mapbox GL JS концепция viewport querying строится вокруг текущего состояния камеры карты: центра, масштаба, наклона и границ видимой области. Все операции выборки геоданных выполняются относительно того, что уже отрисовано в кадре или находится в пределах текущего отображаемого экстента.

Viewport querying делится на два основных направления:

  • запросы к отрисованным объектам (rendered features)
  • запросы к исходным данным источников (source features)

Разница между ними принципиальна: первый тип работает с тем, что реально попало в текущий рендеринг, второй — с геометрией в источнике, независимо от визуального состояния.


Отрисованные объекты: map.queryRenderedFeatures

Основной механизм viewport querying — метод queryRenderedFeatures. Он позволяет получать объекты, которые уже прошли стадию рендеринга и находятся в текущем кадре.

Базовая сигнатура

map.queryRenderedFeatures(point?, options?)

Метод поддерживает несколько режимов вызова:

  • без параметров — возвращает все объекты в viewport
  • с точкой — возвращает объекты под курсором
  • с bbox — возвращает объекты в области

Запрос объектов под курсором

Наиболее распространённый сценарий — выбор объектов по координатам пикселя:

map.on('click', (e) => {
  const features = map.queryRenderedFeatures(e.point);
  console.log(features);
});

e.point — экранные координаты события, которые автоматически преобразуются в пространство canvas.

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

  • учитываются только видимые слои
  • фильтры слоя (filter) влияют на результат
  • учитывается порядок слоёв (z-index)
  • возвращаются только объекты текущего tile render state

Ограничение выборки слоями

Viewport querying может быть ограничен конкретными слоями:

const features = map.queryRenderedFeatures(e.point, {
  layers: ['cities-layer', 'roads-layer']
});

Такой подход используется для:

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

При наличии большого количества слоёв фильтрация по layers критична для предотвращения избыточных вычислений.


Запрос по области (bounding box)

Viewport querying поддерживает прямоугольные области:

const bounds = [
  [left, top],
  [right, bottom]
];

const features = map.queryRenderedFeatures(bounds);

Применения:

  • выделение объектов рамкой (box selection)
  • инструменты анализа плотности данных
  • массовая выборка объектов в видимой зоне

Bounding box работает в пиксельных координатах текущего canvas, а не в географических координатах.


Отличие rendered features от source features

В Mapbox GL JS существует отдельный метод:

map.querySourceFeatures(sourceId, options)

Он возвращает данные напрямую из источника, минуя рендеринг.

Ключевые отличия:

Rendered features:

  • зависят от текущего zoom
  • зависят от стилей слоя
  • учитывают фильтры и видимость
  • ограничены текущими тайлами

Source features:

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

Геометрическая привязка viewport

Viewport querying тесно связан с системой координат карты:

  • screen coordinates (pixel space)
  • geographic coordinates (LngLat)
  • tile coordinates (z/x/y)

При выполнении queryRenderedFeatures происходит промежуточное преобразование:

  1. пиксель → нормализованное пространство canvas
  2. canvas → tile space
  3. tile space → feature geometry

Эта цепочка объясняет, почему результаты зависят от zoom уровня и угла наклона.


Учет наклона и вращения карты

При использовании pitch и bearing viewport перестаёт быть строго прямоугольным в географическом смысле.

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

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

Это критично для:

  • 3D зданий
  • extruded polygons
  • наклонённых тайлов

Работа с пересечениями (hit detection)

Viewport querying часто используется для определения попадания курсора в объект.

Пример:

map.on('mousemove', (e) => {
  const features = map.queryRenderedFeatures(e.point);

  map.getCanvas().style.cursor =
    features.length ? 'pointer' : '';
});

Алгоритм hit detection включает:

  • проверку видимости слоя
  • проверку z-order
  • учет прозрачности и layout visibility
  • геометрическое пересечение с пикселем

Приоритет слоёв и конфликт выборки

При пересечении нескольких объектов результат queryRenderedFeatures не гарантирует единственный элемент.

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

  • полигон перекрывает линию
  • несколько слоёв с одинаковой геометрией
  • символы (symbol layers) накладываются друг на друга

Решения:

  • сортировка результатов по layer и source
  • использование filter для уточнения
  • ограничение layers в запросе
  • анализ feature.properties

Производительность viewport querying

Viewport querying напрямую зависит от:

  • количества слоёв
  • сложности геометрии
  • наличия кластеризации
  • текущего zoom level
  • размера viewport

Оптимизационные техники:

1. Ограничение слоёв

map.queryRenderedFeatures(point, {
  layers: ['interactive-layer']
});

2. Использование debounce для mousemove

let timeout;

map.on('mousemove', (e) => {
  clearTimeout(timeout);
  timeout = setTimeout(() => {
    map.queryRenderedFeatures(e.point);
  }, 50);
});

3. Кластеризация источников

Для point-данных кластеризация существенно снижает количество объектов в viewport.


Взаимодействие с фильтрами стилей

Viewport querying учитывает выражения фильтрации слоя:

'filter': ['==', ['get', 'type'], 'park']

Это означает:

  • объекты, исключённые фильтром, не возвращаются
  • изменение фильтра мгновенно влияет на queryRenderedFeatures
  • нет необходимости дополнительной постобработки

Использование с event API

Viewport querying тесно связан с событийной моделью Mapbox GL JS.

Основные события:

  • click
  • mousemove
  • mouseenter / mouseleave
  • touchstart

Пример комбинированного сценария:

map.on('click', (e) => {
  const features = map.queryRenderedFeatures(e.point, {
    layers: ['buildings']
  });

  if (features.length) {
    const feature = features[0];
    console.log(feature.properties);
  }
});

Особенности работы с символами (symbol layers)

Symbol layers имеют специфическое поведение:

  • текст и иконка считаются отдельными элементами hit detection
  • возможны множественные совпадения на один пиксель
  • порядок размещения влияет на результат

Viewport querying в symbol layers часто требует дополнительной логики:

  • фильтрация по symbol-sort-key
  • приоритет text-field над icon
  • проверка geometry.type

Влияние масштабирования (zoom)

На разных zoom уровнях viewport querying возвращает разные наборы данных:

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

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


Ограничения viewport querying

Несмотря на гибкость, существуют ограничения:

  • отсутствие доступа к объектам вне viewport через rendered features
  • зависимость от текущего style state
  • невозможность прямого spatial index запроса (для этого требуется source querying)
  • ограничение на количество возвращаемых объектов в сложных сценах

Практическая модель комбинированного запроса

В реальных системах viewport querying часто комбинируется с source querying:

const rendered = map.queryRenderedFeatures(point, {
  layers: ['places']
});

const source = map.querySourceFeatures('places-source');

Такой подход позволяет:

  • использовать rendered features для UI взаимодействия
  • использовать source features для аналитики и агрегации
  • синхронизировать визуальный и аналитический слой данных

Влияние WebGL рендеринга

Mapbox GL JS использует WebGL, что означает:

  • viewport querying работает поверх GPU-рендеринга
  • часть вычислений происходит в шейдерах
  • пиксельная точность зависит от devicePixelRatio
  • возможны минимальные расхождения между визуальным пикселем и геометрией

Типовые архитектуры использования viewport querying

Интерактивные карты

  • hover подсветка
  • click selection
  • tooltips

Аналитические панели

  • сбор объектов в viewport
  • агрегация по области
  • динамические фильтры

GIS-инструменты

  • выделение полигонов
  • сравнение слоёв
  • spatial inspection

Связь с состоянием камеры

Viewport querying всегда зависит от состояния камеры:

  • center
  • zoom
  • bearing
  • pitch

Любое изменение камеры мгновенно изменяет набор доступных результатов, поскольку пересчитывается:

  • видимая область
  • пересечение тайлов
  • порядок отрисовки слоёв

Поведение при анимации карты

Во время flyTo или easeTo:

  • результаты queryRenderedFeatures могут изменяться между кадрами
  • стабильность выборки достигается только после завершения анимации
  • быстрые последовательные запросы могут давать разные результаты

Обработка пустых результатов

Viewport querying может возвращать пустой массив в случаях:

  • слой скрыт (visibility: none)
  • объект вне viewport
  • фильтр исключает все объекты
  • тайл ещё не загружен

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