1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8

# теория · шаг 5 из 8

DCEL: как сшить пересечения в карту

Коротко

Пересечения рёбер ещё не составляют карту. Чтобы обходить области и переносить их свойства, храни вершины, грани и ориентированные полурёбра. У полуребра есть близнец, следующая и предыдущая записи на границе, начало и грань слева. DCEL занимает линейную память относительно размера разбиения. При наложении двух карт сначала разбей рёбра в пересечениях, затем восстанови связи и пары исходных областей. Здесь размер результата может быть квадратичным относительно входа.

Если механизм уже знаком - листай до «Теперь сам».

Как думаешь: что нужно сохранить

Есть карта районов и карта зон затопления. Заметающая прямая нашла все пересечения границ. Можно ли теперь для каждой полученной области сказать, какой район и какая зона ей соответствуют? Одного списка координат недостаточно: у пересечения несколько выходящих рёбер, и нужно выбрать правильное продолжение границы. Нарисуй две пересекающиеся окружности. Их пересечения одинаковы при обходе любой области, а сами области различаются.

На что это похоже

Представь перекрёсток с двусторонними улицами. Для движения по кварталу важна сторона улицы: идя туда, ты оставляешь слева один квартал, обратно - другой. Поэтому одна геометрическая улица получает две записи. В каждой записан следующий поворот при обходе конкретного квартала. Координаты описывают положение дороги, а указатели - то, куда можно продолжить обход.

Механизм: от определения к алгоритму

DCEL - двусвязный список рёбер плоского разбиения. Каждое геометрическое ребро представляют два противоположно направленных полуребра. В записи хранят origin, twin, next, prev, face. Конец можно получить как начало близнеца. Грань находится слева от направления обхода: внешнюю границу ограниченной грани обходят против часовой стрелки, границы дыр - по часовой.

У грани нужен представитель каждой компоненты границы. Одна ссылка не позволит обойти и внешний контур, и две внутренние дырки. У вершины достаточно ссылки на одно исходящее полуребро; чередование переходов через близнеца и соседнее полуребро позволяет обходить её окружение. Обратные ссылки дают проверяемые равенства: twin[twin[e]] == e, prev[next[e]] == e.

Для наложения разбиений соедини геометрические рёбра двух входов, найди пересечения заметанием и разрежь каждое затронутое ребро. В вершине упорядочь исходящие направления, чтобы сшить границы новых граней. Потом перенеси исходные метки. Каждая новая грань принадлежит паре старых граней. При n исходных рёбрах и k пересечениях типичная оценка заметания - O((n+k) log n). Она предполагает корректную обработку совпадений и вырожденностей, а не только общего положения.

Правило

Связи DCEL должны одновременно сохранять геометрию ребра, обход каждой компоненты границы и грань слева; найденные пересечения необходимо превратить в вершины и новые полурёбра.

Разбор: проследим вычисление

Построим ограниченную квадратную грань и внешнюю грань. Четыре стороны дают восемь полурёбер. Программа проверяет обратные связи, непрерывность обхода и противоположность направлений. Она не строит наложение: это минимальная проверка структуры после геометрического этапа. Для внешней грани направление обхода противоположно внутреннему.

dcel-i-nalozhenie.py
points = [(0, 0), (3, 0), (3, 2), (0, 2)]
origin = [0, 1, 2, 3, 1, 2, 3, 0]
twin = [4, 5, 6, 7, 0, 1, 2, 3]
nxt = [1, 2, 3, 0, 7, 4, 5, 6]
prev = [3, 0, 1, 2, 5, 6, 7, 4]
face = [0] * 4 + [1] * 4

def boundary(start):
    edges = []
    e = start
    while True:
        edges.append(e)
        e = nxt[e]
        if e == start:
            return edges
        assert len(edges) <= len(origin)

for e in range(len(origin)):
    assert twin[twin[e]] == e
    assert prev[nxt[e]] == e
    assert nxt[prev[e]] == e
    assert face[nxt[e]] == face[e]
    assert origin[nxt[e]] == origin[twin[e]]

for start in (0, 4):
    edges = boundary(start)
    vertices = [points[origin[e]] for e in edges]
    area2 = 0
    for i, a in enumerate(vertices):
        b = vertices[(i + 1) % len(vertices)]
        area2 += a[0] * b[1] - a[1] * b[0]
    print("грань", face[start], "обход", edges, "площадь*2", area2)
print("полурёбер", len(origin))
↳  выводpython3 dcel-i-nalozhenie.py
грань 0 обход [0, 1, 2, 3] площадь*2 12
грань 1 обход [4, 7, 6, 5] площадь*2 -12
полурёбер 8

Разбор: где проходит граница

Добавь внутренний квадрат, который не касается внешнего. Между квадратами находится одна грань с дырой. Если сохранить только внешний цикл, обход формально замкнётся и все локальные ссылки будут правильны, но дыра исчезнет из представления. Поэтому проверки указателей не заменяют проверку числа компонент границ. Это другой вид дефекта: структура согласована локально, но не описывает исходную карту целиком.

Что ломается без этого

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

Зачем это в работе

В геоинформационной системе объединяют кадастр с охранными зонами. После наложения нужны площади частей, их соседи и принадлежность исходным объектам. Эти запросы используют связи граней, а не повторный поиск пересечений. Проверять DCEL удобно сразу после каждой локальной правки: ошибка близнеца тогда привязана к одному разрезанному ребру, а не к последующему обходу.

Теперь сам

У ограниченной грани внешняя граница из пяти рёбер и одна треугольная дыра. Сколько представителей компонент ей нужно, и сколько полурёбер непосредственно входит в её границу?

Ответ: два представителя и восемь полурёбер. Близнецы этих восьми принадлежат соседним граням. Дополнительные восемь записей необходимы всей структуре, но в эту границу не входят.

Главное

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

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.