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라인은 약간의 설명이 필요할 수 있습니다. 내가 그 아는 것이 거의 모든 언어로는,이 함수는 라디안 각도를 반환합니다. 로 나누면 라디안에서 전체 원의 분수로 변환되고 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
mapfloat2키로? 이것은 심각하게 보이지 않습니다.
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.