Работа с результатами запросов

В архитектуре MapLibre GL JS работа с результатами запросов основана на взаимодействии с отрисованными в данный момент векторными тайлами, источниками данных и серверными API. Запросы могут возвращать как уже загруженные на клиент данные, так и результаты внешних сервисов, интегрированных в приложение. Основные механизмы включают выбор объектов на карте, фильтрацию геометрий, обращение к источникам данных и обработку асинхронных ответов.


Запрос объектов на отрисованной карте

Одним из ключевых инструментов является метод получения объектов, попадающих в заданную область экрана. Это позволяет работать с интерактивностью без обращения к серверу.

Основной метод:

map.queryRenderedFeatures(bbox, options);

Принципы работы

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

Пример выборки по области

const features = map.queryRenderedFeatures(
  [
    [xMin, yMin],
    [xMax, yMax]
  ],
  {
    layers: ['cities-layer']
  }
);

Особенности результата

Каждый элемент результата содержит:

  • geometry — геометрия объекта;
  • properties — атрибуты из исходного источника;
  • layer — слой, на котором объект отрисован;
  • source — идентификатор источника данных;
  • state — динамическое состояние фичи (если используется feature-state).

Запрос объектов из источников данных

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

map.querySourceFeatures('earthquakes', {
  sourceLayer: 'points',
  filter: ['>', ['get', 'mag'], 5]
});

Отличия от визуального запроса

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

Типичные сценарии применения

  • аналитическая обработка загруженных тайлов;
  • предварительная фильтрация данных;
  • построение собственных кластеризаций;
  • вычисление статистики по источнику.

Фильтрация результатов через выражения

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

const features = map.queryRenderedFeatures({
  layers: ['restaurants'],
  filter: ['==', ['get', 'type'], 'fastfood']
});

Фильтры поддерживают:

  • логические операции (all, any, none);
  • сравнения (==, !=, >, <, >=, <=);
  • проверку наличия свойств;
  • работу с массивами и строками.

Обработка результатов клика по карте

Частый сценарий работы с результатами запросов связан с обработкой событий интерфейса. При клике на карту выполняется запрос к отрисованным объектам в точке события.

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

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

Важные аспекты

  • координаты события передаются в пикселях;
  • порядок слоёв влияет на приоритет результата;
  • возможны перекрытия нескольких объектов;
  • часто используется сортировка по layer или дополнительным атрибутам.

Работа с несколькими слоями

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

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

Поведение системы

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

Использование буферных зон при запросах

Для повышения точности интерактивности часто используется расширение области запроса вокруг точки события.

const buffer = 5;

const features = map.queryRenderedFeatures([
  [e.point.x - buffer, e.point.y - buffer],
  [e.point.x + buffer, e.point.y + buffer]
]);

Практическое значение

  • компенсируется неточность кликов;
  • учитываются тонкие линии и мелкие объекты;
  • повышается удобство интерфейса на мобильных устройствах.

Работа с кластеризованными данными

При использовании кластеризации результаты запросов требуют дополнительной обработки, так как возвращаемые объекты могут представлять собой агрегаты.

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

Структура кластерного объекта

  • cluster: true — признак кластера;
  • point_count — количество объектов внутри;
  • cluster_id — идентификатор для раскрытия;
  • агрегированные свойства.

Раскрытие кластера

map.getSource('points').getClusterExpansionZoom(clusterId, (err, zoom) => {
  map.easeTo({
    center: cluster.geometry.coordinates,
    zoom
  });
});

Асинхронная обработка внешних запросов

Помимо локальных данных, результаты запросов часто приходят из внешних API, например геокодеров или сервисов поиска.

fetch('https://api.example.com/geocode?q=Berlin')
  .then(response => response.json())
  .then(data => {
    console.log(data.features);
  });

Интеграция с картой

  • результаты конвертируются в GeoJSON;
  • добавляются в источники через setData;
  • обновляют визуализацию слоёв.
map.getSource('search-results').setData(geojson);

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

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

Основные этапы:

  • загрузка тайлов;
  • обновление кэша источника;
  • перерисовка слоёв;
  • повторный запрос результатов.

Изменения данных не всегда мгновенно отражаются в queryRenderedFeatures, так как обновление зависит от рендера WebGL-контекста.


Приоритизация результатов

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

Факторы приоритета:

  • порядок слоёв в стиле;
  • z-index (визуальный порядок);
  • прозрачность слоёв;
  • тип геометрии (точки перекрывают линии, линии перекрывают полигоны или наоборот в зависимости от стиля).

Фильтрация по состоянию фичи (feature-state)

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

map.setFeatureState(
  {
    source: 'cities',
    id: 123
  },
  {
    selected: true
  }
);

Запрос:

map.queryRenderedFeatures({
  layers: ['cities'],
  filter: ['==', ['feature-state', 'selected'], true]
});

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

Запросы к результатам напрямую зависят от объёма данных и сложности сцены.

Факторы влияния:

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

Оптимизация достигается через:

  • ограничение списка слоёв;
  • использование bounding box;
  • кэширование результатов;
  • минимизацию повторных запросов при движении карты.

Объединение результатов нескольких типов запросов

В реальных сценариях часто комбинируются разные источники результатов:

  • queryRenderedFeatures для интерактивности;
  • querySourceFeatures для аналитики;
  • внешние API для поиска;
  • локальные вычисления на основе геометрии.

Объединённые данные приводятся к единому формату GeoJSON FeatureCollection:

const combined = {
  type: 'FeatureCollection',
  features: [
    ...renderedFeatures,
    ...sourceFeatures,
    ...apiFeatures
  ]
};

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

Пустые выборки являются нормальным состоянием при работе с динамическими данными.

Причины:

  • отсутствие объектов в области запроса;
  • фильтры исключили все результаты;
  • данные ещё не загружены;
  • слой не активен или скрыт.

Корректная обработка включает проверку длины массива и учёт асинхронных состояний источников.

if (!features.length) {
  return;
}

Преобразование результатов для визуализации

Полученные данные часто требуют преобразования перед повторным отображением.

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

  • агрегация свойств;
  • преобразование координат;
  • нормализация атрибутов;
  • генерация новых GeoJSON-структур.
const geojson = {
  type: 'FeatureCollection',
  features: features.map(f => ({
    type: 'Feature',
    geometry: f.geometry,
    properties: {
      name: f.properties.name,
      category: f.properties.type
    }
  }))
};

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

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

map.addSource('dynamic', {
  type: 'geojson',
  data: geojson
});

map.addLayer({
  id: 'dynamic-layer',
  type: 'circle',
  source: 'dynamic'
});

Такой подход позволяет строить интерактивные интерфейсы, основанные на выборе и фильтрации данных в реальном времени.