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

В экосистеме Mapbox GL JS под «результатами» понимаются данные, возвращаемые различными источниками: запросами к слоям карты, событиями взаимодействия, геокодингом, источниками GeoJSON, векторными тайлами и вычисляемыми выборками объектов. Эти результаты имеют разную структуру, но объединяются общим принципом: представляют набор геопространственных сущностей с набором свойств и геометрией.

Основные категории:

  • результаты запросов к рендеру карты (rendered features)
  • результаты запросов к источникам данных (source features)
  • результаты событий пользовательского взаимодействия
  • результаты внешних API (геокодинг, тайлы, кластеризация)
  • производные результаты (фильтрация, агрегация, трансформация)

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


Результаты queryRenderedFeatures

Метод map.queryRenderedFeatures() возвращает объекты, которые в данный момент отрисованы на экране.

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

Характерные особенности:

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

Типичная структура результата:

{
  "type": "Feature",
  "geometry": { "type": "Point", "coordinates": [30, 50] },
  "properties": {
    "name": "Object A"
  },
  "layer": {
    "id": "points-layer"
  }
}

Ключевой момент — наличие поля layer, которое связывает результат с конкретным слоем стиля.


Результаты querySourceFeatures

Метод map.querySourceFeatures() работает на уровне источника данных, минуя визуальные ограничения.

const features = map.querySourceFeatures('cities-source', {
  sourceLayer: 'cities'
});

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

  • возвращает все доступные фичи в источнике
  • не зависит от текущего viewport
  • требует корректного указания sourceLayer для vector tiles
  • может быть значительно более объёмным по данным

Используется для:

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

Результаты событий взаимодействия

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

map.on('click', 'points-layer', (e) => {
  const feature = e.features[0];
  const coordinates = e.lngLat;
});

Структура события:

  • e.features — выбранные объекты слоя
  • e.lngLat — географические координаты клика
  • e.point — экранные координаты
  • e.originalEvent — нативное DOM-событие

Особенность подхода заключается в том, что результат уже предфильтрован по слою, что уменьшает необходимость ручного поиска.


Результаты Mapbox Geocoding API

Геокодинг возвращает структурированные JSON-ответы, содержащие географические сущности.

Пример результата:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "place_name": "Almaty, Kazakhstan",
      "geometry": {
        "type": "Point",
        "coordinates": [76.9286, 43.2220]
      },
      "properties": {
        "accuracy": "city"
      }
    }
  ]
}

Особенности обработки:

  • всегда возвращается FeatureCollection
  • результаты ранжированы по релевантности
  • присутствуют метаданные качества (relevance, accuracy)
  • возможно пагинирование через limit

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

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

Работа с результатами GeoJSON источников

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

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

Далее результаты доступны через:

map.querySourceFeatures('places');

или:

map.queryRenderedFeatures();

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

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

Результаты кластеризации

Кластеризация в источниках GeoJSON формирует специальные объекты:

map.addSource('points', {
  type: 'geojson',
  data: data,
  cluster: true,
  clusterMaxZoom: 14,
  clusterRadius: 50
});

Результаты содержат:

  • кластерные объекты (cluster: true)
  • количество точек (point_count)
  • идентификатор кластера (cluster_id)

Пример обработки:

map.on('click', 'clusters', (e) => {
  const clusterId = e.features[0].properties.cluster_id;
});

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

map.getSource('points').getClusterExpansionZoom(clusterId, (err, zoom) => {
  map.easeTo({ zoom });
});

Кластерные результаты требуют отдельной логики декомпозиции.


Фильтрация и преобразование результатов

Результаты часто проходят постобработку:

const filtered = features.filter(f => f.properties.type === 'restaurant');

Распространённые операции:

  • фильтрация по свойствам
  • сортировка по расстоянию
  • группировка по категориям
  • вычисление агрегатов

Пример сортировки по расстоянию:

features.sort((a, b) => {
  return a.properties.distance - b.properties.distance;
});

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


Форматирование свойств результатов

Свойства объектов (properties) часто требуют нормализации перед отображением.

Типичные преобразования:

  • приведение строк к читаемому виду
  • обработка отсутствующих значений
  • форматирование чисел
  • локализация данных
function formatFeature(feature) {
  return {
    name: feature.properties.name || 'Без названия',
    category: feature.properties.category?.toUpperCase(),
    rating: Number(feature.properties.rating || 0).toFixed(1)
  };
}

В сложных приложениях формируется слой абстракции между сырыми результатами и UI-слоем.


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

При интенсивной работе с Mapbox GL JS критично учитывать стоимость операций:

  • queryRenderedFeatures вызывается только при необходимости
  • минимизация количества фильтров на клиенте
  • кэширование результатов запросов
  • ограничение области поиска через bounding box

Пример ограничения:

map.queryRenderedFeatures([
  [left, bottom],
  [right, top]
]);

Также используется дебаунсинг событий:

let timeout;
map.on('moveend', () => {
  clearTimeout(timeout);
  timeout = setTimeout(updateResults, 200);
});

Ошибки и крайние случаи в результатах

Типичные проблемы:

Пустые результаты

Возникают при отсутствии объектов в зоне запроса:

if (!features.length) {
  return;
}

Несовпадение слоёв

Запрос может вернуть пустой массив при неправильном layer id.

Неполные свойства

Vector tiles могут не содержать всех ожидаемых полей.

Дублирование объектов

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

Несогласованность между source и rendered

querySourceFeatures и queryRenderedFeatures могут давать разные наборы данных при одинаковых координатах.


Работа с результатами в многоуровневой архитектуре

В сложных приложениях результаты проходят несколько этапов:

  1. получение сырых данных (source / API)
  2. фильтрация на уровне источника
  3. выборка отображаемых объектов
  4. постобработка и нормализация
  5. привязка к UI (popup, sidebar, overlay)

Такая структура позволяет отделить геопространственную логику от визуального слоя и уменьшить связанность компонентов внутри приложений, использующих Mapbox GL JS