Алгоритм накачивания / выкачивания (смещения, буферизации) полигонов

202

Как бы я «надул» многоугольник? То есть я хочу сделать что-то похожее на это:

альтернативный текст

Требование состоит в том, что ребра / точки нового (надутого) многоугольника находятся на одном и том же постоянном расстоянии от старого (исходного) многоугольника (на примере изображения это не так, поскольку тогда придется использовать дуги для завышенных вершин, но давайте забудь об этом сейчас;)).

Математический термин для того, что я ищу, на самом деле является внутренним / внешним смещением многоугольника . +1 к балтинту за указание на это. Альтернативное наименование - полигональная буферизация .

Результаты моего поиска:

Вот несколько ссылок:

Игорь Брейц
источник
17
Это совсем не тривиальный вопрос: если дефляция / инфляция мала, ничего серьезного не произойдет, но в какой-то момент вершины исчезнут. Вероятно, это было сделано раньше, поэтому я бы сказал: используйте чужой алгоритм, не создавайте свой собственный.
Мартин
1
Действительно, если ваш полигон вогнут для начала (как в примере выше), вы должны решить, что должно произойти в точке, где наивный алгоритм хочет создать самопересекающийся «полигон» ...
AakashM
Да, главная проблема - вогнутые части многоугольника, в этом и заключается сложность. Я все еще думаю, что не должно быть такой проблемой, чтобы вычислить, когда должна быть удалена определенная вершина. Главный вопрос - какой тип асимптотической сложности это потребует.
Игорь Брейц
Здравствуйте, это тоже моя проблема, за исключением того, что мне нужно сделать это в 3D. Есть ли альтернатива подходу «Прямые скелеты трехмерных многогранников», описанному в статье arxiv.org/pdf/0805.0022.pdf ?
Stephanmg

Ответы:

138

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

Хотя Clipper в первую очередь предназначен для операций срезания полигонов, он также выполняет смещение полигонов. Библиотека с открытым исходным кодом написана на Delphi, C ++ и C # . Он имеет очень свободную лицензию Boost, позволяющую бесплатно использовать ее как в бесплатных, так и в коммерческих приложениях.

Смещение полигонов может быть выполнено с использованием одного из трех стилей смещения - квадрат, округление и сглаживание.

Стили смещения полигонов

Ангус Джонсон
источник
2
Очень круто! Где вы были 2 года назад? :) В конце концов мне пришлось реализовать свою собственную логику смещения (и я на это потерял много времени). Какой алгоритм вы используете для смещения полигонов, кстати? Я использовал Grassfire. Вы обрабатываете отверстия в полигонах?
Игорь Брейц
2
2 года назад я искал достойное решение для обрезки полигонов, которое не было бы обременено сложными лицензионными проблемами :). Смещение ребер достигается путем генерации единичных нормалей для всех ребер. Соединения краев убраны моим полигонным клипером, поскольку ориентации этих перекрывающихся пересечений противоположны ориентации полигонов. Отверстия, несомненно, обрабатываются так же, как и самопересекающиеся многоугольники и т. Д. Нет ограничений по их типу или количеству. См. Также «Смещение полигонов путем вычисления чисел обмоток
Ангус Джонсон,
Вау! Не думайте, что этот вопрос «забыт»! Я посмотрел здесь на прошлой неделе - я не ожидал вернуться к этому! Огромное спасибо!
Крис Бурт-Браун
Документы Клипера по поли буферизации находятся здесь: angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/…
Дрю Ноакс
5
Для тех, кто хочет сделать это, другой альтернативой является использование GEOS, а если вы используете python, оболочка GEOS, Shapely. Очень красивый пример: toblerity.github.com/shapely/manual.html#object.buffer
pelson,
40

Нужный многоугольник в вычислительной геометрии называется смещенным внутрь / наружу многоугольником, и он тесно связан с прямым скелетом .

Это несколько смещенных многоугольников для сложного многоугольника:

И это прямой скелет для другого многоугольника:

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

С вычислительной точки зрения: если у вас есть прямой скелет, вы сможете относительно легко построить смещенные полигоны. Библиотека с открытым исходным кодом и (бесплатная для некоммерческого) библиотека CGAL имеет пакет, реализующий эти структуры. Посмотрите этот пример кода, чтобы вычислить смещенные полигоны, используя CGAL.

Руководство по пакету должно дать вам хорошее начальное представление о том, как построить эти структуры, даже если вы не собираетесь использовать CGAL, и содержит ссылки на статьи с математическими определениями и свойствами:

Руководство CGAL: 2D прямолинейное смещение скелета и многоугольника

balint.miklos
источник
12

Для таких вещей я обычно использую JTS . В демонстрационных целях я создал этот jsFiddle, который использует JSTS (порт JavaScript JTS). Вам просто нужно преобразовать нужные вам координаты в координаты JSTS:

function vectorCoordinates2JTS (polygon) {
  var coordinates = [];
  for (var i = 0; i < polygon.length; i++) {
    coordinates.push(new jsts.geom.Coordinate(polygon[i].x, polygon[i].y));
  }
  return coordinates;
}

Результат примерно такой:

введите описание изображения здесь

Дополнительная информация : Я обычно использую этот тип надувания / выкачивания (немного модифицированный для моих целей) для установки границ с радиусом на многоугольниках, которые нарисованы на карте (с помощью листовок или карт Google). Вы просто конвертируете (lat, lng) пары в координаты JSTS, а все остальное тоже самое. Пример:

введите описание изображения здесь

Марко Летич
источник
9

Похоже, что вы хотите, это:

  • Начиная с вершины, лицом против часовой стрелки вдоль смежного края.
  • Замените ребро новым, параллельным ребром, расположенным на расстоянии d«слева» от старого.
  • Повторите для всех краев.
  • Найдите пересечения новых ребер, чтобы получить новые вершины.
  • Определите, стали ли вы скрещенным многоугольником, и решите, что с этим делать. Вероятно, добавьте новую вершину в точке пересечения и избавьтесь от некоторых старых. Я не уверен, есть ли лучший способ обнаружить это, чем просто сравнить каждую пару несмежных ребер, чтобы увидеть, находится ли их пересечение между обеими парами вершин.

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

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

Стив Джессоп
источник
6

В мире ГИС для этой задачи используется отрицательная буферизация: http://www-users.cs.umn.edu/~npramod/enc_pdf.pdf

Библиотека JTS должна сделать это для вас. См. Документацию по операции с буфером: http://tsusiatsoftware.net/jts/javadoc/com/vividsolutions/jts/operation/buffer/package-summary.html

Для приблизительного обзора см. Также Руководство разработчика: http://www.vividsolutions.com/jts/bin/JTS%20Developer%20Guide.pdf

stryeko
источник
5

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

Переместите все линии наружу на некоторое расстояние.

Рассмотрим всю пару соседних линий (линии, а не отрезок), найдите пересечение. Это новая вершина.

Очистите новую вершину, удалив все пересекающиеся части. - у нас есть несколько случаев здесь

(а) Случай 1:

 0--7  4--3
 |  |  |  |
 |  6--5  |
 |        |
 1--------2

если вы потратите его на одного, вы получите это:

0----a----3
|    |    |
|    |    |
|    b    |
|         |
|         |
1---------2

7 и 4 перекрываются .. если вы видите это, вы удаляете эту точку и все точки между ними.

(б) дело 2

 0--7  4--3
 |  |  |  |
 |  6--5  |
 |        |
 1--------2

если вы потратите его на два, вы получите это:

0----47----3
|    ||    |
|    ||    |
|    ||    |
|    56    |
|          |
|          |
|          |
1----------2

Чтобы решить эту проблему, для каждого сегмента линии необходимо проверить, не перекрываются ли они с последними сегментами.

(в) дело 3

       4--3
 0--X9 |  |
 |  78 |  |
 |  6--5  |
 |        |
 1--------2

Расход на 1. Это более общий случай для случая 1.

(d) дело 4

так же, как case3, но расход на два.

На самом деле, если вы можете обработать случай 4. Все другие случаи - это просто особый случай с некоторым перекрытием линий или вершин.

Для случая 4 вы сохраняете стек вершин. Вы нажимаете, когда обнаруживаете, что линии перекрываются с последней, и выталкиваете его, когда получаете последнюю строку. - так же, как то, что вы делаете в выпуклой оболочке.

J-16 SDiZ
источник
Вы знаете какой-либо алгоритм Psedo для этого.
EmptyData
5

Вот альтернативное решение, посмотрите, нравится ли вам это больше.

  1. Сделайте триангуляцию , это не должно быть Делоне - подойдет любая триангуляция.

  2. Раздуйте каждый треугольник - это должно быть тривиально. если вы храните треугольник в направлении против часовой стрелки, просто переместите линии на правую сторону и сделайте пересечение.

  3. Объедините их, используя модифицированный алгоритм отсечения Вейлера-Атертона

J-16 SDiZ
источник
как вы точно раздули треугольники? Ваш вывод зависит от триангуляции? При таком подходе вы можете справиться со случаем, когда вы сокращаете многоугольник?
balint.miklos
Вы уверены, что этот подход действительно работает для инфляции полигонов? Что происходит, когда вогнутые части многоугольника раздуваются до такой степени, что некоторые вершины должны быть удалены. Дело в том, что когда вы смотрите, что происходит с треугольниками после поли. инфляция, треугольники не раздуты, вместо этого они искажены.
Игорь Брейц
1
Игорь: алгоритм отсечения Вейлера-Атертона может правильно обрабатывать случай «некоторые вершины должны быть исключены»;
J-16 SDiZ
@balint: накачать треугольник тривиально: если вы храните вертрекс в нормальном порядке, правая сторона всегда "наружу". Просто обработайте эти отрезки как линии, переместите их наружу и найдите взаимодействие - это новая вершина. Для самой триангуляции, если подумать, триангуляция Делоне может дать лучший результат.
J-16 SDiZ
4
Я думаю, что такой подход может легко дать плохие результаты. Даже для простого примера, как треугольник четырехугольной с использованием диагонали. Для двух увеличенных треугольников вы получаете: img200.imageshack.us/img200/2640/counterm.png и их объединение - это не то, что вы ищете. Я не вижу, как этот метод полезен.
balint.miklos
3

Большое спасибо Ангусу Джонсону за его библиотеку клиперов. Есть хорошие примеры кода для выполнения отсечения на домашней странице отсечения по адресу http://www.angusj.com/delphi/clipper.php#code, но я не видел пример смещения полигонов. Поэтому я подумал, что, может быть, кому-то это пригодится, если я выложу свой код:

    public static List<Point> GetOffsetPolygon(List<Point> originalPath, double offset)
    {
        List<Point> resultOffsetPath = new List<Point>();

        List<ClipperLib.IntPoint> polygon = new List<ClipperLib.IntPoint>();
        foreach (var point in originalPath)
        {
            polygon.Add(new ClipperLib.IntPoint(point.X, point.Y));
        }

        ClipperLib.ClipperOffset co = new ClipperLib.ClipperOffset();
        co.AddPath(polygon, ClipperLib.JoinType.jtRound, ClipperLib.EndType.etClosedPolygon);

        List<List<ClipperLib.IntPoint>> solution = new List<List<ClipperLib.IntPoint>>();
        co.Execute(ref solution, offset);

        foreach (var offsetPath in solution)
        {
            foreach (var offsetPathPoint in offsetPath)
            {
                resultOffsetPath.Add(new Point(Convert.ToInt32(offsetPathPoint.X), Convert.ToInt32(offsetPathPoint.Y)));
            }
        }

        return resultOffsetPath;
    }
PainElemental
источник
2

Еще одним вариантом является использование boost :: polygon - документации немного не хватает, но вы должны обнаружить, что методы resizeи bloat, а также перегруженный +=оператор, которые фактически реализуют буферизацию. Так, например, увеличить размер многоугольника (или набора многоугольников) на некоторое значение можно так же просто, как:

poly += 2; // buffer polygon by 2
Пол Р
источник
Я не понимаю, как вы должны делать что-то с boost :: polygon, так как он поддерживает только целочисленные координаты? Скажем, у меня есть общий многоугольник (координаты с плавающей точкой), и я хочу его расширить - что бы я сделал?
Дэвид Дория
@DavidDoria: это зависит от того, какое разрешение / точность и динамический диапазон вам нужны для ваших координат, но вы можете использовать 32-битное или 64-битное int с соответствующим коэффициентом масштабирования. Между прочим, я (случайно) использовал boost :: polygon с координатами float в прошлом, и, похоже, он работает нормально, но он может быть не на 100% надежным (документы предупреждают об этом!).
Пол Р
Я был бы в порядке с «это будет работать работать большую часть времени» :). Я попробовал это: ideone.com/XbZeBf, но он не компилируется - есть мысли?
Дэвид Дория
Я не вижу ничего явно неправильного, но в моем случае я использовал прямолинейную специализацию (polygon_90), поэтому я не знаю, имеет ли это значение. Прошло уже несколько лет с тех пор, как я поиграл с этим.
Пол Р
ОК - теперь он возвращается ко мне - вы можете использовать только +=с набором полигонов , а не с отдельными полигонами. Попробуйте это с помощью std :: vector многоугольников. (Конечно, вектор должен содержать только один многоугольник).
Пол Р
1

Судя по совету @ JoshO'Brian, rGeosпакет на Rязыке реализует этот алгоритм. См rGeos::gBuffer.

Карл Виттофт
источник
0

Есть несколько библиотек, которые можно использовать (также можно использовать для наборов данных 3D).

  1. https://github.com/otherlab/openmesh
  2. https://github.com/alecjacobson/nested_cages
  3. http://homepage.tudelft.nl/h05k3/Projects/MeshThickeningProj.htm

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

Последний имеет наименьшие зависимости, является автономным и может читать в файлах .obj.

С наилучшими пожеланиями, Стефан

stephanmg
источник
0

Я использую простую геометрию: векторы и / или тригонометрию

  1. В каждом углу найдите средний вектор и средний угол. Средний вектор - среднее арифметическое двух единичных векторов, определенных краями угла. Средний угол - это половина угла, определенного краями.

  2. Если вам нужно расширить (или сжать) свой полигон на величину d от каждого ребра; Вы должны выйти (в) на величину d / sin (midAngle), чтобы получить новую угловую точку.

  3. Повторите это для всех углов

*** Будьте осторожны в вашем направлении. Сделайте CounterClockWise Test, используя три точки, определяющие угол; выяснить, какой выход, или в.

user2800464
источник