Разбиение полигонов

Разбиение полигонов в Turf.js — это набор геопространственных операций, направленных на получение нескольких независимых геометрий из исходного полигона. В отличие от простых булевых операций, таких как пересечение или объединение, задача разбиения требует построения новых границ, корректной обработки самопересечений и поддержания валидности GeoJSON-структуры.

В Turf.js нет единственной универсальной функции «splitPolygon», поэтому разбиение реализуется через комбинацию булевых операций, работы с линиями, сетками и вспомогательной геометрией. Основные подходы зависят от характера разреза: по линии, по сетке, по маске или по произвольной кривой.

Полигон в Turf.js представлен как GeoJSON-объект типа Polygon или MultiPolygon. Любое разбиение сводится к преобразованию исходной области в множество новых областей:

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

Ключевым ограничением является то, что Turf.js оперирует двумерной моделью на сфере (WGS84), поэтому все операции геометрически приближённые.

Разбиение полигона по линии

Наиболее распространённый сценарий — разрез полигона линией. Логика строится на пересечении полигона с «разделяющей» геометрией и последующем формировании двух и более частей.

В Turf.js для этого используются комбинации:

  • @turf/intersect — вычисление пересечения;
  • @turf/difference — вычитание одной геометрии из другой;
  • @turf/line-intersect — поиск точек пересечения;
  • вспомогательная буферизация (@turf/buffer) для стабилизации операций.

Базовый подход через difference

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

import difference from "@turf/difference";
import polygon from "@turf/helpers";

const poly = polygon([[
  [0, 0], [10, 0], [10, 10], [0, 10], [0, 0]
]]);

// Разделяющая линия, расширенная до полигона
const cutter = polygon([[
  [5, -5], [6, -5], [6, 15], [5, 15], [5, -5]
]]);

const part1 = difference(poly, cutter);

В этой схеме линия преобразуется в узкую полосу-полигон, которая «вырезает» часть области. Для получения второй половины выполняется обратная операция.

const part2 = difference(poly, difference(poly, cutter));

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

Разбиение через пересечение полуплоскостей

Более строгий метод — моделирование разреза через две полуплоскости. Линия разреза задаётся как бесконечная плоскость, ограниченная рамками карты.

Алгоритм:

  1. Определяется линия разреза.
  2. Строятся два больших полигона, покрывающих всю область по разные стороны линии.
  3. Выполняется intersection исходного полигона с каждым из полигонов.
import intersect from "@turf/intersect";

// условные "левый" и "правый" полигоны
const leftHalf = {
  type: "Polygon",
  coordinates: [[
    [-100, -100],
    [5, -100],
    [5, 100],
    [-100, 100],
    [-100, -100]
  ]]
};

const rightHalf = {
  type: "Polygon",
  coordinates: [[
    [5, -100],
    [100, -100],
    [100, 100],
    [5, 100],
    [5, -100]
  ]]
};

const partA = intersect(poly, leftHalf);
const partB = intersect(poly, rightHalf);

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

Разбиение полигона сеткой

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

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

Построение сетки

Сетка создаётся вручную через шаг по координатам:

const cells = [];

for (let x = 0; x < 10; x += 2) {
  for (let y = 0; y < 10; y += 2) {
    cells.push({
      type: "Polygon",
      coordinates: [[
        [x, y],
        [x + 2, y],
        [x + 2, y + 2],
        [x, y + 2],
        [x, y]
      ]]
    });
  }
}

Пересечение с полигоном

import intersect from "@turf/intersect";

const clippedCells = cells
  .map(cell => intersect(poly, cell))
  .filter(Boolean);

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

Разбиение по bounding box и тайлам

Для больших геометрий часто используется разбиение через bbox (bounding box).

import bbox from "@turf/bbox";
import bboxPolygon from "@turf/bbox-polygon";

const bounds = bbox(poly);

const tile = bboxPolygon([
  bounds[0],
  bounds[1],
  bounds[2],
  bounds[3]
]);

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

Разбиение с учётом криволинейных границ

При разбиении по сложным линиям (например, маршрутам или изолиниям) используется промежуточное построение точек пересечения и реконструкция полигонов.

Алгоритм:

  1. Вычисляются точки пересечения линии и границы полигона.
  2. Исходный контур разбивается в этих точках.
  3. Формируются новые замкнутые контуры.

Turf.js предоставляет базовые инструменты:

  • lineIntersect
  • lineSplit
  • polygonToLine
  • lineToPolygon (в отдельных расширениях)

Пример поиска пересечений:

import lineIntersect from "@turf/line-intersect";

const intersections = lineIntersect(polygonToLine(poly), cutterLine);

Дальнейшая реконструкция требует сортировки точек по направлению линии и построения новых контуров.

Обработка отверстий (holes)

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

Правило:

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

В Turf.js это часто решается через повторную проверку booleanPointInPolygon для контрольных точек.

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

const belongs = booleanPointInPolygon(holeCenter, part);

Численная устойчивость и проблемы топологии

Разбиение полигонов в Turf.js подвержено типичным геометрическим проблемам:

  • дублирование вершин после операций пересечения;
  • микросегменты (slivers);
  • самопересечения после вычитания;
  • потеря замкнутости контуров;
  • ошибки при работе с почти параллельными линиями.

Для стабилизации применяются:

  • @turf/simplify для уменьшения количества точек;
  • буферизация с нулевым или малым радиусом;
  • округление координат;
  • фильтрация коллинеарных точек.

Практический сценарий: разбиение полигона на две части

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

  1. Линия разреза расширяется до большой полосы.
  2. Создаются два полуполигона.
  3. Выполняется intersect.
  4. Результаты нормализуются.
import intersect from "@turf/intersect";

const left = intersect(poly, leftHalfPlane);
const right = intersect(poly, rightHalfPlane);

const result = [left, right].filter(Boolean);

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

Разбиение как основа пространственного анализа

Разбиение полигонов используется в задачах:

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

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