QueryRenderedFeatures

queryRenderedFeatures — ключевой метод в Mapbox GL JS, предназначенный для извлечения объектов, уже отрисованных на карте в текущем viewport. Он работает не с исходными данными источников, а с тем, что фактически попало в сцену рендеринга WebGL, что делает его основным инструментом для интерактивных сценариев: клики по карте, подсветка объектов, выбор геометрии, анализ видимой области и построение пользовательских взаимодействий поверх слоёв.

Метод map.queryRenderedFeatures() возвращает массив GeoJSON-подобных объектов, которые соответствуют визуально отображённым элементам карты в момент вызова. Это означает:

  • учитываются стиль, фильтры слоёв и zoom-уровень
  • учитывается видимая область (viewport)
  • учитываются только те данные, которые уже попали в рендер

Ключевое отличие от querySourceFeatures заключается в том, что последний работает с исходными данными источника, а queryRenderedFeatures — с результатом рендеринга.

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

Сигнатура метода

map.queryRenderedFeatures(geometry?, options?)

Параметры

geometry (optional) Ограничивает область поиска:

  • точка { x, y }
  • прямоугольник [[x1, y1], [x2, y2]]

Если не указан, поиск выполняется по всему viewport.

options (object)

Основные поля:

  • layers — массив ID слоёв, в которых искать
  • filter — выражение фильтрации по свойствам feature

Пример:

map.queryRenderedFeatures(point, {
  layers: ['cities-layer'],
  filter: ['==', ['get', 'type'], 'capital']
});

Геометрия запроса

Точечный запрос

Используется при клике или наведении:

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

e.point содержит координаты пикселя относительно canvas.

Область (bounding box)

Позволяет выделять объекты в прямоугольной зоне:

const features = map.queryRenderedFeatures([
  [100, 100],
  [300, 300]
]);

Этот режим часто используется для drag-selection интерфейсов.

Ограничение слоями

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

map.queryRenderedFeatures(point, {
  layers: ['roads', 'buildings']
});

Если слои не указаны:

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

Фильтрация по свойствам

Фильтры используют expression-синтаксис Mapbox GL JS:

map.queryRenderedFeatures(point, {
  filter: ['>=', ['get', 'population'], 1000000]
});

Поддерживаются:

  • логические операторы (==, !=, >, <, all, any)
  • доступ к свойствам через ['get', 'property']
  • математические выражения

Фильтрация выполняется после рендеринга, но до возврата результата API.

Возвращаемая структура данных

Каждый feature имеет структуру GeoJSON:

{
  type: "Feature",
  geometry: { ... },
  properties: { ... },
  layer: {
    id: "layer-name",
    type: "fill"
  },
  source: "source-id",
  sourceLayer: "source-layer-name"
}

Особое внимание:

  • layer — информация о визуальном слое, где объект отрисован
  • sourceLayer — актуально для vector tiles
  • properties — свойства из исходных данных

Поведение при наложении слоёв

При пересечении нескольких слоёв результат может содержать несколько features из одной точки.

Порядок зависит от:

  • порядка слоёв в стиле
  • z-index (layout order)
  • прозрачности и visibility

Для контроля можно ограничить список layers:

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

Использование в событиях

Наиболее частый сценарий — обработка кликов:

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

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

Также применяется в:

  • mousemove (hover эффекты)
  • touchstart (мобильные интерфейсы)
  • drag selection

Hover-интерактивность

Для подсветки объектов при наведении:

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

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

Расширенный вариант — динамическое изменение стиля:

map.setFilter('highlight-layer', [
  'in',
  'id',
  ...features.map(f => f.id)
]);

Производительность и оптимизация

queryRenderedFeatures может быть дорогим при частых вызовах.

Рекомендации:

1. Ограничение layers

layers: ['relevant-layer']

2. Минимизация частоты вызова

  • избегать вызова в scroll/drag без throttle
  • использовать requestAnimationFrame

3. Уменьшение области поиска

  • передавать точку вместо всего viewport

4. Избегать сложных filter expressions

Отличие от querySourceFeatures

Метод Работает с Учитывает стиль Видимость
queryRenderedFeatures отрисованные объекты да только видимые
querySourceFeatures исходные данные нет все

queryRenderedFeatures используется для UI-интеракций, querySourceFeatures — для анализа данных.

Работа с символами и текстом

Для слоёв типа symbol запрос может возвращать:

  • точки POI
  • текстовые метки
  • иконки

Однако важно учитывать, что:

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

Z-index и порядок попадания в результат

Если несколько features пересекаются:

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

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

1. Отсутствие layers Приводит к шумным результатам.

2. Использование вне события Без контекста координат результат бессмысленен.

3. Ожидание source данных Метод возвращает только рендер, а не полный dataset.

4. Игнорирование zoom На разных zoom уровни видимость объектов различается.

Сценарии применения

Выделение объектов на карте

const selected = map.queryRenderedFeatures(e.point, {
  layers: ['zones']
});

Построение информационных панелей

map.on('click', (e) => {
  const f = map.queryRenderedFeatures(e.point)[0];
  showPopup(f.properties);
});

Геоаналитика в видимой области

const features = map.queryRenderedFeatures();
const total = features.length;

Drag selection

map.queryRenderedFeatures(bbox, {
  layers: ['parcels']
});

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

Во время:

  • flyTo
  • easeTo
  • rotate

результаты могут меняться между кадрами, так как рендер обновляется непрерывно.

Особенности WebGL-рендера

Поскольку Mapbox GL JS использует WebGL:

  • queryRenderedFeatures работает на GPU-результате
  • выборка происходит из уже отрисованного буфера
  • нет прямого доступа к «сырым» геометриям

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

Обработка кластеров

При работе с cluster layers результат может содержать:

  • cluster feature
  • expanded points (при zoom)
map.queryRenderedFeatures(point, {
  layers: ['clusters']
});

Дальнейшая логика часто требует:

map.getSource('points').getClusterExpansionZoom(clusterId);

Практика построения интерактивных слоёв

Типичный паттерн:

  1. пользователь взаимодействует с картой
  2. вызывается queryRenderedFeatures
  3. выбирается feature
  4. обновляется UI или стиль слоя
map.on('click', (e) => {
  const f = map.queryRenderedFeatures(e.point, {
    layers: ['regions']
  })[0];

  if (!f) return;

  map.setFeatureState(
    { source: 'regions', id: f.id },
    { selected: true }
  );
});

Ограничения метода

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

Эти ограничения определяют его позицию как UI-метода, а не аналитического API.