QueryRenderedFeatures

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


queryRenderedFeatures выполняет пространственно-логический запрос к уже отрисованной сцене. Это означает:

  • учитывается текущий zoom и bounds карты
  • учитываются стили слоёв (visibility, minzoom, maxzoom)
  • учитываются фильтры слоя (filter)
  • учитывается порядок слоёв (z-index через beforeId)
  • возвращаются только те features, которые реально попали в текущий кадр рендеринга

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


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

map.queryRenderedFeatures(point?, options?)

Аргументы

point (optional) Точка или прямоугольник области запроса:

  • {x, y} — пиксельная координата
  • BBox — массив [x1, y1, x2, y2]

Если не передан, анализируется весь viewport.

options (optional)

{
  layers?: string[],
  filter?: array,
  validate?: boolean
}

Ограничение по слоям (layers)

Параметр layers позволяет сузить поиск до конкретных слоёв карты:

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

Это критично для производительности при сложных стилях с десятками слоёв.


Фильтрация через filter expression

Фильтр работает в формате MapLibre Style Specification:

map.queryRenderedFeatures(event.point, {
  filter: ['==', ['get', 'type'], 'restaurant']
});

Примеры выражений:

  • сравнение значений: ['==', ['get', 'id'], 10]
  • логика: ['all', expr1, expr2]
  • диапазоны: ['>', ['get', 'population'], 100000]

Фильтр применяется уже после рендеринга, что делает его мощным инструментом интерактивной аналитики.


Базовый пример использования

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

  console.log(features);
});

Возвращаемый массив содержит объекты:

{
  type: 'Feature',
  geometry: {...},
  properties: {...},
  layer: {...}
}

Каждый feature уже «привязан» к конкретному слою карты.


Работа с точкой клика

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

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

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

Пиксельная координата e.point является ключевым входом, так как WebGL работает в экранных координатах, а не в географических.


Использование bounding box

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

const bbox = [
  startPoint.x,
  startPoint.y,
  endPoint.x,
  endPoint.y
];

const features = map.queryRenderedFeatures(bbox, {
  layers: ['buildings']
});

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

  • box selection
  • drag selection
  • анализ плотности объектов

Отличие от querySourceFeatures

queryRenderedFeatures:

  • работает с отрисованным кадром
  • учитывает стиль и видимость
  • быстрее для интерактивных сценариев
  • ограничен текущим viewport

querySourceFeatures:

  • работает с сырыми данными источника
  • не зависит от рендера
  • может возвращать скрытые объекты
  • требует знания source-layer (для vector tiles)

Пример различия:

map.queryRenderedFeatures(...); // только видимые объекты
map.querySourceFeatures('my-source'); // все доступные из source

Приоритет слоёв и порядок возврата

Features возвращаются в порядке визуального наложения слоёв. Это важно:

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

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

При активной карте с большим количеством слоёв queryRenderedFeatures может быть дорогой операцией.

Ключевые факторы оптимизации:

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

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

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

  • избегать вызова на mousemove без throttle/debounce
  • использовать idle или render события осторожно

Узкие области запроса

map.queryRenderedFeatures(bbox)

быстрее, чем полный viewport-запрос без параметров.


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

Частый паттерн — подсветка объекта под курсором:

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

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

Подсветка выбранного объекта

let selectedId = null;

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

  if (!features.length) return;

  selectedId = features[0].properties.id;

  map.setFilter('regions-highlight', [
    '==',
    ['get', 'id'],
    selectedId
  ]);
});

Работа с кластеризацией

При использовании кластеров (GeoJSON source with cluster: true) queryRenderedFeatures возвращает:

  • кластерные точки
  • или отдельные элементы, если zoom достаточно высокий

Пример:

map.queryRenderedFeatures(e.point, {
  layers: ['clusters']
});

Дальнейшая работа часто включает:

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

Особенности работы с символами и текстом

Слои типа symbol могут возвращать несколько features на одной пиксельной позиции:

  • иконка
  • текст
  • дублирующие label-слои

Это приводит к ситуации, когда queryRenderedFeatures возвращает несколько объектов для одного визуального элемента.


Фильтрация по геометрии

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

const point = map.project([lng, lat]);

const features = map.queryRenderedFeatures(point);

И обратное:

const lngLat = map.unproject(point);

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

Запрос без ограничения слоёв

Приводит к перегрузке и лишним данным:

map.queryRenderedFeatures(e.point);

Ожидание всех данных source

Метод не предназначен для полного обхода данных.

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

Передача [lng, lat] вместо {x, y} приводит к пустому результату.


Использование в аналитике интерфейса

queryRenderedFeatures часто используется как основа:

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

Комбинирование с setFeatureState

Типичный сценарий выделения:

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

if (features.length) {
  const id = features[0].id;

  map.setFeatureState(
    { source: 'places', id },
    { hover: true }
  );
}

Поведение при пересечении тайлов

MapLibre рендерит данные через vector tiles. При запросе:

  • один feature может быть представлен в нескольких тайлах
  • метод автоматически дедуплицирует результаты частично, но не всегда полностью
  • порядок может отличаться между кадрами при панорамировании

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

В стилях с большим количеством выражений (expression-based styling) результат queryRenderedFeatures уже содержит:

  • вычисленные свойства стиля
  • итоговую визуализацию слоя
  • интерполированные значения (если применимо)

Это позволяет использовать метод как инструмент «визуальной семантики», а не только доступа к данным.