Алгоритм надування / дефляції (зміщення, буферизація) полігонів


202

Як би я "надув" багатокутник? Тобто я хочу зробити щось подібне до цього:

alt текст

Вимога полягає в тому, що ребра / точки нового (завищеного) полігону знаходяться на однаковій постійній відстані від старого (оригінального) багатокутника (на прикладі цього зображення вони не є, оскільки тоді доведеться використовувати дуги для завищених вершин, але давайте забудьте про це наразі;)).

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

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

Ось декілька посилань:


17
Це зовсім не тривіальне питання: якщо дефляція / інфляція невелика, нічого серйозного не станеться, але в якийсь момент вершини зникнуть. Можливо, це було раніше, тому я б сказав: використовуйте чужий алгоритм, не будуйте свій власний.
Martijn

1
Дійсно, якщо ваш багатокутник увігнутий для початку (як у наведеному вище прикладі), ви повинні вирішити, що повинно статися в тому місці, коли наївний алгоритм хоче зробити самопересічний «багатокутник» ...
AakashM

Так, головна проблема - це увігнуті частини багатокутника, саме тут лежить складність. Я все ще думаю, що не повинно бути такою проблемою підрахувати, коли певна вершина повинна бути усунена. Головне питання - яка асимптотична складність цього потребуватиме.
Ігор Брейц

Привіт, це теж моя проблема, за винятком того, що мені потрібно це робити в 3D. Чи існує альтернатива прямолінійному скелету тривимірних багатогранників, описаному в статті arxiv.org/pdf/0805.0022.pdf ?
stephanmg

Відповіді:


138

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

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

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

Стилі зміщення багатокутника


2
Дуже круто! Де ти був 2 роки тому? :) Врешті-решт мені довелося реалізувати власну логіку компенсації (і втратила на це багато часу). Який алгоритм ви використовуєте для зміщення багатокутника, BTW? Я використовував кострицю. Чи обробляєте ви отвори в багатокутниках?
Ігор Брейц

2
2 роки тому я шукав гідне рішення для вирізання багатокутника, який не був обтяжений складними питаннями ліцензії :). Зсув краю досягається створенням одиничних нормалей для всіх ребер. Крайові з’єднання прикручуються моєю багатокутною затискачем, оскільки орієнтації цих перекритих перехрестів протилежні орієнтації полігонів. Отвори, безумовно, обробляються так само, як пересічні багатокутники тощо. Немає обмежень щодо їх типу чи кількості. Дивіться також " Зміщення
Angus Johnson

Ого! Ні на секунду не думайте, що це питання "забуте"! Я зазирнув сюди минулого тижня - я не сподівався повернутися до цього! Дякую купу!
Кріс Берт-Браун

Документи Кліппера
Drew

5
Для всіх, хто хоче це зробити, ще одна альтернатива - використовувати GEOS, а якщо ви використовуєте python, оболонку GEOS, Shapely. Справді гарний приклад: toblerity.github.com/shapely/manual.html#object.buffer
pelson

40

Полігон, який ви шукаєте, в обчислювальній геометрії називається зміщеним вгору / назовні полігоном і він тісно пов'язаний з прямим скелетом .

Це кілька зміщених багатокутників для складного багатокутника:

А це прямий скелет для іншого багатокутника:

Як зазначалося в інших коментарях, також, залежно від того, наскільки ви плануєте "надути / здути" свій багатокутник, ви можете отримати різні можливості підключення для виводу.

З точки зору обчислення: коли у вас є прямий скелет, ви зможете відносно легко побудувати зміщені багатокутники. У бібліотеці CGAL з відкритим кодом та (безкоштовно для некомерційних) є пакет, що реалізує ці структури. Дивіться цей приклад коду для обчислення зміщених полігонів за допомогою CGAL.

Посібник з пакета повинен дати вам хорошу відправну точку щодо побудови цих структур, навіть якщо ви не збираєтесь використовувати CGAL, і містить посилання на документи з математичними визначеннями та властивостями:

Посібник CGAL: 2D прямий скелет та зміщення багатокутника


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


5

Кожна лінія повинна розділити площину на "всередину" та "контур"; це можна дізнатися, використовуючи звичайний метод внутрішнього продукту.

Перенесіть усі лінії на деяку відстань.

Розгляньте всі пари сусідніх ліній (лінії, а не відрізок лінії), знайдіть перетин. Це нова вершина.

Очистіть нову вершину, видаливши всі пересічні частини. - у нас тут є декілька випадків

(а) Випадок 1:

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

якщо ви витратите їх на одиницю, ви отримали це:

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

7 і 4 перекриваються .. якщо ви бачите це, ви видаляєте цю точку і всі точки між ними.

(b) випадок 2

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

якщо ви витратите їх на два, ви отримали це:

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

Щоб вирішити це, для кожного сегмента рядка потрібно перевірити, чи він не перетинається з останніми сегментами.

(c) випадок 3

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

витрати на 1. це більш загальний випадок для випадку 1.

(d) випадок 4

те саме, що і у випадку 3, але витрати на два.

Насправді, якщо ви можете вирішити випадок 4. Усі інші випадки - це лише окремий випадок, який перекривається певною лінією чи вершиною.

У випадку 4, ви зберігаєте стек вершин .. ви натискаєте, коли ви знаходите лінії, що перекриваються останнім рядком, з'являються, коли ви отримуєте останній рядок. - точно так, як ви робите у опуклому корпусі.


чи знаєте ви алгоритм пседо для цього.
EmptyData

5

Ось альтернативне рішення, подивіться, чи вам це подобається краще.

  1. Виконайте тріангуляцію , це не повинно бути зворотним - будь-яка тріангуляція зробить це.

  2. Надуйте кожен трикутник - це має бути тривіально. якщо ви зберігаєте трикутник у порядку проти годинникової стрілки, просто перемістіть лінії праворуч і зробіть перетин.

  3. Об'єднайте їх за допомогою модифікованого алгоритму відсікання Вайлера-Атертона


як ти точно надуваєш трикутники? Чи залежить ваш вихід від триангуляції? З таким підходом ви можете впоратися з випадком, коли ви стискаєте багатокутник?
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;
    }

2

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

poly += 2; // buffer polygon by 2

Я не розумію, як ви повинні робити що-небудь з boost :: polygon, оскільки він підтримує лише цілі координати? Скажіть, у мене є загальний багатокутник (з плаваючою комою), і я хочу його розширити - що б я зробив?
Девід Дорія

@DavidDoria: це залежить від того, яка роздільна здатність / точність та динамічний діапазон потрібні для ваших координат, але ви можете використовувати 32-бітний або 64-бітний int з відповідним коефіцієнтом масштабування. Між іншим, я (випадково) використовував boost :: багатокутник з плаваючими координатами в минулому, і, здається, він працює нормально, але він може бути не на 100% надійним (документи застерігають від цього!).
Пол Р

Я був би в порядку з "це буде працювати більшу частину часу" :). Я спробував це: ideone.com/XbZeBf, але він не компілюється - будь-які думки?
Девід Дорія

Я не бачу нічого очевидно неправильного, але в моєму випадку я використовував прямолінійні спеціалізації (polygon_90), тому не знаю, чи це має значення. Минуло кілька років, як я зіграв з цим, хоча.
Пол Р

Гаразд - до мене це повертається зараз - ви можете використовувати лише +=з набором полігонів , а не з окремими багатокутниками. Спробуйте з std :: vector of polygons. (Звичайно, вектор повинен містити лише один многокутник).
Пол Р

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-файлах.

Найкращі побажання, Стефане


0

Я використовую просту геометрію: вектори та / або тригонометрію

  1. У кожному куті знайдіть середній вектор та середній кут. Середній вектор - середнє арифметичне для двох одиничних векторів, визначених ребрами кута. Середній кут - це половина кута, визначеного ребрами.

  2. Якщо вам потрібно розширити (або контрактувати) ваш багатокутник на суму d від кожного краю; Ви повинні вийти (in) на суму d / sin (midAngle), щоб отримати нову кутову точку.

  3. Повторіть це для всіх кутів

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.