Как определить, находится ли список точек многоугольника по часовой стрелке?


260

Имея список точек, как мне найти, если они расположены по часовой стрелке?

Например:

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

сказал бы, что это против часовой стрелки (или против часовой стрелки, для некоторых людей).


6
ПОЖАЛУЙСТА, ОБРАТИТЕ ВНИМАНИЕ: Принятый ответ и многие ответы после него требуют большого количества сложений и умножений (они основаны на расчетах площади, которые заканчиваются отрицательно или положительно; например, «формула шнурка»). Перед реализацией одного из них рассмотрим ответ 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 canvas, используют инвертированную ось 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. Т.е. должен брать индекс по модулю N ( N ≡ 0) Формула работает только для замкнутых многоугольников. Полигоны не имеют воображаемых ребер.
Оливье Жако-Дескомб

4
Этот blog.element84.com/polygon-winding.html объясняет простым английским языком, почему это решение работает.
Давид Зоричта

49

Крест продукт измеряет степень перпендикулярной-ность двух векторов. Представьте, что каждое ребро вашего многоугольника является вектором в плоскости xy трехмерного (3-D) пространства xyz. Тогда перекрестным произведением двух последовательных ребер является вектор в направлении 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[From point4to point0]
edgeA[From point0to `point1 '

Эти два ребра сами являются векторами, координаты x и y которых можно определить, вычитая координаты их начальной и конечной точек:

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

И крест произведение этих двух смежных ребер вычисляется с использованием определитель следующей матрицы, которая строится путем ввода координат двух векторов ниже символов , представляющих три оси координат ( i, j, & k). Третья (нулевая) -значная координата существует потому, что концепция кросс-произведения является трехмерной конструкцией, и поэтому мы расширяем эти 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 - хотя я не сдавал тест Люка, я считаю, что он прав. Такова природа суммирования в сочетании с нелинейной шкалой [без арксинуса с арксинусом]. Подумайте, что предложил Марсбер, что вы правильно отклонили. Он предложил вам «просто посчитать», и вы указали, что несколько больших значений могут перевесить большое количество небольших значений. Теперь рассмотрим arcsin каждого значения против нет. Разве это не тот случай, когда отказ от арксинуса дает неправильный вес каждому значению, следовательно, имеет тот же недостаток (хотя и гораздо меньше)?
ToolmakerSteve

47

Я думаю, это довольно старый вопрос, но я все равно собираюсь выкинуть другое решение, потому что оно простое и не математически интенсивное - оно просто использует базовую алгебру. Вычислите подписанную площадь многоугольника. Если оно отрицательное, точки расположены по часовой стрелке, если оно положительное, они против часовой стрелки. (Это очень похоже на решение Беты.)

Вычислите область со знаком: 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 н )

Или в псевдокоде:

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, но я отформатировал подписи, чтобы сделать их более читабельными.)
Шон Боб

Я использовал это решение, и оно отлично подошло для моего использования. Обратите внимание, что если вы можете планировать заранее и резервировать и дополнительно два вектора в своем массиве, вы можете избавиться от сравнения (или%), добавив первый вектор в конец массива. Таким образом, вы просто перебираете все элементы, кроме последнего (length-2 вместо length-1).
Эрик Фортье

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

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

38

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


Ссылки:


7
Это также объясняется в en.wikipedia.org/wiki/Curve_orientation . Дело в том, что найденная точка должна быть на выпуклой оболочке, и необходимо только локально смотреть на одну точку выпуклой оболочки (и ее непосредственных соседей), чтобы определить ориентацию всего многоугольника.
М Кац

1
Потрясенный и благоговейный, он не получил больше голосов. Для простых полигонов ( которых больше всего в некоторых полях ), этот ответ дает O(1)решение. Все остальные ответы дают O(n)решения для nчисла точек многоугольника. Для еще более глубокой оптимизации, см. Подраздел « Практические соображения » фантастической статьи о ориентации кривой «Википедия» .
Сесил Карри

8
Пояснение: это решение возможноO(1)только в том случае, если либо (A) этот многоугольник является выпуклым (в этом случае любая произвольная вершина находится на выпуклой оболочке и, следовательно, достаточно), либо (B) вы уже знаете вершину с наименьшей координатой Y. Если это не так (т. Е. Этот многоугольник невыпуклый, и вы ничего о нем не знаете), требуетсяO(n)поиск. Однако, поскольку суммирование не требуется, это все равно значительно быстрее, чем любое другое решение для простых многоугольников.
Сесил Карри


1
@CecilCurry Я думаю, что ваш второй комментарий объясняет, почему это не получило больше голосов. Это дает неправильные ответы в определенных сценариях, без какого-либо упоминания об этих ограничениях.
LarsH

24

Вот простая реализация алгоритма на 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. Я чувствовал себя комфортно, используя его - используйте на свой страх и риск.

Пример использования:

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

Не могли бы вы немного объяснить, что делает ваш код и почему вы это делаете?
18:25

@nbro этот код реализует ответ lhf . Легко сохранить не-OpenLayer часть в чистой функции javascript, имея вершины непосредственно в качестве параметра. Он хорошо работает и может быть адаптирован к случаю multiPolygon .
allez l'OM

2

Если вы используете Matlab, функция ispolycwвозвращает true, если вершины многоугольника расположены по часовой стрелке.


1

Кроме того, как описано в этой статье Википедии ориентация кривой , давалось 3 очка p, qи rна самолете (т.е. с координатами х и у), можно вычислить знак следующего определителя

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

Если определитель отрицателен (то есть Orient(p, q, r) < 0), то многоугольник ориентирован по часовой стрелке (CW). Если определитель является положительным (то есть Orient(p, q, r) > 0), многоугольник ориентирован против часовой стрелки (против часовой стрелки). Определитель равен нулю (т.е. Orient(p, q, r) == 0) , если точек p, qи rявляются коллинеарны .

В приведенной выше формуле, мы предварять те , перед координатами p, q и rпотому , что мы используем однородные координаты .


@tibetty Можете ли вы объяснить, почему этот метод не работает во многих ситуациях, если многоугольник вогнутый?
nbro

1
Пожалуйста, посмотрите на последнюю таблицу в ссылке на статью в вики вашего поста. Мне легко привести ложный пример, но трудно доказать это.
tibetty

1
Пожалуйста, посмотрите на последнюю таблицу в ссылке на статью в вики вашего поста. Мне легко привести ложный пример, но трудно доказать это.
tibetty

1
@tibetty правильно. Вы не можете просто взять любые три точки вдоль многоугольника; Вы можете находиться в выпуклой или вогнутой области этого многоугольника. Внимательно читая вики, нужно взять три точки вдоль выпуклой оболочки, которая окружает многоугольник . Из «практических соображений»: «Не нужно строить выпуклую оболочку многоугольника, чтобы найти подходящую вершину. Распространенным выбором является вершина многоугольника с наименьшей координатой X. Если их несколько, один с наименьшей координатой Y выбрана. Она гарантированно будет [a] вершиной выпуклой оболочки многоугольника. "
ToolmakerSteve

1
Следовательно , более ранний ответ lhf , который похож и ссылается на ту же статью вики, но указывает на такую ​​точку зрения. [Очевидно, не имеет значения, выбираете ли вы наименьшее или наибольшее, х или у, если только вы избегаете быть в середине; эффективно один работает от одного края ограничивающей рамки вокруг полигона, чтобы гарантировать в области вогнутой].
ToolmakerSteve

1

Код 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 для стандартных координат.
Уорик Эллисон

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, был опубликован OP в этой теме ( 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, это так, тогда движение - это против часовой стрелки. Если движение 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)

ИМХО, было бы безопаснее придерживаться фундаментальной математики, показанной в ответе ЛХФ - спасибо, что упомянули его. Проблема в том, чтобы свести его к квадрантам, состоит в том, что достаточно усилий, чтобы доказать, что ваша формула верна во всех случаях. Вы правильно рассчитали "больше на запад"? В вогнутом многоугольнике , где обе [1] и [3] « на западе и на юге» [2]? Правильно ли вы справились с разными длинами [1] и [3] в этой ситуации? Я понятия не имею, тогда как, если я непосредственно вычисляю этот угол (или его определитель), я использую известные формулы.
ToolmakerSteve

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

Это не решает мою проблему. Что это за формула? Это определитель ориентации, как указано в вики-ссылке из ответа lhf? Если так, то так и скажи. Объясните, что вы делаете быстрые проверки, которые обрабатывают большинство случаев, чтобы избежать стандартной математики. Если это так, то ваш ответ теперь имеет смысл для меня. (Minor гнида: было бы легче читать , если вы использовали .xи .yиз структуры, вместо того , [0]и [1]я не знаю , что ваш код говорил, первый раз , когда я взглянул на него.)
ToolmakerSteve

Поскольку у меня не было уверенности в вашем подходе, я реализовал подход lhf ; формула из его ссылки. Медленная часть - поиск подходящей вершины - O (N). Найденный определитель является операцией O (1) с использованием 6 умножений и 5 сложений. Эта последняя часть - то, что вы оптимизировали; но вы сделали это, добавив дополнительные if-тесты. Лично я не могу оправдать использование нестандартного подхода - нужно будет проверить правильность каждого шага - но спасибо за интересный анализ квадрантов!
ToolmakerSteve


-4

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

Предположим, есть линии от этой точки до ваших точек.

найти угол между двумя линиями для line0 line1

чем сделать это для line1 и line2

...

...

если этот угол монотонно увеличивается, чем против часовой стрелки,

иначе, если монотонно уменьшается, то по часовой стрелке

иначе (это не монотонно)

Вы не можете решить, так что это не мудро


под "центром масс" я думаю, что вы имеете в виду "центроид"?
Вики Chijwani

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