Самопересечения

Самопересечение в контексте картографических данных возникает тогда, когда граница полигона пересекает саму себя, образуя некорректную или неоднозначную геометрию. В терминах GeoJSON это означает, что последовательность координат, описывающих контур Polygon или MultiPolygon, нарушает правило простоты (simple geometry), при котором рёбра не должны пересекаться, кроме как в вершинах.

В Mapbox GL JS такие геометрии могут приводить к непредсказуемому рендерингу, особенно в слоях типа fill, fill-extrusion и при использовании кластеризации или динамической стилизации. Поведение не всегда выражается ошибкой — чаще оно проявляется визуальными артефактами: «дырками», наложениями, исчезновением частей полигона или странными заливками.

Ключевой момент: Mapbox GL JS не гарантирует исправление геометрии на стороне клиента. Библиотека предполагает, что входные данные уже валидны.


Как Mapbox GL JS интерпретирует самопересекающиеся полигоны

В основе рендеринга лежит векторный тайловый конвейер Mapbox. На этапе триангуляции полигона используется алгоритм разбиения на треугольники (earcut). Именно здесь самопересечения становятся критическими.

При корректной геометрии:

  • контур разбивается на замкнутые области;
  • применяется правило заполнения (non-zero или even-odd);
  • формируется стабильная триангуляция.

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

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

Особенно чувствительны случаи, где:

  • контур образует «бантик» (figure-eight);
  • есть пересечение внешнего кольца с внутренними кольцами (holes);
  • присутствуют дублирующиеся вершины.

Типовые причины появления самопересечений

Ошибки генерации геометрии

Часто данные приходят из CAD-систем, GIS-экспорта или пользовательского рисования на карте. На этапе генерации могут возникать:

  • некорректные алгоритмы упрощения (simplification);
  • ошибки округления координат;
  • неконтролируемое добавление промежуточных точек.

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

Алгоритмы вроде Douglas-Peucker при агрессивной настройке допускают пересечения рёбер.

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

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

При высокой tolerance контур может «переламываться» внутрь себя, образуя пересечения.

Ручное редактирование координат

При интерактивном редактировании пользователем:

  • добавление точек без проверки валидности;
  • перемещение вершин через противоположные рёбра;
  • отсутствие snap-to-edge логики.

Поведение Mapbox GL JS при отрисовке

Fill-слои

В слоях типа fill самопересечения чаще всего проявляются как:

  • исчезающие сегменты полигона;
  • «переливы» заливки;
  • визуальные разрывы.
map.addLayer({
  id: "areas",
  type: "fill",
  source: "geo",
  paint: {
    "fill-color": "#3b82f6",
    "fill-opacity": 0.6
  }
});

Fill-rule и его влияние

Mapbox GL JS использует правило заполнения, аналогичное графическим API:

  • nonzero (по умолчанию для большинства случаев)
  • evenodd (в некоторых визуальных сценариях)

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

Самопересекающиеся полигоны могут менять интерпретацию «внутренней» и «внешней» области.


Диагностика самопересечений

Использование Turf.js для проверки валидности

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

const isSelfIntersecting = !turf.kinks(polygon).features.length;

Функция kinks выявляет точки пересечения рёбер.

Если результат содержит геометрии — полигон имеет самопересечения.


Проверка через JSTS

Более строгий подход — использование JSTS (JavaScript Topology Suite):

import jsts from "jsts";

const reader = new jsts.io.GeoJSONReader();
const geom = reader.read(polygon);

const validator = new jsts.operation.valid.IsValidOp(geom);

const valid = validator.isValid();

JSTS позволяет не только выявить проблему, но и получить объяснение причины.


Исправление самопересечений

1. Автоматическая очистка геометрии

Самый практичный способ — привести полигон к валидной форме.

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

const cleaned = turf.rewind(turf.cleanCoords(polygon));

cleanCoords удаляет дублирующиеся точки, а rewind корректирует порядок обхода.


2. Разбиение на простые полигоны

Если исходная геометрия сложная, её можно разложить:

const exploded = turf.polygonSplit(polygon);

Или через более общие алгоритмы:

  • polygon partitioning;
  • triangulation-based decomposition.

3. Буферизация с нулевым радиусом

Один из наиболее стабильных способов «починки»:

const fixed = turf.buffer(polygon, 0);

Этот приём заставляет геометрический движок пересчитать топологию.


Особенности работы с MultiPolygon

В MultiPolygon самопересечения могут возникать не только внутри одного кольца, но и между частями:

  • перекрывающиеся полигоны;
  • касающиеся границы;
  • дублирующие области.

Mapbox GL JS рассматривает их как независимые геометрии, но при рендеринге они могут накладываться, создавая визуальные артефакты.


Влияние на стилизацию и интерактивность

hover и feature-state

Самопересечения могут нарушать hit-testing:

map.on("mousemove", "areas", (e) => {
  map.setFeatureState(
    { source: "geo", id: e.features[0].id },
    { hover: true }
  );
});

Если геометрия некорректна:

  • курсор может «прыгать» между сегментами;
  • features[0] может возвращать неожиданную часть полигона;
  • события могут срабатывать несколько раз.

Fill-extrusion

В 3D-слоях проблема усиливается:

  • самопересекающиеся полигоны могут создавать разрывы в экструзии;
  • высотная интерпретация становится нестабильной;
  • появляются визуальные «впадины».
map.addLayer({
  id: "buildings",
  type: "fill-extrusion",
  source: "geo",
  paint: {
    "fill-extrusion-height": 50,
    "fill-extrusion-color": "#aaa"
  }
});

Алгоритмические аспекты

Ориентация кольца (winding order)

Важную роль играет направление обхода вершин:

  • внешнее кольцо: обычно clockwise;
  • внутренние кольца (holes): counter-clockwise.

Нарушение ориентации усиливает эффект самопересечений, особенно при комбинировании с упрощением.

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

const corrected = turf.rewind(polygon, { reverse: false });

Топологическая консистентность

Mapbox GL JS ожидает, что:

  • каждая вершина принадлежит ровно двум рёбрам;
  • рёбра не пересекаются вне вершин;
  • внутренние кольца строго вложены.

При нарушении этих правил триангулятор работает эвристически, а не детерминированно.


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

Валидация на этапе ввода данных

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

  • проверка turf.kinks;
  • контроль минимального расстояния между вершинами;
  • запрет пересечений при рисовании.

Ограничение редактирования

При интерактивных редакторах:

  • snap-to-grid;
  • snap-to-edge;
  • запрет пересечения активного сегмента с другими рёбрами;
  • локальная проверка на каждый drag event.

Контроль упрощения

При генерации тайлов или оптимизации:

  • использовать адаптивное упрощение;
  • уменьшать tolerance для малых полигонов;
  • избегать упрощения на уровне, где теряется топология.

Поведение при загрузке через vector tiles

Векторные тайлы Mapbox также не защищены от самопересечений. При генерации на сервере (tippecanoe, tilemaker):

  • ошибки геометрии «запекаются» в тайлы;
  • клиент уже не может исправить структуру без повторной обработки;
  • визуальные артефакты масштабируются на все уровни зума.

Связь с производительностью рендеринга

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

  • триангуляции;
  • пересчёта hit-test;
  • обновления buffer cache.

На больших наборах данных это приводит к:

  • падению FPS;
  • задержкам при pan/zoom;
  • росту памяти WebGL контекста.

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

Интересная особенность Mapbox GL JS — зависимость артефактов от zoom:

  • на низких zoom: пересечения сглаживаются;
  • на средних: появляются разрывы;
  • на высоких: становятся явно видимыми.

Это связано с тем, что геометрия пересчитывается на уровне тайлов и LOD (Level of Detail), где разные упрощённые версии полигона могут иметь разные топологические ошибки.