다각형 점 목록이 시계 방향인지 확인하는 방법?


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

초승달 모양과 같은 볼록하지 않은 다각형의 경우 제안 된 방법 중 일부가 실패합니다. 다음은 볼록하지 않은 다각형에서 작동하는 간단한 것입니다 (그림 8과 같이 자체 교차 다각형에서도 작동하여 대부분 시계 방향 인지 여부를 알려줍니다 ).

모서리에 대한 합, (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)의 2 배입니다. x의 부호 규칙을 확인하십시오. 삼각형으로 이것을 시도하면 곧 작동하는 방법을 보게 될 것입니다.
Beta

72
사소한 경고 :이 답변은 일반적인 직교 좌표계를 가정합니다. 언급 할 가치가있는 이유는 HTML5 캔버스와 같은 일부 공통 컨텍스트가 반전 된 Y 축을 사용하기 때문입니다. 그런 다음 규칙을 뒤집어 야합니다. 면적이 음수 이면 곡선이 시계 방향입니다.
LarsH

8
@ Mr.Qbs : 그래서 내 방법은 작동하지만 중요한 부분건너 뛰면 작동하지 않습니다. 이것은 뉴스가 아닙니다.
Beta

11
@ Mr. Qbs : 항상 마지막 지점을 첫 번째 지점에 연결해야합니다. 0에서 N-1까지 번호가 매겨진 N 포인트가있는 경우 Sum( (x[(i+1) mod N] - x[i]) * (y[i] + y[(i+1) mod N]) )i = 0에서 N-1까지 계산해야합니다 . 즉, 인덱스를 사용해야합니다. Modulo N ( N ≡ 0) 수식은 닫힌 다각형 에만 적용됩니다 . 다각형에는 가상의 가장자리가 없습니다.
Olivier Jacot-Descombes

4
blog.element84.com/polygon-winding.html 은이 솔루션이 작동하는 이유를 간단한 영어로 설명합니다.
David Zorychta 1

49

외적은 두 벡터의 수직 다움의 정도를 측정한다. 다각형의 각 가장자리가 3 차원 xyz 공간의 xy 평면에있는 벡터라고 가정합니다. 그런 다음 두 개의 연속 모서리의 교차 곱은 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의 세그먼트 point0point1
edgeB사이 point1에이 point2
...
edgeE사이 point4point0.

그런 다음 정점 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). 교차 곱 개념이 3 차원 구조이기 때문에 세 번째 값이 0 인 좌표가 있으므로 교차 곱을 적용하기 위해 이러한 2 차원 벡터를 3 차원으로 확장합니다.

 i    j    k 
-4    0    0
 1    4    0    

모든 교차 곱이 곱해지는 두 벡터의 평면에 수직 인 벡터를 생성한다는 점을 감안할 때, 위의 행렬의 결정 요인에는 k(또는 z 축) 성분 만 있습니다. z 축 구성 요소
의 크기를 계산하는 공식 k
a1*b2 - a2*b1 = -4* 4 - 0* 1 = -16

이 값의 크기 ( -16)는 2 개의 원래 벡터 사이의 각도 사인 값에 2 개의 벡터 크기의 곱을 곱한 값입니다.
실제로 그 값에 대한 다른 공식은
A X B (Cross Product) = |A| * |B| * sin(AB)입니다.

따라서 각도 측정으로 돌아가려면이 값 ( -16)을 두 벡터 크기의 곱으로 나눠야합니다 .

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

따라서 sin (AB) = -16 / 16.4924=-.97014...

이것은 정점 뒤의 다음 세그먼트가 왼쪽 또는 오른쪽으로 구부러 졌는지 여부와 그 정도를 측정 한 것입니다. 아크 사인을 취할 필요가 없습니다. 우리가 관심을 가질 것은 그 규모뿐 아니라 물론 그 징후 (긍정적이든 부정적이든)입니다!

닫힌 패스 주위의 다른 4 개의 점마다이 작업을 수행하고 각 정점에서이 계산의 값을 더합니다.

최종 합이 양수이면 시계 방향, 음수, 시계 반대 방향으로 이동합니다.


3
실제로이 솔루션은 허용 된 솔루션과 다른 솔루션입니다. 그들이 동등한 지 아닌지는 내가 조사하고있는 질문이지만, 그렇지 않다고 생각합니다 ... 허용 된 답변은 다각형의 상단 가장자리 아래 영역과 아래 영역 사이의 차이를 취하여 다각형 영역을 계산합니다. 다각형의 아래쪽 가장자리 하나는 음수 (왼쪽에서 오른쪽으로 이동하는 것)이고 다른 하나는 음수입니다. 시계 방향으로 이송 할 때 상단 모서리가 왼쪽에서 오른쪽으로 이송되고 더 커지므로 전체가 양수입니다.
Charles Bretana

1
내 솔루션은 각 정점에서 가장자리 각도의 변화에 ​​대한 사인의 합을 측정합니다. 이것은 시계 방향으로 이동할 때 양수이고 시계 반대 방향으로 이동할 때 음수입니다.
Charles Bretana

2
볼록성을 가정하지 않는 한 (이 경우 하나의 꼭짓점 만 검사하면)이 접근법을 사용하면 arcsin을
가져와야합니다.

2
아크 신을 가져 가야합니다. 볼록하지 않은 임의의 비 다각형 다각형에서 시도해보십시오. arcsin을 사용하지 않으면 일부 다각형에 대해 테스트가 실패합니다.
Luke Hutchison

1
@CharlesBretana-Luke의 테스트를 실행하지 않았지만 그가 옳다고 생각합니다. 그것이 비선형 스케일 과 결합 된 합산 의 특성입니다 [arcsin 대 arcsin없이]. marsbear가 당신이 올바르게 거부했다고 제안한 것을 고려하십시오. 그는 당신에게 "그냥 세는 것"을 제안했고, 당신은 소수의 큰 값이 많은 작은 값보다 클 수 있다고 지적했습니다. 이제 각 값의 arcsin을 고려하십시오. 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 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에서는)이어야한다고 생각합니다. 반면에 선형 대수 수업을들은 지 10 년이 지났습니다.
Michael Eric Oberlin

아니. source 를 확인하면 수식이 실제로 마지막 항 (y1 및 x1)에서 첫 번째 점을 다시 참조한다는 것을 알 수 있습니다. (미안 해요, LaTeX를 잘 알고 아니에요,하지만 난 그들이 더 쉽게 읽을 수 있도록하기 위해 첨자를 포맷.)
숀 빈이

나는이 솔루션을 사용했으며 내 용도에 완벽하게 작동했습니다. 배열에서 미리 계획하고 여분의 두 벡터를 추가로 계획 할 수 있으면 배열의 끝에 첫 번째 벡터를 추가하여 비교 (또는 %)를 제거 할 수 있습니다. 이렇게하면 마지막 요소 (길이 -1 대신 길이 -2)를 제외한 모든 요소를 ​​반복하여 반복 할 수 있습니다.
Eric Fortier

2
@EricFortier-FWIW는 가능한 큰 배열의 크기를 조정하는 대신 각 반복마다 previousPoint다음 반복 과 같이 포인트를 저장하는 효율적인 대안입니다 . 루프를 시작하기 전에 previousPoint배열의 마지막 지점으로 설정하십시오. 트레이드 오프는 추가 로컬 변수 사본이지만 적은 어레이 액세스입니다. 그리고 가장 중요한 것은 입력 배열을 만질 필요가 없다는 것입니다.
ToolmakerSteve

2
@MichaelEricOberlin- 마지막 점에서 첫 번째 점까지 선분을 포함하여 다각형 을 닫는 데 필요합니다 . (A 정확한 계산이 동일 할 것이며, 그 위치에 상관없이 닫힌 다각형을 개시한다.)
ToolmakerSteve

36

y가 가장 작은 정점을 찾으십시오 (동점이있는 경우 가장 큰 x). 정점하자 A하고 목록에서 이전 정점 수 B와 목록의 다음 정점 수 C. 이제 계산 기호 의 십자가 제품을 AB하고 AC.


참고 문헌 :


7
이 내용은 en.wikipedia.org/wiki/Curve_orientation 에도 설명되어 있습니다. 있습니다. 요점은 발견 된 지점이 볼록 껍질에 있어야하며, 전체 다각형의 방향을 결정하기 위해 볼록 껍질 (및 그 바로 인접한 이웃)의 단일 지점을 로컬로 볼 필요가 있다는 것입니다.
M Katz

1
충격을 받고 경외심을 불러 일으켰다. 간단한 다각형 ( 일부 필드에서 대부분의 다각형 )의 경우이 답변으로 O(1)솔루션을 얻을 수 있습니다. 다른 모든 답변 은 다각형 포인트 수에 O(n)대한 솔루션을 산출 합니다 n. 더 심도있는 최적화를 위해서는 Wikipedia의 환상적인 커브 오리엔테이션 기사 의 실용적인 고려 사항 하위 섹션을 참조하십시오.
세실 카레

8
설명 : 이 솔루션은 (A) 이 다각형이 볼록한 경우 (이 경우 임의의 꼭지점이 볼록 껍질에 상주하므로 충분합니다) 또는 (B) Y 좌표가 가장 작은 꼭지점을 이미 알고있는O(1)경우에만 가능합니다. 그렇지 않은 경우 (즉,이 다각형은 볼록하지 않으며 이에 대해 아무 것도 모르는 경우)검색이 필요합니다. 그러나 합계가 필요하지 않기 때문에 간단한 다각형의 다른 솔루션보다 훨씬 빠릅니다. O(n)
세실 커리


1
@CecilCurry 귀하의 두 번째 의견에 이것이 더 많은지지를받지 못한 이유가 설명되어 있습니다. 특정 시나리오에서는 이러한 제한에 대한 언급없이 잘못된 답변을 제공합니다.
LarsH

23

이 답변을 기반으로 한 알고리즘의 간단한 C # 구현은 다음과 같습니다 .

우리가 가지고 있다고 가정 해 봅시다 Vector유형 XY유형의 속성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;
}

% 모듈로 연산을 수행하는 모듈로 또는 나머지 연산자입니다. Wikipedia에 따르면 을 다른 숫자로 나눈 후 나머지를 찾는 입니다.


6

꼭짓점 중 하나에서 시작하여 각 변의 각도를 계산하십시오.

첫 번째와 마지막은 0입니다 (그래서 건너 뛰십시오). 나머지의 경우, 각도의 사인은 정규화의 교차 곱에 의해 (point [n] -point [0]) 및 (point [n-1] -point [0])의 단위 길이로 주어집니다.

값의 합이 양수이면 다각형이 시계 반대 방향으로 그려집니다.


교차 곱이 기본적으로 각도의 사인에 양의 스케일링 계수를 곱한 방법으로 볼 때 교차 곱을 수행하는 것이 좋습니다. 더 빠르고 덜 복잡합니다.
ReaperUnreal

4

가치있는 점을 위해이 믹스 인을 사용하여 Google Maps API v3 앱의 와인딩 순서를 계산했습니다.

이 코드는 다각형 영역의 부작용을 활용합니다. 꼭지점의 시계 방향 권선 순서는 양수 영역을 생성하는 반면 동일한 꼭지점의 시계 반대 방향 권선 순서는 음수 값과 동일한 영역을 생성합니다. 이 코드는 또한 Google Maps 지오메트리 라이브러리에서 일종의 개인 API를 사용합니다. 나는 그것을 사용하는 것이 편안하다고 생각했습니다-당신의 책임하에 사용하십시오.

샘플 사용법 :

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 규범 (특히 geoJson에 대한 IETF 참조)은 '오른쪽 규칙'을 따르는 것입니다. 나는 구글이 불평하고 있다고 생각한다. 이 경우 바깥 쪽 고리는 시계 반대 방향 (양수 영역)이고 안쪽 고리 (구멍)는 시계 방향으로 감습니다 (음수 영역은 메인 영역에서 제거).
allez l' OM

4

JavaScript에서 Sean의 답변 구현 :

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는 googlemaps와 같은 자바 스크립트 기반지도 관리 라이브러리이며 openlayers 2에서 작성 및 사용됩니다.
MSS

코드가하는 일과 왜 그 일을하는지 설명 할 수 있습니까?
nbro

@nbro이 코드는 lhf 응답을 구현합니다 . 정점을 직접 매개 변수로 사용 하여 OpenLayer가 아닌 부분을 순수 자바 스크립트 함수로 유지하는 것이 쉽습니다 . 잘 작동하며 multiPolygon 의 경우에 적합 할 수 있습니다 .
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, qr있습니다 선상 .

상기 화학식에서는 좌표 앞에 덧붙이 것들 p, qr우리가 사용하기 때문에 균일 한 좌표 .


@tibetty 다각형이 오목하면이 방법이 여러 상황에서 작동하지 않는 이유를 설명 할 수 있습니까?
nbro

1
게시물의 위키 항목 참조에서 마지막 표를 확인하십시오. 내가 틀린 예를 제시하는 것은 쉽지만 그것을 증명하기는 어렵다.
tibetty

1
게시물의 위키 항목 참조에서 마지막 표를 확인하십시오. 내가 틀린 예를 제시하는 것은 쉽지만 그것을 증명하기는 어렵다.
tibetty

1
@tibetty가 정확합니다. 다각형을 따라 단순히 세 점을 취할 수는 없습니다. 해당 다각형의 볼록한 영역 또는 오목한 영역에있을 수 있습니다. 위키를주의 깊게 읽으면 다각형을 둘러싸는 볼록 껍질을 따라 세 점이 필요 합니다 . "실제 고려 사항"에서 : "적절한 정점을 찾기 위해 다각형의 볼록 껍질을 구성 할 필요는 없습니다. 일반적으로 X 좌표가 가장 작은 다각형의 정점을 선택할 수 있습니다. Y 좌표가 가장 작은 것을 선택합니다. 다각형의 볼록 껍질의 정점이 보장됩니다. "
ToolmakerSteve

1
따라서 lhf의 이전 답변 은 유사하며 동일한 위키 기사를 참조하지만 그러한 요점을 지정합니다. [중간에있는 것을 피하는 한, 가장 작은 것이든지 가장 큰 것이든지, 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. 여기 에 제시된 3 가지 점 으로 CW 또는 CCW를 계산 하십시오 .


어쩌면 이 작품 다각형 완전히 볼록 인 경우. 오목한 부분이있는 경우 확실히 신뢰할 수 없습니다. 동굴 가장자리 중 하나의 "잘못된"쪽에있는 점을 쉽게 찾아서 그 가장자리에 연결하십시오. 오답을 얻을 것이다.
ToolmakerSteve

다각형이 오목하더라도 작동합니다. 점은 오목한 다각형 안에 있어야합니다. 그러나 나는 복잡한 다각형에 대해 확신하지 못합니다 (테스트하지 않았습니다)
Venkata Goli

"다각형이 오목한 경우에도 작동합니다." -예 : poly (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의 서쪽이면, 그 움직임은 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)

IMHO, lhf의 답변에 표시된 기본 수학을 고수하는 것이 더 안전합니다 . 언급 해 주셔서 감사합니다. 사분면으로 감소시키는 문제는 모든 경우에 수식이 올바른지 증명 하기 위해 상당한 양의 작업이 필요 하다는 것 입니다. "더 서쪽"을 정확하게 계산 했습니까? 오목 다각형 모두 [1] [3] "서쪽과 남쪽"[2]의인가? 해당 상황에서 서로 다른 길이의 [1] 및 [3]을 올바르게 처리 했습니까? 나는 그 각도 (또는 그 결정 요인)를 직접 계산하면 잘 알려진 수식을 사용하고 있습니다.
ToolmakerSteve

3 점이 볼록한 경우 if 문이 항상 작동하는지 확인하십시오. if 문이 반환되면 정답을 얻을 수 있습니다. 모양이 오목하고 극단적 인 경우 if 문은 반환되지 않습니다. 그때 수학을해야합니다. 대부분의 이미지에는 하나의 사분면이 있으므로 부분이 쉽습니다. 내 서브 루틴 호출의 99 % 이상이 if 문으로 처리됩니다.
VectorVortec

그것은 나의 관심사를 해결하지 못한다. 그 공식은 무엇입니까? lhf의 답변에서 위키 링크에 제공된 방향이 결정적인가? 그렇다면 그렇게 말하십시오. 표준 수학을 피하기 위해 대부분의 경우를 처리하는 빠른 검사를 수행하고 있다고 설명합니다. 그렇다면, 당신의 대답은 이제 나에게 의미가 있습니다. (마이너의 니트 : 당신이 사용하는 경우 읽기 쉬울 것 .x.y구조체 대신에, [0]그리고 [1]내가 당신의 코드가 한 말 나는 그것을 보았다 처음 몰랐다..)
ToolmakerSteve

나는 당신의 접근 방식에 대해 확신이 없었기 때문에 lhf의 접근 방식을 구현했습니다 . 그의 링크에서 공식. 느린 부분은 찾아 적절한 정점 - O (N) 검색. 일단 결정되면 결정자는 5 곱하기와 6 곱하기를 사용하는 O (1) 연산입니다. 마지막 부분은 최적화 한 것입니다. 추가 if-test를 추가하면됩니다. 나는 비표준 접근법을 취하는 것을 개인적으로 정당화 할 수는 없습니다-각 단계가 올바른지 확인해야합니다-그러나 사분면에 대한 흥미로운 분석에 감사드립니다!
ToolmakerSteve

0

lhf의 답변 을 구현하는 C # 코드 :

// 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

다음은 이 답변을 기반으로 한 간단한 Python 3 구현입니다 (차례로 수락 된 답변에서 제안 된 솔루션을 기반으로 함 ).

def is_clockwise(points):
    # points is your list (or array) of 2d points.
    assert len(points) > 0
    s = 0.0
    for p1, p2 in zip(points, points[1:] + [points[0]]):
        s += (p2[0] - p1[0]) * (p2[1] + p1[1])
    return s > 0.0

-4

이 점들의 질량 중심을 찾으십시오.

이 지점에서 지점까지 선이 있다고 가정하십시오.

line0에 대한 두 줄 사이의 각도를 찾으십시오.

line1과 line2보다

...

...

이 각도가 시계 반대 방향보다 단조 증가하는 경우

그렇지 않으면 단조로 감소하면 시계 방향입니다

그렇지 않은 경우 (단조가 아님)

당신은 결정할 수 없으므로 현명하지 않습니다


"중심의 중심"이란 말은 "중심"을 의미한다고 생각합니까?
Vicky Chijwani

다각형이 완전히 볼록한 경우 작동합니다. 그러나 볼록하지 않은 다각형에 적합한 답을 대신 사용하는 것이 좋습니다.
ToolmakerSteve 2019
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.