Union полигонов

Объединение полигонов (Union) — это геометрическая операция, которая формирует новый полигон на основе двух или более исходных полигонов. Результирующая геометрия содержит всю площадь, занимаемую хотя бы одним из входных объектов.

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

В веб-картографии операция Union применяется для решения множества задач:

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

MapLibre GL JS отвечает за отображение пространственных данных, однако не содержит встроенных алгоритмов геометрической обработки. Для выполнения операций Union обычно используются специализированные библиотеки:

  • Turf.js;
  • JSTS;
  • Martinez Polygon Clipping;
  • OpenLayers Geometry Utilities.

Наиболее распространённым решением в экосистеме 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.


Подключение Turf.js

Для работы через npm:

npm install @turf/turf

Импорт:

import * as turf from "@turf/turf";

При использовании через CDN:

<script src="https://unpkg.com/@turf/turf@latest/turf.min.js"></script>

После подключения становятся доступны функции пространственного анализа.


Выполнение Union для двух полигонов

В Turf.js объединение выполняется функцией union.

Пример:

const unionPolygon = turf.union(
    turf.featureCollection([
        polygonA,
        polygonB
    ])
);

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

Проверка результата:

console.log(unionPolygon);

В результате формируется новый объект GeoJSON.


Отображение объединённого полигона в MapLibre GL JS

Создание карты:

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 содержит итоговую геометрию.


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

Более компактный вариант:

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 автоматически перерисует слой.

Такой механизм особенно полезен при интерактивном редактировании геометрий.


Работа с MultiPolygon

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

Например:

{
    type: "MultiPolygon",
    coordinates: [...]
}

Это происходит, когда объединяемые области не имеют общих точек.

MapLibre GL JS одинаково хорошо отображает Polygon и MultiPolygon через слой типа fill.

Дополнительная обработка обычно не требуется.


Проверка типа результата

Перед дальнейшими вычислениями полезно определить тип геометрии.

console.log(
    unionResult.geometry.type
);

Возможные значения:

Polygon
MultiPolygon

На основе результата можно выбирать дальнейшую логику обработки.


Производительность при большом количестве объектов

Операция Union относится к вычислительно сложным геометрическим операциям.

При работе с сотнями или тысячами объектов рекомендуется:

  • выполнять объединение на сервере;
  • использовать Web Workers;
  • минимизировать количество повторных пересчётов;
  • кэшировать результаты;
  • упрощать геометрию перед обработкой.

Пример упрощения:

const simplified = turf.simplify(
    polygon,
    {
        tolerance: 0.001,
        highQuality: false
    }
);

После упрощения количество вершин уменьшается, что ускоряет вычисления.


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

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

Главный поток:

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]
        ])
    );

}

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

  • расчёта площади;
  • отображения единой территории;
  • построения буферных зон;
  • поиска пересечений;
  • экспорта в GeoJSON;
  • формирования аналитических отчётов.

Комбинирование Union с другими операциями Turf.js

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

Вычисление площади:

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.