Разность полигонов — это геометрическая операция, при которой из одного полигона вычитается область другого полигона. В результате формируется новая геометрия, содержащая только те части исходного объекта, которые не пересекаются с вычитаемым объектом.
Операция широко применяется в геоинформационных системах:
В Turf.js данная операция реализована функцией
difference.
Пусть существуют два полигона:
Тогда результат вычисляется следующим образом:
A − B
В итоговую геометрию попадают все точки полигона A, которые не принадлежат полигону B.
Возможны несколько сценариев:
A = большой прямоугольник
B = круг внутри него
После вычитания внутри прямоугольника появится отверстие.
Если B полностью покрывает A:
A ⊂ B
Результат отсутствует.
Функция возвращает:
null
Если полигоны не пересекаются, результат совпадает с исходным полигоном A.
Современный вариант импорта:
import { difference } from "@turf/turf";
Импорт отдельного пакета:
import difference from "@turf/difference";
Рассмотрим простой пример.
import * as turf from "@turf/turf";
const polygon1 = turf.polygon([
[
[0, 0],
[10, 0],
[10, 10],
[0, 10],
[0, 0]
]
]);
const polygon2 = turf.polygon([
[
[5, 5],
[15, 5],
[15, 15],
[5, 15],
[5, 5]
]
]);
Первый полигон представляет квадрат 10×10.
Второй квадрат перекрывает его правую верхнюю часть.
Для вычисления результата используется функция:
const result = turf.difference(
turf.featureCollection([
polygon1,
polygon2
])
);
Результат:
{
type: "Feature",
geometry: {
type: "Polygon",
...
}
}
Полученная геометрия содержит область первого квадрата без перекрывающейся части второго.
В новых версиях Turf.js операция принимает коллекцию объектов.
Пример:
const result = turf.difference(
turf.featureCollection([
polygonA,
polygonB
])
);
Первый объект считается базовым.
Все последующие объекты последовательно вычитаются из него.
Логически операция выглядит так:
(((A - B) - C) - D)
Допустим, имеется земельный участок и несколько зон, которые необходимо исключить.
const area = turf.polygon([
[
[0, 0],
[20, 0],
[20, 20],
[0, 20],
[0, 0]
]
]);
const lake = turf.polygon([
[
[3, 3],
[8, 3],
[8, 8],
[3, 8],
[3, 3]
]
]);
const forest = turf.polygon([
[
[12, 12],
[18, 12],
[18, 18],
[12, 18],
[12, 12]
]
]);
const industrialZone = turf.polygon([
[
[5, 12],
[10, 12],
[10, 18],
[5, 18],
[5, 12]
]
]);
Вычисление:
const result = turf.difference(
turf.featureCollection([
area,
lake,
forest,
industrialZone
])
);
В результате получится участок без трёх исключённых областей.
Одним из наиболее распространённых результатов операции является появление внутренних колец.
Исходный полигон:
const territory = turf.polygon([
[
[0, 0],
[20, 0],
[20, 20],
[0, 20],
[0, 0]
]
]);
Вычитаемый полигон полностью находится внутри:
const restrictedZone = turf.polygon([
[
[5, 5],
[15, 5],
[15, 15],
[5, 15],
[5, 5]
]
]);
Разность:
const result = turf.difference(
turf.featureCollection([
territory,
restrictedZone
])
);
Геометрия результата будет содержать:
{
type: "Polygon",
coordinates: [
[...внешний контур...],
[...внутреннее кольцо...]
]
}
Второй массив координат представляет отверстие.
После вычитания полигон может распасться на несколько независимых частей.
Пример:
const base = turf.polygon([
[
[0, 0],
[20, 0],
[20, 10],
[0, 10],
[0, 0]
]
]);
const cutter = turf.polygon([
[
[8, -1],
[12, -1],
[12, 11],
[8, 11],
[8, -1]
]
]);
Вычитаемая область полностью разрезает исходную фигуру.
После выполнения операции:
const result = turf.difference(
turf.featureCollection([
base,
cutter
])
);
Результат может иметь тип:
MultiPolygon
Структура:
{
type: "MultiPolygon",
coordinates: [...]
}
Каждая часть представляет отдельный полигон.
Перед дальнейшей обработкой желательно убедиться, что результат существует.
const result = turf.difference(
turf.featureCollection([
polygon1,
polygon2
])
);
if (result) {
console.log("Разность вычислена");
}
Если первый полигон полностью исчезнет:
if (result === null) {
console.log("Объект полностью удалён");
}
Очень часто операция используется совместно с функцией вычисления площади.
const result = turf.difference(
turf.featureCollection([
polygon1,
polygon2
])
);
const area = turf.area(result);
console.log(area);
Так можно определить площадь территории после исключения определённых зон.
Практический пример.
const land = turf.polygon([...]);
const river = turf.polygon([...]);
const lake = turf.polygon([...]);
Удаление водных объектов:
const cleanLand = turf.difference(
turf.featureCollection([
land,
river,
lake
])
);
После этого:
const usefulArea = turf.area(cleanLand);
Будет получена площадь участка без водоёмов.
Часто требуется удалить область вокруг объекта.
Сначала создаётся буфер:
const road = turf.lineString([
[0, 0],
[10, 0]
]);
const buffer = turf.buffer(road, 1, {
units: "kilometers"
});
Затем буфер вычитается:
const result = turf.difference(
turf.featureCollection([
territory,
buffer
])
);
Так формируется территория без зоны влияния дороги.
Нередко операция пересечения используется перед вычитанием.
Например, необходимо убедиться, что объекты действительно пересекаются.
const overlap = turf.intersect(
turf.featureCollection([
polygon1,
polygon2
])
);
Если пересечение существует:
if (overlap) {
const result = turf.difference(
turf.featureCollection([
polygon1,
polygon2
])
);
}
Такой подход позволяет избегать лишних вычислений в сложных системах.
Иногда несколько объектов предварительно объединяют.
const united = turf.union(
turf.featureCollection([
zone1,
zone2,
zone3
])
);
После этого выполняют единственную операцию вычитания:
const result = turf.difference(
turf.featureCollection([
territory,
united
])
);
Подобная схема часто оказывается быстрее и проще для анализа.
Полученный объект является обычным GeoJSON.
Его можно напрямую отображать в картографических библиотеках.
Пример для Leaflet:
L.geoJSON(result).addTo(map);
Пример для MapLibre:
map.addSource("result", {
type: "geojson",
data: result
});
Поскольку результат может быть как Polygon, так и
MultiPolygon, большинство современных картографических
движков корректно обрабатывают оба варианта автоматически.
Неверно:
[
[0, 0],
[10, 0],
[10, 10],
[0, 10]
]
Правильно:
[
[0, 0],
[10, 0],
[10, 10],
[0, 10],
[0, 0]
]
Первая и последняя точки должны совпадать.
Проблемный пример:
[
[0, 0],
[10, 10],
[0, 10],
[10, 0],
[0, 0]
]
Такой контур образует фигуру типа «бантик».
Подобные геометрии могут приводить к некорректным результатам при вычислении разности.
Если вычитаемая область полностью покрывает исходную:
const result = turf.difference(
turf.featureCollection([
smallPolygon,
hugePolygon
])
);
Результат:
null
Этот случай необходимо обрабатывать отдельно.
Скорость выполнения операции зависит от:
Для больших наборов данных рекомендуется:
union;simplify.Пример:
const optimized = turf.simplify(complexPolygon, {
tolerance: 0.0001,
highQuality: false
});
После упрощения операция разности выполняется заметно быстрее на сложных геоданных.
Имеются:
Исходные данные:
const constructionSite = turf.polygon([...]);
const sanitaryZone = turf.polygon([...]);
const waterProtection = turf.polygon([...]);
const buildings = turf.polygon([...]);
Вычисление пригодной территории:
const availableArea = turf.difference(
turf.featureCollection([
constructionSite,
sanitaryZone,
waterProtection,
buildings
])
);
Определение площади:
const squareMeters =
turf.area(availableArea);
Полученная геометрия представляет все участки, пригодные для строительства после исключения ограничивающих объектов.