Группировка по атрибутам

Работа с пространственными данными в Turf.js строится вокруг стандарта GeoJSON, где каждая сущность представлена как Feature с набором геометрии и свойств. Атрибутная группировка в таком контексте представляет собой преобразование массива геообъектов в структуру, где элементы объединяются по значениям одного или нескольких полей properties.

Каждый объект Feature содержит поле properties, в котором хранятся пользовательские атрибуты:

const point = {
  type: "Feature",
  geometry: {
    type: "Point",
    coordinates: [76.928, 43.238]
  },
  properties: {
    category: "cafe",
    rating: 4.5,
    city: "Karaganda"
  }
};

Именно содержимое properties используется как основа для группировки. Геометрия при этом не участвует в логике классификации, если не применяется пространственное условие.

Базовая группировка через reduce

Наиболее универсальный способ группировки — использование стандартного массива и метода reduce. Turf.js в данном случае выступает как поставщик геоданных и функций трансформации, но сама группировка выполняется средствами JavaScript.

const features = featureCollection.features;

const grouped = features.reduce((acc, feature) => {
  const key = feature.properties.category;

  if (!acc[key]) {
    acc[key] = [];
  }

  acc[key].push(feature);

  return acc;
}, {});

Результатом становится объект, где ключами выступают значения атрибута category, а значениями — массивы объектов Feature.

Преобразование групп в FeatureCollection

После группировки часто требуется вернуть данные в формат GeoJSON:

import { featureCollection } from "@turf/helpers";

const groupedCollections = Object.fromEntries(
  Object.entries(grouped).map(([key, value]) => [
    key,
    featureCollection(value)
  ])
);

Каждая группа превращается в отдельную FeatureCollection, что удобно для дальнейшей визуализации или анализа.

Группировка по числовым диапазонам

Атрибуты часто имеют непрерывный характер (например, рейтинг, высота, население). В таких случаях применяется биннинг — разбиение на интервалы.

const groupedByRating = features.reduce((acc, feature) => {
  const rating = feature.properties.rating;

  const bin = Math.floor(rating); // 0–1, 1–2, 2–3 и т.д.

  if (!acc[bin]) {
    acc[bin] = [];
  }

  acc[bin].push(feature);

  return acc;
}, {});

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

Комбинированные ключи группировки

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

const grouped = features.reduce((acc, feature) => {
  const { city, category } = feature.properties;
  const key = `${city}:${category}`;

  if (!acc[key]) {
    acc[key] = [];
  }

  acc[key].push(feature);

  return acc;
}, {});

Такой метод формирует многомерную группировку без необходимости вложенных структур.

Пространственно-атрибутная группировка

Turf.js предоставляет инструменты для пространственного анализа, которые часто комбинируются с атрибутной логикой. Например, группировка точек по принадлежности полигону.

import booleanPointInPolygon from "@turf/boolean-point-in-polygon";

const groupedByZone = zones.features.reduce((acc, polygon) => {
  acc[polygon.properties.zone] = [];

  features.forEach(point => {
    if (booleanPointInPolygon(point, polygon)) {
      acc[polygon.properties.zone].push(point);
    }
  });

  return acc;
}, {});

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

Использование кластеризации как альтернативы группировке

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

import clustersDbscan from "@turf/clusters-dbscan";

const clustered = clustersDbscan(featureCollection, 50, {
  units: "kilometers"
});

Каждой точке присваивается свойство cluster, после чего группировка выполняется по этому полю:

const clusters = clustered.features.reduce((acc, feature) => {
  const key = feature.properties.cluster;

  if (!acc[key]) {
    acc[key] = [];
  }

  acc[key].push(feature);

  return acc;
}, {});

Стабилизация и нормализация ключей

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

const normalize = (value) =>
  String(value).trim().toLowerCase();

const grouped = features.reduce((acc, feature) => {
  const key = normalize(feature.properties.category);

  if (!acc[key]) {
    acc[key] = [];
  }

  acc[key].push(feature);

  return acc;
}, {});

Нормализация обеспечивает консистентность групп.

Группировка по нескольким полям с иерархией

Иерархическая структура применяется при необходимости вложенных категорий:

const grouped = features.reduce((acc, feature) => {
  const city = feature.properties.city;
  const category = feature.properties.category;

  if (!acc[city]) {
    acc[city] = {};
  }

  if (!acc[city][category]) {
    acc[city][category] = [];
  }

  acc[city][category].push(feature);

  return acc;
}, {});

Результат представляет собой дерево, удобное для построения UI-структур и аналитических панелей.

Производительность при больших наборах данных

При обработке тысяч и миллионов объектов критичным становится снижение количества проходов по массиву. Оптимизация достигается за счёт:

  • Однопроходного reduce
  • Минимизации вызовов функций внутри цикла
  • Предварительной нормализации данных
  • Избежания вложенных итераций внутри reduce

Неэффективный подход:

features.forEach(f => {
  features.forEach(other => {});
});

Оптимальный подход:

const grouped = new Map();

for (let i = 0; i < features.length; i++) {
  const key = features[i].properties.category;

  if (!grouped.has(key)) {
    grouped.set(key, []);
  }

  grouped.get(key).push(features[i]);
}

Типичные ошибки при группировке атрибутов

Использование свойств без проверки приводит к некорректным ключам:

const key = feature.properties.type; // может быть undefined

Следствием становится группа “undefined”, которая засоряет структуру.

Корректная обработка:

const key = feature.properties.type || "unknown";

Ещё одна проблема — мутация исходных объектов при формировании групп. GeoJSON-объекты должны оставаться неизменяемыми, так как используются в пространственных вычислениях Turf.js.

Смешивание пространственных и атрибутных операций

Комбинированные сценарии часто включают предварительную фильтрацию по геометрии:

import bbox from "@turf/bbox";
import booleanIntersects from "@turf/boolean-intersects";

const filtered = features.filter(f =>
  booleanIntersects(f, searchPolygon)
);

const grouped = filtered.reduce((acc, f) => {
  const key = f.properties.category;

  if (!acc[key]) acc[key] = [];
  acc[key].push(f);

  return acc;
}, {});

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