2D 벡터를 가장 가까운 8 방향 나침반 방향으로 변환하는 가장 좋은 방법은 무엇입니까?


17

x와 y로 표현 된 2D 벡터가 있다면, 그것을 가장 가까운 나침반 방향으로 변환하는 좋은 방법은 무엇입니까?

예 :

x:+1,  y:+1 => NE
x:0,   y:+3 => N
x:+10, y:-2 => E   // closest compass direction

문자열이나 열거 형으로 사용 하시겠습니까? (예, 중요합니다)
— Philipp

두 가지 방법으로 사용되기 때문에 :) 선택해야한다면 문자열을 사용합니다.
— izb

1
성능뿐만 아니라 간결성 만 걱정하십니까?
— Marcin Seredynski

2
var angle = Math.atan2 (y, x); Return <Direction> Math.floor ((Math.round (angle / (2 * Math.PI / 8)) + 8 + 2) % 8); 나는 이것을 사용한다
— Kikaimaru

간결함 : 표현이나 진술의 간결함으로 표시 : 모든 정교함과 불필요한 세부 사항이 없음. 그냥 밖으로 던지고 ...
— Dialock

답변:


25

가장 간단한 방법은 atan2()Tetrad가 주석에서 제안한 것처럼을 사용하여 벡터의 각도를 얻은 다음 (가상 코드)와 같이 크기를 조정하고 둥글게하는 것입니다.

// enumerated counterclockwise, starting from east = 0:
enum compassDir {
    E = 0, NE = 1,
    N = 2, NW = 3,
    W = 4, SW = 5,
    S = 6, SE = 7
};

// for string conversion, if you can't just do e.g. dir.toString():
const string[8] headings = { "E", "NE", "N", "NW", "W", "SW", "S", "SE" };

// actual conversion code:
float angle = atan2( vector.y, vector.x );
int octant = round( 8 * angle / (2*PI) + 8 ) % 8;

compassDir dir = (compassDir) octant;  // typecast to enum: 0 -> E etc.
string dirStr = headings[octant];

octant = round( 8 * angle / (2*PI) + 8 ) % 8라인은 약간의 설명이 필요할 수 있습니다. 내가 그 아는 것이 거의 모든 언어로는,이 함수는 라디안 각도를 반환합니다. 2π 로 나누면 라디안에서 전체 원의 분수로 변환되고 8을 곱하면 8/8의 원으로 변환 한 다음 가장 가까운 정수로 반올림합니다. 마지막으로, 랩 어라운드를 처리하기 위해 모듈로 8을 줄이므로 0과 8이 모두 동쪽으로 올바르게 매핑됩니다.atan2()

이유 + 8I 과거 상기 스킵 일부 언어이다 atan2()부정적인 결과를 반환 할 수있다 (예들 - π 에 + π 대신 0~2에서보다 π )와 나머지 연산자 ( %마이너스 값을 반환하도록 정의 될 수있다) 부정 인수 (또는 부정 인수에 대한 동작은 정의되지 않았을 수 있음). 8축소 전에 입력에 (즉, 한 번의 전체 회전)을 추가 하면 다른 방식으로 결과에 영향을 미치지 않으면 서 인수가 항상 양수입니다.

언어에서 편리한 가장 가까운 함수로 반올림하지 않으면 잘리는 정수 변환을 대신 사용하여 다음과 같이 인수에 0.5를 추가하십시오.

int octant = int( 8 * angle / (2*PI) + 8.5 ) % 8;  // int() rounds down

일부 언어에서 기본 부동 소수점 정수 변환은 음수 입력을 아래로가 아니라 0으로 올림합니다. 이는 입력이 항상 양수인지 확인하는 또 다른 이유입니다.

물론, 8해당 줄에서 발생하는 모든 항목을 다른 숫자 (예 : 16 진지도에있는 경우 4 또는 16 또는 6 또는 12)로 대체 하여 원을 여러 방향으로 나눌 수 있습니다. 그에 따라 열거 형 / 배열을 조정하십시오.


일반적 atan2(y,x)으로 그렇지 않습니다 atan2(x,y).
— sam hocevar

@Sam : 죄송합니다. 물론 atan2(x,y)북쪽에서 시작하여 나침반 방향을 시계 방향으로 나열한 경우에도 작동합니다.
— 일 마리 카로 넨

2
그건 그렇고 +1, 나는 이것이 가장 간단하고 엄격한 대답이라고 생각합니다.
— 샘 호세 바

1
@TheLima :octant = round(8 * angle / 360 + 8) % 8
— 일 마리 카로 넨

1
이것은 4 방향 나침반로 쉽게 변환 할 수 있으며 quadtant = round(4 * angle / (2*PI) + 4) % 4enum :을 사용하십시오 { E, N, W, S }.
— Spoike

10

8 가지 옵션 (또는 더 정밀한 정밀도를 원하면 16 이상)이 있습니다.

enter image description here

atan2(y,x)벡터의 각도를 얻는 데 사용하십시오 .

atan2() 다음과 같은 방식으로 작동합니다.

enter image description here

따라서 x = 1, y = 0은 0이되고 x = -1, y = 0에서 불 연속적이며 π와 -π를 모두 포함합니다.

이제 우리 atan2()는 위의 나침반 의 출력과 일치 하도록 출력을 매핑하면 됩니다.

가장 간단한 구현은 각도의 증분 확인입니다. 정밀도를 높이기 위해 쉽게 수정할 수있는 의사 코드는 다음과 같습니다.

//start direction from the lowest value, in this case it's west with -π
enum direction {
west,
south,
east,
north
}

increment = (2PI)/direction.count
angle = atan2(y,x);
testangle = -PI + increment/2
index = 0

while angle > testangle
    index++
    if(index > direction.count - 1)
        return direction[0] //roll over
    testangle += increment


return direction[index]

정밀도를 높이려면 방향 열거 형에 값을 추가하면됩니다.

이 알고리즘은 나침반 주위의 증가하는 값을 확인하여 마지막으로 확인한 위치와 새 위치 사이에 각도가 있는지 확인합니다. 이것이 우리가 -PI + 증가 / 2에서 시작하는 이유입니다. 각 방향 주위에 동일한 공간을 포함하도록 검사를 오프셋하려고합니다. 이 같은:

enter image description here

West의 반환 값 atan2()이 불연속 이기 때문에 West는 두 개로 나뉩니다 .


4
"각도로 변환"하는 쉬운 방법은을 사용하는 atan2것이지만 0 도는 북쪽이 아니라 동쪽 일 것입니다.
— Tetrad

1
angle >=위의 코드 에서 점검 할 필요는 없습니다 . 예를 들어 각도가 45보다 작은 경우 북쪽이 이미 반환되었으므로 동쪽 확인에 대해 각도> = 45인지 확인할 필요가 없습니다. 마찬가지로 서쪽으로 돌아 오기 전에 수표를 전혀 필요로하지 않습니다. 유일한 가능성입니다.
— MrKWatkins

4
나는 이것을 지시를 얻는 간결한 방법이라고 부르지 않을 것이다. 다소 어색한 것으로 보이며이를 다른 "해상도"에 적용하려면 많은 변경이 필요합니다. if16 방향 이상으로 가고 싶다면 많은 말을하지 마십시오 .
— bummzack 2013

2
벡터를 정규화 할 필요가 없습니다. 크기의 변화에 ​​따라 각도가 동일하게 유지됩니다.
— Kylotan

@bummzack 덕분에 게시물을 편집하여 열거 형 값을 더 추가하여 정확성을 높이고 간결하게 만들었습니다.
— MichaelHouse

8

벡터를 다룰 때마다 특정 프레임에서 각도로 변환하는 대신 기본 벡터 작업을 고려하십시오.

쿼리 벡터 v와 단위 벡터 집합이 주어지면 s가장 정렬 된 벡터는 s_i최대화 하는 벡터 입니다 dot(v,s_i). 이는 매개 변수에 대해 고정 길이가 주어진 내적은 방향이 같은 벡터의 경우 최대 값을 가지며 반대 방향 인 벡터의 경우 최소값을 가지므로 매끄럽게 변경되기 때문입니다.

이것은 2보다 큰 차원으로 일반화되고, 임의의 방향으로 확장 가능하며 무한 그라데이션과 같은 프레임 특정 문제를 겪지 않습니다.

구현 측면에서, 이것은 각 기본 방향의 벡터에서 해당 방향을 나타내는 식별자 (열, 문자열, 필요한 것)와 연결되는 것으로 요약됩니다. 그런 다음 방향 설정을 반복하여 가장 높은 내적을 가진 방향을 찾습니다.

map<float2,Direction> candidates;
candidates[float2(1,0)] = E; candidates[float2(0,1)] = N; // etc.

for each (float2 dir in candidates)
{
    float goodness = dot(dir, v);
    if (goodness > bestResult)
    {
        bestResult = goodness;
        bestDir = candidates[dir];
    }    
}

2
이 구현은 너무 많은 문제없이 분기없이 작성되고 벡터화 될 수 있습니다.
— 승인

1
map와 float2키로? 이것은 심각하게 보이지 않습니다.
— sam hocevar

교훈적인 방식으로 "의사 코드"입니다. 패닉 최적화 구현을 원한다면 GDSE가 카피 파스타로 갈 곳이 아닐 것입니다. float2를 키로 사용하는 경우 float는 여기에서 사용하는 정수를 정확하게 나타낼 수 있으며 완벽하게 좋은 비교기를 만들 수 있습니다. 부동 소수점 키에는 특수 값이 포함되어 있거나 계산 된 결과를 찾으려고하는 경우에만 적합하지 않습니다. 연관 시퀀스를 반복하는 것이 좋습니다. 배열에서 선형 검색을 사용할 수 있었지만 무의미한 혼란이었습니다.
— Lars Viklund

3

여기에 언급되지 않은 한 가지 방법은 벡터를 복소수로 처리하는 것입니다. 삼각법이 필요하지 않으며, 특히 머리글이 숫자 쌍으로 표시되어 있기 때문에 회전을 추가, 곱하기 또는 반올림하는 데 매우 직관적 일 수 있습니다.

익숙하지 않은 경우 방향은 a + b (i) 형식으로 표현되고 b는 실제 구성 요소이고 b (i)는 가상입니다. X가 실제이고 Y가 상상 인 데카르트 평면을 상상한다면, 1은 동쪽 (오른쪽), 나는 북쪽입니다.

핵심 부분은 다음과 같습니다 . 8 개의 기본 방향은 실제 및 가상 구성 요소에 대해 숫자 1, -1 또는 0으로 만 표시됩니다. 따라서 X, Y 좌표를 비율로 줄이고 방향을 얻으려면 가장 가까운 정수로 반올림하면됩니다.

NW (-1 + i)       N (i)        NE (1 + i)
W  (-1)          Origin        E  (1)
SW (-1 - i)      S (-i)        SE (1 - i)

가장 가까운 값으로 향하는 대각 변환의 경우 X와 Y를 비례 적으로 줄이면 더 큰 값은 정확히 1 또는 -1입니다. 세트

// Some pseudocode

enum xDir { West = -1, Center = 0, East = 1 }
enum yDir { South = -1, Center = 0, North = 1 }

xDir GetXdirection(Vector2 heading)
{
    return round(heading.x / Max(heading.x, heading.y));
}

yDir GetYdirection(Vector2 heading)
{
    return round(heading.y / Max(heading.x, heading.y));
}

원래 (10, -2)의 두 구성 요소를 반올림하면 1 + 0 (i) 또는 1이됩니다. 가장 가까운 방향은 동쪽입니다.

위의 내용은 실제로 복잡한 숫자 구조를 사용할 필요는 없지만 그렇게 생각하면 8 가지 기본 방향을 더 빨리 찾을 수 있습니다. 둘 이상의 벡터의 순 표제를 얻으려면 일반적인 방법으로 벡터 수학을 수행 할 수 있습니다. (복소수이므로 더하기가 아니라 결과에 곱하기)


1
이것은 굉장하지만, 내가 시도한 것과 비슷한 실수를합니다. 답은 가깝지만 옳지 않습니다. E와 NE의 경계 각도는 22.5도이지만 26.6도에서 잘립니다.
— izb

Max(x, y)Max(Abs(x, y))마이너스 사분면에서 작동 해야합니다 . 나는 그것을 시도하고 izb와 같은 결과를 얻었다-이것은 나침반 각도를 잘못된 각도로 바꾼다. heading.y / heading.x가 0.5를 넘을 때 (그래서 반올림 값이 0에서 1로 바뀌면) arctan (0.5) = 26.565 ° 인 경우 전환 될 것이라고 생각합니다.
— amitp

여기에서 복소수를 사용하는 다른 방법은 복소수의 곱셈에 회전이 있다는 것을 관찰하는 것입니다. 원을 중심으로 한 회전의 1/8을 나타내는 복소수를 생성하면 그 값을 곱할 때마다 하나의 옥탄트가 이동합니다. 그래서 당신은 물을 수 있습니다 : 우리는 동쪽에서 현재 제목으로 얼마나 많은 곱셈이 걸 렸는지 계산할 수 있습니까? "여기에 몇 번 곱해야하는지"에 대한 답은 대수 입니다. 복소수에 대한 로그를 검색하면 atan2를 사용합니다. 그래서 이것은 Ilmari의 대답과 같습니다.
— amitp

-2

이것은 작동하는 것 같습니다 :

public class So49290 {
    int piece(int x,int y) {
        double angle=Math.atan2(y,x);
        if(angle<0) angle+=2*Math.PI;
        int piece=(int)Math.round(n*angle/(2*Math.PI));
        if(piece==n)
            piece=0;
        return piece;
    }
    void run(int x,int y) {
        System.out.println("("+x+","+y+") is "+s[piece(x,y)]);
    }
    public static void main(String[] args) {
        So49290 so=new So49290();
        so.run(1,0);
        so.run(1,1);
        so.run(0,1);
        so.run(-1,1);
        so.run(-1,0);
        so.run(-1,-1);
        so.run(0,-1);
        so.run(1,-1);
    }
    int n=8;
    static final String[] s=new String[] {"e","ne","n","nw","w","sw","s","se"};
}

이것이 왜 투표되지 않습니까?
— Ray Tayek

코드 뒤에 설명이 없기 때문에 가능성이 높습니다. 이것이 왜 솔루션이며 어떻게 작동합니까?
— Vaillancourt

당신은 그것을 실행 했습니까?
— Ray Tayek

아니요, 수업 이름이 주어 졌다고 가정했고 효과가있었습니다. 그리고 그것은 훌륭합니다. 그러나 당신은 사람들이 투표 한 이유를 물었고 나는 대답했습니다. 나는 그것이 작동하지 않았다는 것을 결코 암시하지 않았다 :)
— Vaillancourt

-2

E = 0, NE = 1, N = 2, NW = 3, W = 4, SW = 5, S = 6, SE = 7

f (x, y) = mod ((4-2 * (1 + sign (x)) * (1-sign (y ^ 2))-(2 + sign (x)) * sign (y)

    -(1+sign(abs(sign(x*y)*atan((abs(x)-abs(y))/(abs(x)+abs(y))))

    -pi()/(8+10^-15)))/2*sign((x^2-y^2)*(x*y))),8)

현재로서는 이것은 말이되지 않는 많은 문자 일뿐입니다. 왜 이것이 해결책이 될까요? 어떻게 작동합니까?
— Vaillancourt

나는 jn excel을 쓰고 완벽하게 작동하는 공식을 작성합니다.
— theodore panagos

= MOD ((4-2 * (1 + SIGN (X1)) * (1-SIGN (Y1 ^ 2))-(2 + SIGN (X1)) * SIGN (Y1)-(1 + SIGN (ABS (SIGN (X1 * Y1) * ATAN ((ABS (X1) -ABS (Y1)) / (ABS (X1) + ABS (Y1))))-PI () / (8 + 10 ^ -15))) / 2 * SIGN ((X1 ^ 2-Y1 ^ 2) * (X1 * Y1))), 8)
— 시어 도어 파나 고스

-4

문자열을 원할 때 :

h_axis = ""
v_axis = ""

if (x > 0) h_axis = "E"    
if (x < 0) h_axis = "W"    
if (y > 0) v_axis = "S"    
if (y < 0) v_axis = "N"

return v_axis.append_string(h_axis)

비트 필드를 사용하여 상수를 제공합니다.

// main direction constants
DIR_E = 0x1
DIR_W = 0x2
DIR_S = 0x4
DIR_N = 0x8
// mixed direction constants
DIR_NW = DIR_N | DIR_W    
DIR_SW = DIR_S | DIR_W
DIR_NE = DIR_N | DIR_E
DIR_SE = DIR_S | DIR_E

// calculating the direction
dir = 0x0

if (x > 0) dir |= DIR_E 
if (x < 0) dir |= DIR_W    
if (y > 0) dir |= DIR_S    
if (y < 0) dir |= DIR_N

return dir

약간의 성능 향상은 <-checks를 해당 >-checks 의 else-branch에 넣는 것이지만 가독성에 해를 끼치기 때문에 그렇게하지 않았습니다.


2
죄송하지만, 내가 찾고있는 답변을 정확하게 제공하지는 않습니다. 이 코드를 사용하면 벡터가 정확히 북쪽에 있으면 "N"만, x가 다른 값이면 NE 또는 NW가됩니다. 내가 필요한 것은 가장 가까운 나침반 방향입니다. 예를 들어 벡터가 NW보다 N에 더 가까운 경우 N을 산출합니다.
— izb

이것이 실제로 가장 가까운 방향을 제시합니까? (0.00001,100)의 벡터는 북동쪽을 줄 것입니다. 편집 : 당신은 izb에 나를 이길.
— CiscoIPPhone

가장 가까운 방향을 원한다고 말하지 않았습니다.
— Philipp

1
죄송합니다. 제목에 숨겨져 있습니다. 질문 본문에서 더 명확해야 함
— izb

1
무한 규범을 사용하는 것은 어떻습니까? max (abs (vector.components))로 나누면 해당 표준에 대해 정규화 된 벡터가 제공됩니다. 이제 if (x > 0.9) dir |= DIR_E나머지를 기반으로 작은 점검 테이블을 작성할 수 있습니다. L2 표준 및 atan2를 사용하는 것보다 Phillipp의 원래 코드보다 낫고 약간 저렴해야합니다. 아마 .. 아니면 아닐 수도 있습니다.
— teodron
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.