Intersection геометрий

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

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

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

MapLibre GL JS отвечает за визуализацию данных, тогда как геометрические вычисления обычно выполняются при помощи специализированных библиотек, например Turf.js.


Типы геометрий и результаты пересечения

Результат операции зависит от типов исходных объектов.

Полигон и полигон

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

Исходные объекты:

Polygon A
┌───────────┐
│           │
│   ┌───────┼────┐
│   │       │    │
└───┼───────┘    │
    │            │
    └────────────┘
       Polygon B

Результат:

┌────────┐
│        │
└────────┘

Область пересечения содержит только общую часть двух фигур.


Линия и полигон

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

───────────────
     ┌─────┐
     │     │
     └─────┘
───────────────

После пересечения:

     ┌─────┐
     │─────│
     └─────┘

Точка и полигон

Если точка находится внутри полигона, пересечение существует.

┌──────────┐
│    ●     │
└──────────┘

Если точка расположена снаружи, результат отсутствует.


Линия и линия

При пересечении двух линий результатом может быть:

  • одна точка;
  • несколько точек;
  • общий сегмент линии.

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

Для выполнения операций пересечения обычно используется Turf.js.

Подключение через npm:

npm install @turf/turf

Импорт:

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

Либо импорт только необходимых модулей:

import { polygon } from '@turf/helpers';
import { intersect } from '@turf/intersect';

Создание тестовых полигонов

Два перекрывающихся прямоугольника:

const polygon1 = turf.polygon([
    [
        [0, 0],
        [5, 0],
        [5, 5],
        [0, 5],
        [0, 0]
    ]
]);

const polygon2 = turf.polygon([
    [
        [3, 2],
        [8, 2],
        [8, 7],
        [3, 7],
        [3, 2]
    ]
]);

Графически:

Polygon1
┌─────┐
│     │
│  ┌──┼─────┐
└──┼──┘     │
   │        │
   └────────┘
       Polygon2

Вычисление пересечения полигонов

Основная операция:

const intersection = turf.intersect(
    turf.featureCollection([
        polygon1,
        polygon2
    ])
);

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

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

if (intersection) {
    console.log(intersection);
}

Если полигоны не пересекаются, функция возвращает:

null

Отображение пересечения на карте

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

const map = new maplibregl.Map({
    container: 'map',
    style: 'https://demotiles.maplibre.org/style.json',
    center: [4, 4],
    zoom: 5
});

После загрузки карты:

map.on('load', () => {

    map.addSource('polygon1', {
        type: 'geojson',
        data: polygon1
    });

    map.addSource('polygon2', {
        type: 'geojson',
        data: polygon2
    });

    map.addSource('intersection', {
        type: 'geojson',
        data: intersection
    });

});

Стилизация исходных полигонов

Первый полигон:

map.addLayer({
    id: 'polygon1-layer',
    type: 'fill',
    source: 'polygon1',
    paint: {
        'fill-color': '#2196F3',
        'fill-opacity': 0.4
    }
});

Второй полигон:

map.addLayer({
    id: 'polygon2-layer',
    type: 'fill',
    source: 'polygon2',
    paint: {
        'fill-color': '#4CAF50',
        'fill-opacity': 0.4
    }
});

Выделение области пересечения

Результат удобно отображать отдельным слоем.

map.addLayer({
    id: 'intersection-layer',
    type: 'fill',
    source: 'intersection',
    paint: {
        'fill-color': '#FF5722',
        'fill-opacity': 0.8
    }
});

На карте становятся видны:

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

Проверка существования пересечения

Во многих приложениях важно лишь определить факт пересечения.

Простейшая проверка:

const result = turf.intersect(
    turf.featureCollection([
        polygon1,
        polygon2
    ])
);

const intersects = result !== null;

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

if (intersects) {
    console.log('Есть пересечение');
} else {
    console.log('Пересечения нет');
}

Пересечение пользовательских объектов

Часто данные поступают из GeoJSON-файлов.

Загрузка:

const response = await fetch('/data/districts.geojson');
const districts = await response.json();

Предположим, необходимо найти пересечение двух районов:

const districtA = districts.features[0];
const districtB = districts.features[1];

Вычисление:

const overlap = turf.intersect(
    turf.featureCollection([
        districtA,
        districtB
    ])
);

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


Пересечение нескольких полигонов

Для нахождения общей зоны среди нескольких объектов используется последовательное пересечение.

Исходный массив:

const polygons = [
    polygonA,
    polygonB,
    polygonC
];

Алгоритм:

let commonArea = polygons[0];

for (let i = 1; i < polygons.length; i++) {

    commonArea = turf.intersect(
        turf.featureCollection([
            commonArea,
            polygons[i]
        ])
    );

    if (!commonArea) {
        break;
    }
}

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


Пересечение буфера и объекта

Распространённый сценарий — создание буферной зоны вокруг объекта и поиск пересечения.

Создание точки:

const point = turf.point([30, 50]);

Буфер:

const buffer = turf.buffer(
    point,
    5,
    { units: 'kilometers' }
);

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

const result = turf.intersect(
    turf.featureCollection([
        district,
        buffer
    ])
);

Так определяется часть района, расположенная в радиусе пяти километров от точки.


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

Операторы связи часто рассчитывают общие зоны покрытия базовых станций.

Зоны покрытия:

const coverageA = turf.buffer(
    towerA,
    10,
    { units: 'kilometers' }
);

const coverageB = turf.buffer(
    towerB,
    10,
    { units: 'kilometers' }
);

Пересечение:

const overlapZone = turf.intersect(
    turf.featureCollection([
        coverageA,
        coverageB
    ])
);

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


Использование событий MapLibre

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

Например, после перемещения объекта:

marker.on('dragend', () => {

    const position = marker.getLngLat();

    const point = turf.point([
        position.lng,
        position.lat
    ]);

    const buffer = turf.buffer(
        point,
        2,
        { units: 'kilometers' }
    );

    const result = turf.intersect(
        turf.featureCollection([
            cityPolygon,
            buffer
        ])
    );

});

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


Динамическое обновление источника данных

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

const source = map.getSource('intersection');

Обновление:

source.setData(result);

MapLibre автоматически перерисует слой.


Обработка отсутствия пересечения

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

const result = turf.intersect(
    turf.featureCollection([
        polygon1,
        polygon2
    ])
);

Проверка:

if (!result) {

    console.log('Общая область отсутствует');

    return;
}

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


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

Операции пересечения являются вычислительно затратными.

Основные причины:

  • большое количество вершин;
  • сложные контуры;
  • множественные MultiPolygon-объекты;
  • частые пересчёты в реальном времени.

Практические методы оптимизации:

Упрощение геометрии

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

Ограничение вычислений видимой областью

const bounds = map.getBounds();

Обрабатывать только объекты внутри текущего окна карты значительно эффективнее.


Кэширование результатов

Если набор данных редко изменяется, результаты пересечения можно сохранять:

const cache = new Map();

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


Работа с MultiPolygon

Многие административные границы представлены несколькими несвязанными полигонами.

Проверка типа:

console.log(feature.geometry.type);

Результат:

MultiPolygon

Функция intersect() поддерживает такие объекты напрямую:

const result = turf.intersect(
    turf.featureCollection([
        multiPolygonFeature,
        polygonFeature
    ])
);

Результатом может быть:

  • Polygon;
  • MultiPolygon;
  • null.

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


Практический сценарий: поиск зон затопления

Пусть имеются:

  • слой районов;
  • слой прогнозируемого затопления.

Район:

const district = districtFeature;

Зона затопления:

const floodZone = floodFeature;

Пересечение:

const affectedArea = turf.intersect(
    turf.featureCollection([
        district,
        floodZone
    ])
);

После отображения на карте становится видно, какая часть района попадает в опасную территорию.

Подобный подход применяется в:

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

Сравнение Intersection и других пространственных операций

Операция Назначение
Intersection Получение общей части объектов
Union Объединение геометрий
Difference Вычитание одной геометрии из другой
Buffer Построение зоны вокруг объекта
Dissolve Слияние смежных объектов
Clip Обрезка геометрии по границе

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


Архитектура использования Intersection в MapLibre GL JS

Типичный процесс состоит из нескольких этапов:

  1. Загрузка пространственных данных.
  2. Отображение исходных объектов через GeoJSON-источники.
  3. Выполнение пересечения средствами Turf.js.
  4. Создание нового GeoJSON-объекта результата.
  5. Передача результата в отдельный Source.
  6. Визуализация пересечения специальным слоем.
  7. Обновление результата при изменении данных.

Такая схема обеспечивает разделение обязанностей:

  • MapLibre GL JS выполняет визуализацию;
  • Turf.js выполняет пространственный анализ;
  • GeoJSON служит универсальным форматом обмена геометрическими данными.