Пересечение геометрий (Intersection) — это операция пространственного анализа, позволяющая определить общую область между двумя или несколькими геометрическими объектами. Результатом может быть новая геометрия, содержащая только те координаты, которые принадлежат всем участвующим объектам одновременно.
В веб-картографии пересечения используются для решения множества задач:
MapLibre GL JS отвечает за визуализацию данных, тогда как геометрические вычисления обычно выполняются при помощи специализированных библиотек, например Turf.js.
Результат операции зависит от типов исходных объектов.
Если два полигона перекрываются, результатом становится новый полигон.
Исходные объекты:
Polygon A
┌───────────┐
│ │
│ ┌───────┼────┐
│ │ │ │
└───┼───────┘ │
│ │
└────────────┘
Polygon B
Результат:
┌────────┐
│ │
└────────┘
Область пересечения содержит только общую часть двух фигур.
Если линия проходит через полигон, результатом становится отрезок линии, находящийся внутри полигона.
───────────────
┌─────┐
│ │
└─────┘
───────────────
После пересечения:
┌─────┐
│─────│
└─────┘
Если точка находится внутри полигона, пересечение существует.
┌──────────┐
│ ● │
└──────────┘
Если точка расположена снаружи, результат отсутствует.
При пересечении двух линий результатом может быть:
Для выполнения операций пересечения обычно используется 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
])
);
Полученная область показывает территорию, одновременно обслуживаемую обеими станциями.
Пересечение может вычисляться после пользовательских действий.
Например, после перемещения объекта:
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;
}
Добавление пустого результата в источник данных может привести к ошибкам отображения.
Операции пересечения являются вычислительно затратными.
Основные причины:
Практические методы оптимизации:
const simplified = turf.simplify(
polygon,
{
tolerance: 0.001,
highQuality: false
}
);
const bounds = map.getBounds();
Обрабатывать только объекты внутри текущего окна карты значительно эффективнее.
Если набор данных редко изменяется, результаты пересечения можно сохранять:
const cache = new Map();
Ключом может выступать комбинация идентификаторов объектов.
Многие административные границы представлены несколькими несвязанными полигонами.
Проверка типа:
console.log(feature.geometry.type);
Результат:
MultiPolygon
Функция intersect() поддерживает такие объекты
напрямую:
const result = turf.intersect(
turf.featureCollection([
multiPolygonFeature,
polygonFeature
])
);
Результатом может быть:
Поэтому перед отображением полезно проверять структуру ответа.
Пусть имеются:
Район:
const district = districtFeature;
Зона затопления:
const floodZone = floodFeature;
Пересечение:
const affectedArea = turf.intersect(
turf.featureCollection([
district,
floodZone
])
);
После отображения на карте становится видно, какая часть района попадает в опасную территорию.
Подобный подход применяется в:
| Операция | Назначение |
|---|---|
| Intersection | Получение общей части объектов |
| Union | Объединение геометрий |
| Difference | Вычитание одной геометрии из другой |
| Buffer | Построение зоны вокруг объекта |
| Dissolve | Слияние смежных объектов |
| Clip | Обрезка геометрии по границе |
Intersection отличается тем, что сохраняет только область, принадлежащую всем участвующим геометриям одновременно.
Типичный процесс состоит из нескольких этапов:
Такая схема обеспечивает разделение обязанностей: