Як визначити, чи відповідає перелік точок багатокутника за годинниковою стрілкою?


259

Маючи список точок, як я можу дізнатися, чи вони розташовані за годинниковою стрілкою?

Наприклад:

point[0] = (5,0)
point[1] = (6,4)
point[2] = (4,5)
point[3] = (1,5)
point[4] = (1,0)

сказали б, що це проти годинникової стрілки (або проти годинникової стрілки для деяких людей).


4
УВАГА! Примітка: прийнята відповідь і багато відповідей після неї вимагають багато доповнень та множень (вони ґрунтуються на обчисленнях площі, які закінчуються негативними чи позитивними; наприклад, "формула шнурівки"). Перш ніж реалізувати один із них, розглянемо відповідь lhf , яка простіша / швидша - заснована на вікі - орієнтації простого багатокутника .
ToolmakerSteve

Я завжди думаю про це з точки зору поперечного продукту двох суміжних векторів. Якщо я пройдусь по периметру багатокутника, голова вказує на площину. Я перетинаю вектор площини в мій вектор напрямку ходіння, щоб отримати третій напрямок в моїй системі координат. Якщо цей вектор вказує так, що інтер'єр ліворуч - це проти годинникової стрілки; якщо інтер’єр праворуч - це за годинниковою стрілкою.
duffymo

Відповіді:


416

Деякі із запропонованих методів вийдуть з ладу у випадку неопуклого багатокутника, наприклад півмісяця. Ось простий, який буде працювати з неопуклими багатокутниками (він навіть буде працювати з самопересічним багатокутником, як цифра-вісім, підказуючи, чи здебільшого це за годинниковою стрілкою).

Сума по краях, (x 2 - x 1 ) (y 2 + y 1 ). Якщо результат позитивний, крива знаходиться за годинниковою стрілкою, якщо негативна - крива проти годинникової стрілки. (Результат - удвічі більше вкладеної області, з +/- умовою.)

point[0] = (5,0)   edge[0]: (6-5)(4+0) =   4
point[1] = (6,4)   edge[1]: (4-6)(5+4) = -18
point[2] = (4,5)   edge[2]: (1-4)(5+5) = -30
point[3] = (1,5)   edge[3]: (1-1)(0+5) =   0
point[4] = (1,0)   edge[4]: (5-1)(0+0) =   0
                                         ---
                                         -44  counter-clockwise

28
Це обчислення застосовано до простого випадку. (Я не маю навичок розміщувати графіку.) Площа під відрізком лінії дорівнює його середній висоті (y2 + y1) / в 2 рази більше її горизонтальної довжини (x2-x1). Зверніть увагу на умовний знак у x. Спробуйте це з деякими трикутниками, і незабаром побачите, як це працює.
Бета

72
Незначне застереження: ця відповідь передбачає нормальну декартову систему координат. Причина, яку варто згадати, полягає в тому, що деякі поширені контексти, наприклад, полотно HTML5, використовують перевернуту вісь Y. Тоді потрібно перевернути правило: якщо площа негативна , крива знаходиться за годинниковою стрілкою.
LarsH

8
@ Mr.Qbs: Отже, мій метод працює, але якщо пропустити життєво важливу частину , він не працює. Це не новина.
Бета

11
@ Mr.Qbs: Ви завжди повинні пов'язувати останню точку з першою. Якщо у вас N балів, пронумерованих від 0 до N-1, то ви повинні обчислити: Sum( (x[(i+1) mod N] - x[i]) * (y[i] + y[(i+1) mod N]) )для i = 0 до N-1. Тобто, повинен приймати індекс Modulo N ( N ≡ 0) Формула працює лише для закритих багатокутників. Полігони не мають уявних ребер.
Олів'є Якот-Дескомб

4
Цей blog.element84.com/polygon-winding.html простою англійською мовою пояснює, чому це рішення працює.
Давид Зоріхта

49

Хрест продукт вимірює ступінь перпендикулярної-ність двох векторів. Уявіть, що кожен край вашого многокутника є вектором у площині xy тривимірного (3-D) ксилового простору. Тоді поперечний добуток двох послідовних ребер є вектором у напрямку z, (позитивний z-напрямок, якщо другий відрізок за годинниковою стрілкою, мінус z-напрямок, якщо він проти годинникової стрілки). Величина цього вектора пропорційна синусу кута між двома вихідними ребрами, тому він досягає максимуму, коли вони перпендикулярні, і звужується, щоб зникнути, коли ребра колінеарні (паралельні).

Отже, для кожної вершини (точки) багатокутника обчисліть величину поперечного добутку двох сусідніх ребер:

Using your data:
point[0] = (5, 0)
point[1] = (6, 4)
point[2] = (4, 5)
point[3] = (1, 5)
point[4] = (1, 0)

Так Позначте краю послідовно , як
edgeAце відрізок від point0до point1і
edgeBміж point1до point2
...
edgeEміж point4і point0.

Тоді вершина A ( point0) знаходиться між
edgeE[Від point4до point0]
edgeA[Від point0до `точки1 '

Ці два ребра самі є векторами, координати x і y можна визначити, віднімаючи координати їх початкової та кінцевої точок:

edgeE= point0- point4= (1, 0) - (5, 0)= (-4, 0) і
edgeA= point1- point0= (6, 4) - (1, 0)= (5, 4) і

І хрест твір цих двох суміжних ребер обчислюється з використанням визначник наступної матриці, яка будується шляхом введення координат двох векторів нижче символів , що представляють три осі координат ( i, j, & k). Третя (нульова) оцінена координата є, тому що концепція перехресного продукту - це 3-D конструкція, і тому ми розширюємо ці 2-D вектори на 3-D для того, щоб застосувати крос-продукт:

 i    j    k 
-4    0    0
 1    4    0    

Зважаючи на те, що всі поперечні продукти дають вектор, перпендикулярний множині площини двох векторів, детермінант матриці вище має лише kкомпонент (або вісь z).
Формула для обчислення величини kкомпонента осі або z є
a1*b2 - a2*b1 = -4* 4 - 0* 1 = -16

Величина цього значення ( -16) - це міра синуса кута між двома вихідними векторами, помноженими на добуток величин двох векторів.
Власне, ще одна формула його значення
A X B (Cross Product) = |A| * |B| * sin(AB).

Отже, щоб повернутися до міри кута, вам потрібно розділити це значення ( -16) на добуток величин двох векторів.

|A| * |B| = 4 * Sqrt(17) =16.4924...

Отже міра гріха (AB) = -16 / 16.4924=-.97014...

Це міра того, чи наступний відрізок після вершини зігнувся вліво або вправо, і на скільки. Не потрібно приймати дугу-синус. Все, про що ми будемо дбати, - це його масштабність, і звичайно її знак (позитивний чи негативний)!

Зробіть це для кожної з інших 4 точок навколо закритого шляху та додайте значення з цього обчислення в кожній вершині.

Якщо остаточна сума позитивна, ви пішли за годинниковою, негативною, проти годинникової стрілки.


3
Власне, це рішення - це інше рішення, ніж прийняте. Чи є вони еквівалентними, чи ні, це питання, яке я досліджую, але підозрюю, що це не так ... Прийнята відповідь обчислює площу полігону, беручи різницю між площею під верхнім краєм полігону і площею під нижній край багатокутника. Один буде негативним (той, де ви рухаєтесь зліва направо), а другий - негативним. Під час руху за годинниковою стрілкою верхній край проходить ліворуч праворуч і більший, тому загальна сума є додатною.
Чарльз Бретана

1
Моє рішення вимірює суму синусів змін кутів ребер у кожній вершині. Це буде позитивним при переході за годинниковою стрілкою та негативним при переході проти годинникової стрілки.
Чарльз Бретана

2
Здається, що при такому підході НЕ потрібно брати дуги, якщо тільки ви не припускаєте опуклості (у такому випадку вам потрібно перевірити лише одну вершину)
agentp

2
Вам НЕ потрібно взяти дугу. Спробуйте це на купі випадкових неопуклих багатокутників, і ви виявите, що тест буде невдалим для деяких багатокутників, якщо ви не візьмете дугу.
Люк Хатчісон

1
@CharlesBretana - поки я не проводив тест Люка, я вважаю, що він правильний. Така природа підсумовування поєднується з нелінійною шкалою [без арцина проти арцина]. Поміркуйте, що запропонував мерщій, що ви правильно відхилили. Він запропонував вам "просто рахувати", і ви вказали, що жменька великих значень може переважати велику кількість малих значень. Тепер розглянемо дугу кожного значення проти ні. Чи все-таки не так, що якщо не брати арцин, то кожне значення надає неправильну вагу, тому має однаковий недолік (хоча набагато менше)?
ToolmakerSteve

47

Я думаю, це досить старе питання, але я все одно викину інше рішення, тому що це прямо і не є математично інтенсивним - він просто використовує базову алгебру. Обчисліть підписану площу багатокутника. Якщо це негативно, точки розташовуються за годинниковою стрілкою, якщо позитивно - проти годинникової стрілки. (Це дуже схоже на рішення Beta.)

Обчисліть підписану площу: A = 1/2 * (x 1 * y 2 - x 2 * y 1 + x 2 * y 3 - x 3 * y 2 + ... + x n * y 1 - x 1 * y n )

Або в псевдокоді:

signedArea = 0
for each point in points:
    x1 = point[0]
    y1 = point[1]
    if point is last point
        x2 = firstPoint[0]
        y2 = firstPoint[1]
    else
        x2 = nextPoint[0]
        y2 = nextPoint[1]
    end if

    signedArea += (x1 * y2 - x2 * y1)
end for
return signedArea / 2

Зауважте, що якщо ви лише перевіряєте замовлення, вам не потрібно буде ділитися на ділення на 2.

Джерела: http://mathworld.wolfram.com/PolygonArea.html


Це був друкарський опис у формулі вашого підписаного району вище? Закінчується на "xn * y1 - x1 * yn"; коли я вважаю, що це має бути "x_n y_ {n + 1} - y_n x_ {n-1}" (принаймні, у LaTeX). З іншого боку, минуло десять років, як я взяв будь-які класи лінійної алгебри.
Майкл Ерік Оберлін

Ні. Якщо ви перевірите джерело , ви побачите, що формула насправді посилається на першу точку ще раз в останньому члені (y1 та x1). (Вибачте, я не дуже знайомий з LaTeX, але я відформатував підписки, щоб зробити їх більш читабельними.)
Шон Бін

Я використовував це рішення, і він прекрасно працював для мого використання. Зауважте, що якщо ви можете планувати заздалегідь і запасувати і додатково два вектори у своєму масиві, ви можете позбутися порівняння (або%), додавши перший вектор у кінці масиву. Таким чином ви просто переведіть петлю на всі елементи, крім останнього (довжина-2 замість довжини-1).
Ерік

2
@EricFortier - FWIW, а не зміна розміру можливо великого масиву, ефективна альтернатива - для кожної ітерації зберегти свою точку як previousPointдля наступної ітерації. Перед початком циклу встановіть previousPointостанню точку масиву. Компроміс - це додаткова локальна змінна копія, але менший доступ до масиву. І найголовніше, не потрібно чіпати вхідний масив.
ToolmakerSteve

2
@MichaelEricOberlin - необхідно закрити полігон, включивши відрізок лінії від останньої точки до першої точки. (Правильний розрахунок буде такий же, незалежно від того , якій точці починається замкнутий багатокутник.)
ToolmakerSteve

36

Знайдіть вершину з найменшим y (і найбільшим x, якщо є зв’язки). Нехай вершиною буде Aі попередня вершина у списку, Bа наступною вершиною у списку буде C. Тепер обчисліть знак перехресного добутку ABта AC.


Список літератури:


7
Це також пояснюється в en.wikipedia.org/wiki/Curve_orientation . Справа в тому, що знайдена точка повинна знаходитися на опуклому корпусі, і потрібно лише локально подивитися на одну точку на опуклому корпусі (та на її безпосередніх сусідів), щоб визначити орієнтацію всього багатокутника.
М Кац

1
Шокований і ошелешений цим не отримав більше відгуків. Для простих багатокутників ( що в багатьох полях є багатокутниками ) ця відповідь дає O(1)рішення. Усі інші відповіді дають O(n)рішення для nкількості полігонових точок. Для більш глибоких оптимізацій см Практичних міркувань підрозділу фантастичною Вікіпедії орієнтації Curve статті.
Сесіль Карі

8
Уточнення: це рішення полягаєO(1)лише втомувипадку, якщо або (A) цей многокутник випуклий (в цьому випадку будь-яка довільна вершина знаходиться на опуклому корпусі і, отже, достатня), або (B) ви вже знаєте вершину з найменшою координатою Y. Якщо це не так (тобто цей многокутник не випуклий і ви нічого не знаєте про нього), потрібенO(n)пошук. Оскільки підсумовування не потрібно, однак це все-таки різко швидше, ніж будь-яке інше рішення для простих багатокутників.
Сесіль Карі


1
@CecilCurry Я думаю, що ваш другий коментар пояснює, чому це не отримало більше відгуків. Він дає неправильні відповіді в певних сценаріях, не згадуючи про ці обмеження.
LarsH

23

Ось проста C # реалізація алгоритму на основі цієї відповіді .

Припустимо, що у нас є Vectorтип, що має Xта Yвластивості типу double.

public bool IsClockwise(IList<Vector> vertices)
{
    double sum = 0.0;
    for (int i = 0; i < vertices.Count; i++) {
        Vector v1 = vertices[i];
        Vector v2 = vertices[(i + 1) % vertices.Count];
        sum += (v2.X - v1.X) * (v2.Y + v1.Y);
    }
    return sum > 0.0;
}

%- оператор модуля чи залишку, що виконує операцію модуля, яка ( згідно Вікіпедії ) знаходить залишок після ділення одного числа на інше.


6

Почніть з однієї з вершин і обчисліть кут, підкреслений кожною стороною.

Перший і останній будуть нульовими (тому пропустіть ці); для решти синус кута буде заданий поперечним добутком нормувань на одиницю довжини (точка [n]-точка [0]) та (точка [n-1]-точка [0]).

Якщо сума значень додатна, то ваш многокутник намальовано в напрямку проти годинникової стрілки.


Бачачи, як перехресне виріб в основному зводиться до позитивного коефіцієнта масштабування, ніж синус кута, можливо, краще просто зробити поперечний продукт. Це буде швидше і менш складним.
ReaperUnreal

4

Для чого це варто, я використав цей міксин для обчислення замовлення намотування для додатків Google Maps API v3.

Код використовує побічний ефект полігонових областей: порядок обмоток вершин за годинниковою стрілкою дає позитивну площу, тоді як порядок намотування проти верхніх годин за тією ж вершиною створює ту саму площу, що і негативне значення. Код також використовує своєрідний приватний API в бібліотеці геометрій Google Maps. Я почував себе комфортно, використовуючи його - використовуйте на свій страх і ризик.

Використання зразка:

var myPolygon = new google.maps.Polygon({/*options*/});
var isCW = myPolygon.isPathClockwise();

Повний приклад з одиничними тестами @ http://jsfiddle.net/stevejansen/bq2ec/

/** Mixin to extend the behavior of the Google Maps JS API Polygon type
 *  to determine if a polygon path has clockwise of counter-clockwise winding order.
 *  
 *  Tested against v3.14 of the GMaps API.
 *
 *  @author  stevejansen_github@icloud.com
 *
 *  @license http://opensource.org/licenses/MIT
 *
 *  @version 1.0
 *
 *  @mixin
 *  
 *  @param {(number|Array|google.maps.MVCArray)} [path] - an optional polygon path; defaults to the first path of the polygon
 *  @returns {boolean} true if the path is clockwise; false if the path is counter-clockwise
 */
(function() {
  var category = 'google.maps.Polygon.isPathClockwise';
     // check that the GMaps API was already loaded
  if (null == google || null == google.maps || null == google.maps.Polygon) {
    console.error(category, 'Google Maps API not found');
    return;
  }
  if (typeof(google.maps.geometry.spherical.computeArea) !== 'function') {
    console.error(category, 'Google Maps geometry library not found');
    return;
  }

  if (typeof(google.maps.geometry.spherical.computeSignedArea) !== 'function') {
    console.error(category, 'Google Maps geometry library private function computeSignedArea() is missing; this may break this mixin');
  }

  function isPathClockwise(path) {
    var self = this,
        isCounterClockwise;

    if (null === path)
      throw new Error('Path is optional, but cannot be null');

    // default to the first path
    if (arguments.length === 0)
        path = self.getPath();

    // support for passing an index number to a path
    if (typeof(path) === 'number')
        path = self.getPaths().getAt(path);

    if (!path instanceof Array && !path instanceof google.maps.MVCArray)
      throw new Error('Path must be an Array or MVCArray');

    // negative polygon areas have counter-clockwise paths
    isCounterClockwise = (google.maps.geometry.spherical.computeSignedArea(path) < 0);

    return (!isCounterClockwise);
  }

  if (typeof(google.maps.Polygon.prototype.isPathClockwise) !== 'function') {
    google.maps.Polygon.prototype.isPathClockwise = isPathClockwise;
  }
})();

Спробувавши це, я отримую прямо протилежний результат, багатокутник, намальований за годинниковою стрілкою, дає негативну область, тоді як один намальований проти годинникової стрілки дає позитивний результат. У будь-якому випадку цей фрагмент все ще є супер корисним 5 років, дякую.
Камерон Робертс

@CameronRoberts Нормою (див. IETF, зокрема, для geoJson) є керування правим правилом. Я думаю, що Google скаржиться. У цьому випадку зовнішнє кільце повинно бути проти годинникової стрілки (даючи позитивну площу), а внутрішні кільця (отвори) обмотуються за годинниковою стрілкою (від'ємну площу потрібно видалити з основної площі).
allez l'OM

4

Впровадження відповіді Шона в JavaScript:

function calcArea(poly) {
    if(!poly || poly.length < 3) return null;
    let end = poly.length - 1;
    let sum = poly[end][0]*poly[0][1] - poly[0][0]*poly[end][1];
    for(let i=0; i<end; ++i) {
        const n=i+1;
        sum += poly[i][0]*poly[n][1] - poly[n][0]*poly[i][1];
    }
    return sum;
}

function isClockwise(poly) {
    return calcArea(poly) > 0;
}

let poly = [[352,168],[305,208],[312,256],[366,287],[434,248],[416,186]];

console.log(isClockwise(poly));

let poly2 = [[618,186],[650,170],[701,179],[716,207],[708,247],[666,259],[637,246],[615,219]];

console.log(isClockwise(poly2));

Досить впевнений, що це правильно. Здається, працює :-)

Ці багатокутники виглядають приблизно так, якщо вам цікаво:


3

Це реалізована функція для OpenLayers 2 . Умова наявності багатокутника за годинниковою стрілкою - area < 0це підтверджено цим посиланням .

function IsClockwise(feature)
{
    if(feature.geometry == null)
        return -1;

    var vertices = feature.geometry.getVertices();
    var area = 0;

    for (var i = 0; i < (vertices.length); i++) {
        j = (i + 1) % vertices.length;

        area += vertices[i].x * vertices[j].y;
        area -= vertices[j].x * vertices[i].y;
        // console.log(area);
    }

    return (area < 0);
}

Openlayers - це бібліотека керування картами на основі JavaScript, як googlemaps, і вона написана та використовується у openlayers 2.
MSS

Чи можете ви пояснити трохи, що робить ваш код, і чому ви це робите?
nbro

@nbro цей код реалізує відповідь lhf . Легко утримати частину, що не є OpenLayer, чистою функцією javascript, маючи вершини безпосередньо як параметр. Він працює добре і міг би бути адаптований до випадку з мультиполігоном .
allez l'OM

2

Якщо ви використовуєте Matlab, функція ispolycwповертає значення true, якщо вершини багатокутника розташовані в порядку годинникової стрілки.


1

Як також пояснено в цій статті Вікіпедії Крива орієнтація , задана 3 точками p, qі rна площині (тобто з координатами x і y) можна обчислити знак наступного визначника

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

Якщо визначник від'ємний (тобто Orient(p, q, r) < 0), то багатокутник орієнтований за годинниковою стрілкою (CW). Якщо визначник позитивний (тобто Orient(p, q, r) > 0), багатокутник орієнтується проти годинникової стрілки (CCW). Визначник дорівнює нулю (тобто Orient(p, q, r) == 0), якщо точки p, qі rє колінеарними .

У формулі вище ми додаємо ті, що знаходяться перед координатами p, q і rтому, що ми використовуємо однорідні координати .


@tibetty Чи можете ви пояснити, чому цей метод не працює в багатьох ситуаціях, якщо полігон увігнутий?
nbro

1
Будь ласка, подивіться останню таблицю у посиланні на елемент wiki у вашому дописі. Мені легко дати хибний приклад, але важко довести це.
тибетті

1
Будь ласка, подивіться останню таблицю у посиланні на елемент wiki у вашому дописі. Мені легко дати хибний приклад, але важко довести це.
тибетті

1
@tibetty правильний. Ви не можете просто взяти будь-які три точки вздовж полігону; ви можете бути або в опуклій, або увігнутій області цього багатокутника. Читаючи вікі уважно, треба провести три точки вздовж опуклого корпусу, який закриває багатокутник . З "практичних міркувань": "Не потрібно будувати опуклий корпус багатокутника, щоб знайти відповідну вершину. Загальним вибором є вершина багатокутника з найменшою координатою X. Якщо їх декілька, то одна з найменшою координатою Y вибрано. Це гарантовано вершина опуклого корпусу багатокутника ".
ToolmakerSteve

1
Отже , раніше відповідь lhf , яка схожа, і посилається на ту саму статтю у wiki, але вказує на таке. [Мабуть, не має значення, чи беруть найменший чи найбільший, x чи y, якщо хтось уникає посередині; ефективно один працює від одного краю обмежує рамки навколо полігону, щоб гарантувати в області увігнутої].
ToolmakerSteve

0

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


Щоправда, але ви неправильно розумієте поняття порядку намотування полігону (за годинниковою або проти годинникової стрілки). У повністю опуклому багатокутнику кут у всіх точках буде за годинниковою стрілкою або всі будуть проти годинникової стрілки [як у першому реченні]. У багатокутнику з увігнутими ділянками "печери" будуть знаходитись у протилежному напрямку, але багатокутник у цілому все ще має чітко визначений інтер'єр і вважається відповідно за годинниковою або проти годинникової стрілки. Див en.wikipedia.org/wiki / ...
ToolmakerSteve

0

Моє рішення C # / LINQ засноване на крос-рекомендаціях щодо продукту @charlesbretana нижче. Можна задати еталонну норму для обмотки. Він повинен працювати до тих пір, поки крива знаходиться в основному в площині, визначеній вектором вгору.

using System.Collections.Generic;
using System.Linq;
using System.Numerics;

namespace SolidworksAddinFramework.Geometry
{
    public static class PlanePolygon
    {
        /// <summary>
        /// Assumes that polygon is closed, ie first and last points are the same
        /// </summary>
       public static bool Orientation
           (this IEnumerable<Vector3> polygon, Vector3 up)
        {
            var sum = polygon
                .Buffer(2, 1) // from Interactive Extensions Nuget Pkg
                .Where(b => b.Count == 2)
                .Aggregate
                  ( Vector3.Zero
                  , (p, b) => p + Vector3.Cross(b[0], b[1])
                                  /b[0].Length()/b[1].Length());

            return Vector3.Dot(up, sum) > 0;

        } 

    }
}

з одиничним тестом

namespace SolidworksAddinFramework.Spec.Geometry
{
    public class PlanePolygonSpec
    {
        [Fact]
        public void OrientationShouldWork()
        {

            var points = Sequences.LinSpace(0, Math.PI*2, 100)
                .Select(t => new Vector3((float) Math.Cos(t), (float) Math.Sin(t), 0))
                .ToList();

            points.Orientation(Vector3.UnitZ).Should().BeTrue();
            points.Reverse();
            points.Orientation(Vector3.UnitZ).Should().BeFalse();



        } 
    }
}

0

Це моє рішення, використовуючи пояснення в інших відповідях:

def segments(poly):
    """A sequence of (x,y) numeric coordinates pairs """
    return zip(poly, poly[1:] + [poly[0]])

def check_clockwise(poly):
    clockwise = False
    if (sum(x0*y1 - x1*y0 for ((x0, y0), (x1, y1)) in segments(poly))) < 0:
        clockwise = not clockwise
    return clockwise

poly = [(2,2),(6,2),(6,6),(2,6)]
check_clockwise(poly)
False

poly = [(2, 6), (6, 6), (6, 2), (2, 2)]
check_clockwise(poly)
True

1
Чи можете ви вказати, на яких інших відповідях ґрунтується саме ця відповідь?
nbro

0

Набагато простіший метод обчислення, якщо ви вже знаєте точку всередині полігону :

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

  2. Додайте відому «внутрішню» точку та сформуйте трикутник.

  3. Обчисліть CW або CCW, як тут запропоновано, за допомогою цих трьох балів.


Можливо, це спрацює, якщо багатокутник повністю опуклий. Це, безумовно, не є надійним, якщо є якісь увігнуті регіони - його легко вибрати точку, яка знаходиться на «неправильній» стороні одного з країв печери, а потім з'єднати її з цим краєм. Отримаєте неправильну відповідь.
ToolmakerSteve

Він працює, навіть якщо багатокутник увігнутий. Точка повинна знаходитися всередині цього увігнутого багатокутника. Однак я не впевнений у складному багатокутнику (не
Венката Голі

"Він працює, навіть якщо полігон увігнутий." - Контрприклад: полі (0,0), (1,1), (0,2), (2,2), (2,0). Відрізок рядка (1,1), (0, 2). Якщо вибрати внутрішню точку в межах (1,1), (0,2), (1,2), щоб утворити трикутник -> (1,1), (0,2), (0,5,1,5)), ви отримаєте протилежне обмотці, ніж якщо ви обрали внутрішню точку в межах (0,0), (1,1), (1,0)> (1,1), (0,2), (0,5,0,5). Це обидва інтер'єри до оригінального багатокутника, але мають протилежні обмотки. Тому одна з них дає неправильну відповідь.
ToolmakerSteve

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

0

Після тестування декількох ненадійних реалізацій алгоритм, що дав задовільні результати щодо орієнтації CW / CCW поза коробкою, був алгоритмом, розміщеним ОП у цій темі ( shoelace_formula_3).

Як завжди, додатне число являє собою орієнтацію CW, тоді як негативне число CCW.


0

Ось швидке рішення 3.0, засноване на відповідях вище:

    for (i, point) in allPoints.enumerated() {
        let nextPoint = i == allPoints.count - 1 ? allPoints[0] : allPoints[i+1]
        signedArea += (point.x * nextPoint.y - nextPoint.x * point.y)
    }

    let clockwise  = signedArea < 0

0

Ще одне рішення для цього;

const isClockwise = (vertices=[]) => {
    const len = vertices.length;
    const sum = vertices.map(({x, y}, index) => {
        let nextIndex = index + 1;
        if (nextIndex === len) nextIndex = 0;

        return {
            x1: x,
            x2: vertices[nextIndex].x,
            y1: x,
            y2: vertices[nextIndex].x
        }
    }).map(({ x1, x2, y1, y2}) => ((x2 - x1) * (y1 + y2))).reduce((a, b) => a + b);

    if (sum > -1) return true;
    if (sum < 0) return false;
}

Прийміть усі вершини як такий масив;

const vertices = [{x: 5, y: 0}, {x: 6, y: 4}, {x: 4, y: 5}, {x: 1, y: 5}, {x: 1, y: 0}];
isClockwise(vertices);

0

Рішення для R для визначення напрямку та реверсу, якщо за годинниковою стрілкою (вважається необхідним для власних об'єктів):

coords <- cbind(x = c(5,6,4,1,1),y = c(0,4,5,5,0))
a <- numeric()
for (i in 1:dim(coords)[1]){
  #print(i)
  q <- i + 1
  if (i == (dim(coords)[1])) q <- 1
  out <- ((coords[q,1]) - (coords[i,1])) * ((coords[q,2]) + (coords[i,2]))
  a[q] <- out
  rm(q,out)
} #end i loop

rm(i)

a <- sum(a) #-ve is anti-clockwise

b <- cbind(x = rev(coords[,1]), y = rev(coords[,2]))

if (a>0) coords <- b #reverses coords if polygon not traced in anti-clockwise direction

0

Хоча ці відповіді правильні, вони більш математичні, ніж необхідні. Припустимо координати карти, де найпівнічніша точка - найвища точка на карті. Знайдіть саму північну точку, і якщо 2 бали зрівняються, то це сама північ, то сама східна (це точка, яку lhf ​​використовує у своїй відповіді). У ваших пунктах,

точка [0] = (5,0)

точка [1] = (6,4)

точка [2] = (4,5)

точка [3] = (1,5)

точка [4] = (1,0)

Якщо припустити, що P2 є найбільш північною, то східну точку або попередню, або наступну точку визначають за годинниковою стрілкою, CW або CCW. Оскільки найпівнічніша точка знаходиться на північній грані, якщо P1 (попередня) до P2 рухається на схід, то напрямок - CW. У цьому випадку він рухається на захід, тому напрямок - CCW, як говориться у прийнятій відповіді. Якщо попередня точка не має горизонтального руху, то така ж система застосовується до наступної точки, P3. Якщо P3 знаходиться на захід від P2, він є, тоді рух - CCW. Якщо рух P2 - P3 - схід, то в цьому випадку захід - рух CW. Припустимо, що nte, P2 у ваших даних, є найбільш північною, ніж східною точкою, а prv - попередньою точкою, P1 у ваших даних, а nxt - наступною точкою, P3 у ваших даних, а [0] - горизонтальною чи східною / захід, де захід менше, ніж схід, і [1] вертикальний.

if (nte[0] >= prv[0] && nxt[0] >= nte[0]) return(CW);
if (nte[0] <= prv[0] && nxt[0] <= nte[0]) return(CCW);
// Okay, it's not easy-peasy, so now, do the math
if (nte[0] * nxt[1] - nte[1] * nxt[0] - prv[0] * (nxt[1] - crt[1]) + prv[1] * (nxt[0] - nte[0]) >= 0) return(CCW); // For quadrant 3 return(CW)
return(CW) // For quadrant 3 return (CCW)

ІМХО, було б безпечніше дотримуватися основної математики, що відображається у відповіді lhf - дякую, що згадуєте про нього. Проблема в тому, щоб зменшити його до квадрантів, полягає в тому, що його досить багато, щоб довести, що ваша формула є правильною у всіх випадках. Ви правильно порахували "більше на захід"? У увігнутому багатокутнику, де і [1], і [3] є "захід і південь" [2]? Ви правильно поводилися з різною довжиною [1] та [3] у цій ситуації? Я поняття не маю, тоді як якщо я прямо обчислюю цей кут (або його визначник), я використовую добре відомі формули.
ToolmakerSteve

@ToolmakerSteve, якщо твердження завжди працюють, якщо 3 точки опуклі. Якщо заяви повернуться, то ви отримаєте правильну відповідь. Висловлювання if не повернеться, якщо форма увігнута і крайня. Ось тоді ви повинні займатися математикою. Більшість зображень мають один квадрант, тому ця частина проста. Більше 99% моїх викликів підпрограми обробляються операторами if.
VectorVortec

Це не стосується мого занепокоєння. Що це за формула? Чи це орієнтир орієнтації, як зазначено у посиланні на wiki з відповіді lhf? Якщо так, то скажіть так. Поясніть, що ви робите швидку перевірку, яка обробляє більшість випадків, щоб уникнути стандартної математики. Якщо це так, то ваша відповідь зараз має для мене сенс. (Minor ніт: було б легше читати , якщо ви використовували .xі .yзі структури, замість того , [0]і [1]я не знаю , що ваш код говорив, перший раз , коли я глянув на нього.)
ToolmakerSteve

Оскільки я не мав впевненості у вашому підході, я реалізував підхід lhf ; формула з його посилання. Повільна частина - це пошук відповідної вершини - O (N) пошуку. Після виявлення визначником є ​​операція O (1), використовуючи 6 множень з 5 додаваннями. Остання частина - це те, що ви оптимізували; але ви зробили це додаванням додаткових тестів. Я особисто не можу виправдати нестандартний підхід - потрібно було б перевірити правильність кожного кроку - Але дякую за цікавий аналіз квадрантів!
ToolmakerSteve

0

C # код для реалізації відповіді lhf :

// https://en.wikipedia.org/wiki/Curve_orientation#Orientation_of_a_simple_polygon
public static WindingOrder DetermineWindingOrder(IList<Vector2> vertices)
{
    int nVerts = vertices.Count;
    // If vertices duplicates first as last to represent closed polygon,
    // skip last.
    Vector2 lastV = vertices[nVerts - 1];
    if (lastV.Equals(vertices[0]))
        nVerts -= 1;
    int iMinVertex = FindCornerVertex(vertices);
    // Orientation matrix:
    //     [ 1  xa  ya ]
    // O = | 1  xb  yb |
    //     [ 1  xc  yc ]
    Vector2 a = vertices[WrapAt(iMinVertex - 1, nVerts)];
    Vector2 b = vertices[iMinVertex];
    Vector2 c = vertices[WrapAt(iMinVertex + 1, nVerts)];
    // determinant(O) = (xb*yc + xa*yb + ya*xc) - (ya*xb + yb*xc + xa*yc)
    double detOrient = (b.X * c.Y + a.X * b.Y + a.Y * c.X) - (a.Y * b.X + b.Y * c.X + a.X * c.Y);

    // TBD: check for "==0", in which case is not defined?
    // Can that happen?  Do we need to check other vertices / eliminate duplicate vertices?
    WindingOrder result = detOrient > 0
            ? WindingOrder.Clockwise
            : WindingOrder.CounterClockwise;
    return result;
}

public enum WindingOrder
{
    Clockwise,
    CounterClockwise
}

// Find vertex along one edge of bounding box.
// In this case, we find smallest y; in case of tie also smallest x.
private static int FindCornerVertex(IList<Vector2> vertices)
{
    int iMinVertex = -1;
    float minY = float.MaxValue;
    float minXAtMinY = float.MaxValue;
    for (int i = 0; i < vertices.Count; i++)
    {
        Vector2 vert = vertices[i];
        float y = vert.Y;
        if (y > minY)
            continue;
        if (y == minY)
            if (vert.X >= minXAtMinY)
                continue;

        // Minimum so far.
        iMinVertex = i;
        minY = y;
        minXAtMinY = vert.X;
    }

    return iMinVertex;
}

// Return value in (0..n-1).
// Works for i in (-n..+infinity).
// If need to allow more negative values, need more complex formula.
private static int WrapAt(int i, int n)
{
    // "+n": Moves (-n..) up to (0..).
    return (i + n) % n;
}

1
Це, мабуть, для Y-позитивних координат. Переверніть CW / CCW для стандартних координат.
Warwick Allison


-4

знайти центр маси цих точок.

припустимо, є лінії від цієї точки до ваших точок.

знайдіть кут між двома прямими для лінії0 пряма1

ніж це робити для line1 та line2

...

...

якщо цей кут монотонно збільшується, ніж проти годинникової стрілки,

інакше, якщо монотонно зменшується, це за годинниковою стрілкою

інше (це не монотонно)

ви не можете вирішити, так що це не мудро


під "центром маси" Я думаю, ви маєте на увазі "центроїд"?
Vicky Chijwani

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