Агрегация значений по областям

Агрегация пространственных данных в Turf.js строится вокруг задачи сопоставления точечных объектов с полигональными зонами и последующего вычисления статистик по группам. Базовый принцип заключается в выполнении пространственного соединения (spatial join), после которого данные агрегируются средствами JavaScript или специализированными утилитами Turf.


Проверка принадлежности точек полигону

Ключевая операция пространственной агрегации — определение, попадает ли точка внутрь полигона. Для этого используется booleanPointInPolygon.

Функция принимает точку и полигон и возвращает логическое значение:

import booleanPointInPolygon from "@turf/boolean-point-in-polygon";
import { point, polygon } from "@turf/helpers";

const pt = point([30.5, 50.5]);
const poly = polygon([[
  [30, 50],
  [31, 50],
  [31, 51],
  [30, 51],
  [30, 50]
]]);

booleanPointInPolygon(pt, poly);

На этом уровне формируется основа для последующей группировки данных: каждая точка либо относится к полигону, либо исключается из набора.


Пространственное объединение точек и полигонов

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

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

function spatialJoin(points, polygons) {
  const result = polygons.features.map(poly => ({
    ...poly,
    properties: {
      ...poly.properties,
      joined: []
    }
  }));

  points.features.forEach(pt => {
    result.forEach(poly => {
      if (booleanPointInPolygon(pt, poly)) {
        poly.properties.joined.push(pt.properties.value);
      }
    });
  });

  return result;
}

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


Использование turf.collect для извлечения значений

В Turf.js предусмотрена утилита collect, предназначенная для извлечения значений свойств точек и их переноса в полигоны.

import collect from "@turf/collect";
import { featureCollection, polygon, point } from "@turf/helpers";

const polys = featureCollection([
  polygon([[
    [0, 0],
    [10, 0],
    [10, 10],
    [0, 10],
    [0, 0]
  ]], { id: 1 })
]);

const pts = featureCollection([
  point([1, 1], { population: 10 }),
  point([2, 2], { population: 20 }),
  point([20, 20], { population: 30 })
]);

collect(polys, pts, "population", "values");

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


Агрегация числовых значений внутри полигонов

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

function aggregate(polygons, propertyName) {
  polygons.features.forEach(poly => {
    const values = poly.properties[propertyName] || [];

    const sum = values.reduce((acc, v) => acc + v, 0);
    const count = values.length;
    const mean = count > 0 ? sum / count : 0;
    const min = Math.min(...values);
    const max = Math.max(...values);

    poly.properties.sum = sum;
    poly.properties.count = count;
    poly.properties.mean = mean;
    poly.properties.min = min;
    poly.properties.max = max;
  });

  return polygons;
}

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


Суммирование и взвешенные показатели

При наличии нескольких атрибутов возможно вычисление взвешенных метрик. Например, суммарная численность населения с учетом коэффициентов:

function weightedSum(values, weights) {
  return values.reduce((acc, v, i) => acc + v * (weights[i] || 1), 0);
}

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


Группировка по идентификаторам зон

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

function groupByPolygon(points, polygons) {
  const groups = new Map();

  polygons.features.forEach(p => {
    groups.set(p.properties.id, []);
  });

  points.features.forEach(pt => {
    polygons.features.forEach(poly => {
      if (booleanPointInPolygon(pt, poly)) {
        groups.get(poly.properties.id).push(pt);
      }
    });
  });

  return groups;
}

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


Пространственная агрегация через индексирование

При большом количестве объектов линейная проверка становится неэффективной. Для оптимизации используется пространственное индексирование: предварительная фильтрация по bounding box.

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

function indexedJoin(points, polygons) {
  const indexed = polygons.features.map(poly => ({
    poly,
    bbox: bbox(poly),
    values: []
  }));

  points.features.forEach(pt => {
    const [x, y] = pt.geometry.coordinates;

    indexed.forEach(item => {
      const [minX, minY, maxX, maxY] = item.bbox;

      if (x >= minX && x <= maxX && y >= minY && y <= maxY) {
        if (booleanPointInPolygon(pt, item.poly)) {
          item.values.push(pt.properties.value);
        }
      }
    });
  });

  return indexed;
}

Фильтрация по bounding box значительно сокращает количество геометрических проверок.


Агрегация по сетке (grid-based aggregation)

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

import squareGrid from "@turf/square-grid";
import booleanPointInPolygon from "@turf/boolean-point-in-polygon";
import bbox from "@turf/bbox";

const grid = squareGrid(bbox(points), 1);

grid.features.forEach(cell => {
  cell.properties.sum = 0;
  cell.properties.count = 0;

  points.features.forEach(pt => {
    if (booleanPointInPolygon(pt, cell)) {
      cell.properties.sum += pt.properties.value;
      cell.properties.count += 1;
    }
  });
});

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


Объединение нескольких атрибутов в одной агрегации

При наличии комплексных данных агрегация может охватывать сразу несколько показателей:

function multiAggregate(polygons, points) {
  polygons.features.forEach(poly => {
    const values = {
      income: [],
      population: []
    };

    points.features.forEach(pt => {
      if (booleanPointInPolygon(pt, poly)) {
        values.income.push(pt.properties.income);
        values.population.push(pt.properties.population);
      }
    });

    poly.properties.income_avg =
      values.income.reduce((a, b) => a + b, 0) / (values.income.length || 1);

    poly.properties.population_sum =
      values.population.reduce((a, b) => a + b, 0);
  });

  return polygons;
}

Работа с пропущенными значениями

При агрегации часто встречаются отсутствующие или некорректные значения. Их обработка выполняется на этапе подготовки данных:

const clean = values.filter(v => typeof v === "number" && !isNaN(v));

Игнорирование невалидных значений предотвращает искажение статистики.


Иерархическая агрегация

При наличии вложенных административных уровней (район → город → область) агрегация выполняется поэтапно: сначала нижний уровень, затем перенос результатов на верхний.

function hierarchical(polygonsLevel1, polygonsLevel2) {
  polygonsLevel2.features.forEach(parent => {
    const children = polygonsLevel1.features.filter(child =>
      booleanPointInPolygon(child.properties.centroid, parent)
    );

    parent.properties.total = children.reduce(
      (acc, c) => acc + (c.properties.sum || 0),
      0
    );
  });

  return polygonsLevel2;
}

Контроль точности при агрегации

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

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