Объединение полигонов (Union) — это геометрическая операция, которая формирует новый полигон на основе двух или более исходных полигонов. Результирующая геометрия содержит всю площадь, занимаемую хотя бы одним из входных объектов.
Если два полигона пересекаются, область пересечения становится частью единой фигуры. Если полигоны соприкасаются границами, после объединения они также превращаются в одну геометрию. Если объекты расположены отдельно друг от друга, результатом может стать мультиполигон.
В веб-картографии операция Union применяется для решения множества задач:
MapLibre GL JS отвечает за отображение пространственных данных, однако не содержит встроенных алгоритмов геометрической обработки. Для выполнения операций Union обычно используются специализированные библиотеки:
Наиболее распространённым решением в экосистеме JavaScript является библиотека Turf.js.
Для выполнения объединения необходимо представить полигоны в формате GeoJSON.
Пример двух пересекающихся полигонов:
const polygonA = {
type: "Feature",
properties: {},
geometry: {
type: "Polygon",
coordinates: [[
[30, 10],
[40, 10],
[40, 20],
[30, 20],
[30, 10]
]]
}
};
const polygonB = {
type: "Feature",
properties: {},
geometry: {
type: "Polygon",
coordinates: [[
[35, 15],
[45, 15],
[45, 25],
[35, 25],
[35, 15]
]]
}
};
Каждый полигон представляет собой объект GeoJSON Feature с геометрией типа Polygon.
Для работы через npm:
npm install @turf/turf
Импорт:
import * as turf from "@turf/turf";
При использовании через CDN:
<script src="https://unpkg.com/@turf/turf@latest/turf.min.js"></script>
После подключения становятся доступны функции пространственного анализа.
В Turf.js объединение выполняется функцией union.
Пример:
const unionPolygon = turf.union(
turf.featureCollection([
polygonA,
polygonB
])
);
Полученная геометрия содержит площадь обоих объектов.
Проверка результата:
console.log(unionPolygon);
В результате формируется новый объект GeoJSON.
Создание карты:
const map = new maplibregl.Map({
container: "map",
style: "https://demotiles.maplibre.org/style.json",
center: [37.5, 17.5],
zoom: 5
});
После загрузки карты добавляется источник данных:
map.on("load", () => {
const unionPolygon = turf.union(
turf.featureCollection([
polygonA,
polygonB
])
);
map.addSource("union-source", {
type: "geojson",
data: unionPolygon
});
});
Теперь данные доступны для визуализации.
Для отображения результирующей области используется слой типа
fill.
map.addLayer({
id: "union-fill",
type: "fill",
source: "union-source",
paint: {
"fill-color": "#0080ff",
"fill-opacity": 0.5
}
});
Карта покажет единый полигон вместо двух отдельных фигур.
Для улучшения восприятия часто отображают контур объединённой области.
map.addLayer({
id: "union-outline",
type: "line",
source: "union-source",
paint: {
"line-color": "#003366",
"line-width": 3
}
});
Использование отдельного линейного слоя позволяет визуально выделить итоговую геометрию.
Во многих приложениях требуется показать разницу между исходными полигонами и результатом объединения.
Добавление исходных объектов:
map.addSource("original-polygons", {
type: "geojson",
data: {
type: "FeatureCollection",
features: [
polygonA,
polygonB
]
}
});
Слой исходных полигонов:
map.addLayer({
id: "original-fill",
type: "fill",
source: "original-polygons",
paint: {
"fill-color": "#ff0000",
"fill-opacity": 0.25
}
});
Слой результата:
map.addLayer({
id: "union-fill",
type: "fill",
source: "union-source",
paint: {
"fill-color": "#0066ff",
"fill-opacity": 0.5
}
});
Такой подход удобен для анализа пространственных операций.
На практике количество объектов редко ограничивается двумя.
Пример коллекции:
const polygons = [
polygonA,
polygonB,
polygonC,
polygonD
];
Последовательное объединение:
let result = polygons[0];
for (let i = 1; i < polygons.length; i++) {
result = turf.union(
turf.featureCollection([
result,
polygons[i]
])
);
}
После завершения цикла переменная result содержит
итоговую геометрию.
Более компактный вариант:
const result = polygons.reduce((accumulator, current) => {
return turf.union(
turf.featureCollection([
accumulator,
current
])
);
});
Подход хорошо подходит для обработки динамических наборов данных.
Часто пользователь выбирает несколько полигонов на карте, после чего система объединяет их.
Массив выбранных объектов:
const selectedFeatures = [];
Добавление объекта:
selectedFeatures.push(feature);
Выполнение объединения:
const unionResult = selectedFeatures.reduce(
(accumulator, current) => {
return turf.union(
turf.featureCollection([
accumulator,
current
])
);
}
);
Полученный результат может немедленно отображаться на карте.
Если источник уже существует, повторное создание слоя не требуется.
Достаточно обновить данные:
map.getSource("union-source").setData(unionResult);
MapLibre автоматически перерисует слой.
Такой механизм особенно полезен при интерактивном редактировании геометрий.
После объединения результат может оказаться мультиполигоном.
Например:
{
type: "MultiPolygon",
coordinates: [...]
}
Это происходит, когда объединяемые области не имеют общих точек.
MapLibre GL JS одинаково хорошо отображает Polygon и MultiPolygon
через слой типа fill.
Дополнительная обработка обычно не требуется.
Перед дальнейшими вычислениями полезно определить тип геометрии.
console.log(
unionResult.geometry.type
);
Возможные значения:
Polygon
MultiPolygon
На основе результата можно выбирать дальнейшую логику обработки.
Операция Union относится к вычислительно сложным геометрическим операциям.
При работе с сотнями или тысячами объектов рекомендуется:
Пример упрощения:
const simplified = turf.simplify(
polygon,
{
tolerance: 0.001,
highQuality: false
}
);
После упрощения количество вершин уменьшается, что ускоряет вычисления.
При больших объёмах данных вычисления могут блокировать главный поток браузера.
Главный поток:
worker.postMessage(polygons);
Код воркера:
self.onmess age = (event) => {
const polygons = event.data;
let result = polygons[0];
for (let i = 1; i < polygons.length; i++) {
result = turf.union(
turf.featureCollection([
result,
polygons[i]
])
);
}
self.postMessage(result);
};
Получение результата:
worker.onmess age = (event) => {
map.getSource("union-source")
.setData(event.data);
};
Такой подход сохраняет отзывчивость интерфейса.
Некорректные полигоны могут приводить к ошибкам пространственного анализа.
Типичные проблемы:
Проверка корректности:
const valid = turf.booleanValid(
polygon
);
Результат:
console.log(valid);
Перед выполнением Union рекомендуется валидировать входные данные.
Для акцентирования объединённой области можно использовать стилизацию.
Пример:
map.addLayer({
id: "union-highlight",
type: "fill",
source: "union-source",
paint: {
"fill-color": "#00aa88",
"fill-opacity": 0.7
}
});
Дополнительно можно использовать:
Предположим, имеется набор районов города.
Структура данных:
const districts = {
type: "FeatureCollection",
features: [...]
};
Пользователь выбирает несколько районов.
Получение выбранных объектов:
const selected = districts.features.filter(
feature => selectedIds.includes(
feature.properties.id
)
);
Объединение:
let merged = selected[0];
for (let i = 1; i < selected.length; i++) {
merged = turf.union(
turf.featureCollection([
merged,
selected[i]
])
);
}
Результирующая геометрия может использоваться для:
Объединённый полигон часто становится промежуточным результатом.
Вычисление площади:
const area = turf.area(
unionResult
);
Построение буфера:
const buffer = turf.buffer(
unionResult,
1,
{
units: "kilometers"
}
);
Получение центра:
const center = turf.centroid(
unionResult
);
Поиск ограничивающего прямоугольника:
const bbox = turf.bbox(
unionResult
);
Использование объединённой геометрии в качестве основы для дальнейшего пространственного анализа является одним из наиболее распространённых сценариев работы с MapLibre GL JS и Turf.js.