Работа с пространственными данными в 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. 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.
После группировки часто требуется вернуть данные в формат 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-структур и аналитических панелей.
При обработке тысяч и миллионов объектов критичным становится снижение количества проходов по массиву. Оптимизация достигается за счёт:
Неэффективный подход:
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;
}, {});
Такой подход уменьшает объём данных до выполнения группировки, повышая эффективность вычислений в геопространственных задачах.