Работа с отверстиями в полигонах

Полигоны с отверстиями в GeoJSON представляются как объект типа Polygon, где первый контур описывает внешнюю границу, а последующие контуры — внутренние вырезы. Такая структура широко используется в геопространственных данных и полностью поддерживается Turf.js при условии корректного формирования координат.

Структура полигона с отверстиями основана на массиве колец:

  • первое кольцо — внешний контур (outer ring)
  • последующие кольца — внутренние контуры (holes)

Каждое кольцо — это массив координат вида [lng, lat], где первая и последняя точки совпадают для замыкания контура.

Формат GeoJSON:

{
  "type": "Polygon",
  "coordinates": [
    [ // внешний контур
      [30.0, 10.0],
      [40.0, 40.0],
      [20.0, 40.0],
      [10.0, 20.0],
      [30.0, 10.0]
    ],
    [ // отверстие
      [20.0, 30.0],
      [35.0, 35.0],
      [30.0, 20.0],
      [20.0, 30.0]
    ]
  ]
}

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

Создание полигона с отверстиями в Turf.js

Turf.js не требует специальных функций для создания отверстий — используется стандарт turf.polygon.

import { polygon } from "@turf/turf";

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

В этом примере внутренняя область автоматически интерпретируется как вырез.

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

Функция booleanPointInPolygon учитывает внутренние кольца и возвращает false, если точка попадает в отверстие.

import { booleanPointInPolygon, point, polygon } from "@turf/turf";

const pt = point([5, 5]);

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

booleanPointInPolygon(pt, poly); // false

Точка внутри внешнего контура, но попадает в отверстие, поэтому исключается из области.

Важность порядка обхода (winding order)

Хотя GeoJSON RFC 7946 не требует строгого направления обхода, многие геоинструменты придерживаются соглашения:

  • внешний контур: против часовой стрелки (CCW)
  • отверстия: по часовой стрелке (CW)

Turf.js в большинстве операций не зависит строго от ориентации, но при комбинировании с другими библиотеками (например, Mapbox GL, JSTS) несоблюдение направления может приводить к некорректной интерпретации отверстий.

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

  • внешний контур — CCW
  • отверстия — CW

Формирование отверстий через булевы операции

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

Использование difference

Функция turf.difference создаёт новую геометрию, вычитая одну область из другой.

import { polygon, difference } from "@turf/turf";

const outer = polygon([[
  [0, 0],
  [20, 0],
  [20, 20],
  [0, 20],
  [0, 0]
]]);

const inner = polygon([[
  [5, 5],
  [15, 5],
  [15, 15],
  [5, 15],
  [5, 5]
]]);

const result = difference(outer, inner);

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

Особенность: результат difference может вернуть не только Polygon, но и MultiPolygon, если вычитание разрывает исходную геометрию.

Объединение и влияние на отверстия

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

import { union } from "@turf/turf";

const merged = union(polyA, polyB);

При объединении сложных фигур важно учитывать:

  • перекрывающиеся отверстия могут «заполняться»
  • результат может быть MultiPolygon
  • структура колец пересобирается алгоритмически

Преобразование MultiPolygon и отверстий

Отверстия в Polygon и MultiPolygon концептуально различаются:

  • Polygon: один внешний контур + внутренние отверстия
  • MultiPolygon: набор независимых полигонов, каждый со своими отверстиями

Пример MultiPolygon:

{
  type: "MultiPolygon",
  coordinates: [
    [
      [
        [0, 0],
        [10, 0],
        [10, 10],
        [0, 10],
        [0, 0]
      ],
      [
        [2, 2],
        [4, 2],
        [4, 4],
        [2, 4],
        [2, 2]
      ]
    ],
    [
      [
        [20, 20],
        [30, 20],
        [30, 30],
        [20, 30],
        [20, 20]
      ]
    ]
  ]
}

Каждый полигон внутри MultiPolygon имеет собственный набор отверстий.

Частые ошибки при работе с отверстиями

1. Незамкнутые кольца

Каждое кольцо должно быть замкнуто:

// неправильно
[ [0,0], [10,0], [10,10], [0,10] ]

// правильно
[ [0,0], [10,0], [10,10], [0,10], [0,0] ]

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

2. Пересечение колец

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

3. Самопересечения

Самопересекающиеся контуры нарушают топологическую корректность. В булевых операциях это часто вызывает null результат.

Проверка точки с несколькими отверстиями

Polygon может содержать несколько внутренних колец:

const poly = polygon([
  outerRing,
  hole1,
  hole2,
  hole3
]);

Правило интерпретации:

  • точка должна находиться внутри внешнего кольца
  • и не должна находиться ни в одном из внутренних колец

Turf.js применяет именно такую модель при пространственных запросах.

Практика построения сложных вырезов

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

  • вычитание зданий из земельного участка
  • вырезание водоёмов из суши
  • создание зон ограниченного доступа внутри области

Композиция операций:

const base = polygon([...]);
const cut1 = polygon([...]);
const cut2 = polygon([...]);

const step1 = difference(base, cut1);
const final = difference(step1, cut2);

Каждая операция может менять структуру геометрии, превращая Polygon в MultiPolygon.

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

Геометрии с большим количеством колец требуют дополнительных вычислений при:

  • point-in-polygon проверках
  • пересечениях
  • буферизации (buffer operations)

Особенно затратны операции:

  • difference
  • union
  • intersect

Рекомендации по снижению нагрузки:

  • минимизировать количество вершин в кольцах
  • избегать избыточных отверстий
  • упрощать геометрию перед булевыми операциями (например, через simplify, если используется сторонняя реализация)

Особенности визуализации

При визуализации (например, через Mapbox GL или Leaflet) отверстия отображаются автоматически при корректной структуре GeoJSON. Важно, чтобы:

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

Нарушение этих условий часто приводит к заполнению отверстий или исчезновению внутренних контуров при рендеринге.