วิธีที่ดีที่สุดในการแปลงเวกเตอร์ 2D เป็นทิศทางเข็มทิศ 8 ทิศทางที่ใกล้ที่สุดคืออะไร?


17

หากคุณมีเวกเตอร์ 2D แสดงเป็น x และ y วิธีที่ดีในการแปลงให้เป็นทิศทางเข็มทิศที่ใกล้ที่สุดคืออะไร?

เช่น

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

คุณต้องการเป็นสตริงหรือ enum หรือไม่? (ใช่มันเป็นเรื่องสำคัญ)
ฟิลิปป์

ทั้งสองอย่างเนื่องจากมันจะใช้ทั้งสองวิธี :) แม้ว่าฉันจะต้องเลือกฉันก็จะเอาสาย
izb

1
คุณกังวลเกี่ยวกับการแสดงเช่นกันหรือเกี่ยวกับความกระชับเท่านั้น?
Marcin Seredynski

2
var angle = Math.atan2 (y, x); return <Direction> Math.floor ((Math.round (มุม / (2 * Math.PI / 8)) + 8 + 2)% 8); ฉันใช้อันนี้
Kikaimaru

กระชับ: ทำเครื่องหมายด้วยความกะทัดรัดของการแสดงออกหรือคำสั่ง: ฟรีจากรายละเอียดทั้งหมดและรายละเอียดที่ฟุ่มเฟือย เพียงแค่ขว้างมันออกไปที่นั่น ...
3014

คำตอบ:


25

วิธีที่ง่ายที่สุดน่าจะเป็นมุมของเวกเตอร์ที่ใช้atan2()ตามที่ Tetrad แนะนำในการคอมเม้นต์จากนั้นทำการสเกลและปัดเศษมันเช่น (pseudocode):

// 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บรรทัดอาจต้องการคำอธิบายบางอย่าง ในทุกภาษาที่ฉันรู้ว่ามันมีอยู่atan2()ฟังก์ชั่นจะคืนค่ามุมเป็นเรเดียน การหารด้วย 2 πจะแปลงจากเรเดียนเป็นเศษส่วนของวงกลมเต็มแล้วคูณด้วย 8 จากนั้นแปลงเป็นแปดของวงกลมซึ่งเราจะปัดเศษเป็นจำนวนเต็มที่ใกล้เคียงที่สุด ในที่สุดเราลดมันแบบโมดูโล 8 เพื่อดูแลการล้อมรอบเพื่อให้ทั้ง 0 และ 8 ถูกแมปอย่างถูกต้องไปทางทิศตะวันออก

เหตุผลของการ + 8ที่ฉันข้ามไปด้านบนคือในบางภาษาatan2()อาจส่งคืนผลลัพธ์เชิงลบ (เช่นจาก - πถึง + πมากกว่าจาก 0 ถึง 2 π ) และตัวดำเนินการโมดูโล ( %) อาจถูกกำหนดให้ส่งคืนค่าลบสำหรับ ข้อโต้แย้งเชิงลบ (หรือพฤติกรรมของมันสำหรับข้อโต้แย้งเชิงลบอาจไม่ได้กำหนด) การเพิ่ม8(เช่นหนึ่งรอบเต็ม) ไปยังอินพุตก่อนการลดจะช่วยให้มั่นใจได้ว่าข้อโต้แย้งนั้นเป็นผลบวกเสมอโดยไม่ส่งผลต่อผลลัพธ์ในทางอื่น

หากภาษาของคุณไม่ได้ให้ฟังก์ชั่นการปัดเศษที่ใกล้เคียงที่สุดคุณสามารถใช้การแปลงจำนวนเต็มที่ถูกตัดทอนแทนและเพียงเพิ่ม 0.5 ลงในอาร์กิวเมนต์เช่นนี้:

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

โปรดทราบว่าในบางภาษาการแปลงแบบลอยเป็นจำนวนเต็มเริ่มต้นจะลบอินพุตที่เป็นศูนย์แทนที่จะเป็นลงซึ่งเป็นอีกสาเหตุหนึ่งที่ทำให้แน่ใจได้ว่าอินพุตนั้นเป็นค่าบวกเสมอ

แน่นอนคุณสามารถแทนที่เหตุการณ์ทั้งหมด8ในบรรทัดนั้นด้วยหมายเลขอื่น (เช่น 4 หรือ 16 หรือ 6 หรือ 12 ถ้าคุณอยู่บนแผนที่ฐานสิบหก) เพื่อแบ่งวงกลมออกเป็นหลายทิศทาง เพียงแค่ปรับ enum / array ตาม


หมายเหตุว่ามันมักจะไม่atan2(y,x) atan2(x,y)
sam hocevar

@Sam: อ๊ะแก้ไขแล้ว แน่นอนว่าatan2(x,y)จะใช้งานได้เช่นกันหากมีใครอยู่ในรายการส่วนหัวของเข็มทิศตามเข็มนาฬิกาเริ่มต้นจากทิศเหนือแทน
Ilmari Karonen

2
+1 ตามจริง ๆ ฉันคิดว่านี่เป็นคำตอบที่ตรงไปตรงมาและเข้มงวดที่สุด
sam hocevar

1
@TheLima:octant = round(8 * angle / 360 + 8) % 8
Ilmari Karonen

1
ทำทราบว่านี้ได้อย่างง่ายดายสามารถแปลงเป็นเข็มทิศ 4 ทิศทาง: quadtant = round(4 * angle / (2*PI) + 4) % 4และการใช้ { E, N, W, S }enum:
Spoike

10

คุณมี 8 ตัวเลือก (หรือ 16 หรือมากกว่านั้นหากคุณต้องการความแม่นยำที่ละเอียดยิ่งขึ้น)

ป้อนคำอธิบายรูปภาพที่นี่

ใช้atan2(y,x)เพื่อให้ได้มุมสำหรับเวกเตอร์ของคุณ

atan2() ทำงานในวิธีต่อไปนี้:

ป้อนคำอธิบายรูปภาพที่นี่

ดังนั้น 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]

ตอนนี้เพื่อเพิ่มความแม่นยำมากขึ้นเพียงแค่เพิ่มค่าให้กับทิศทาง enum

อัลกอริทึมทำงานโดยการตรวจสอบค่าที่เพิ่มขึ้นรอบ ๆ เข็มทิศเพื่อดูว่ามุมของเราวางอยู่ระหว่างจุดที่เราตรวจสอบล่าสุดกับตำแหน่งใหม่หรือไม่ นั่นเป็นเหตุผลที่เราเริ่มต้นด้วย -PI + increment / 2 เราต้องการออฟเซ็ตเช็คของเราเพื่อรวมพื้นที่ที่เท่ากันในแต่ละทิศทาง บางสิ่งเช่นนี้

ป้อนคำอธิบายรูปภาพที่นี่

ทิศตะวันตกแตกเป็นสองส่วนเนื่องจากค่าส่งคืนของatan2()ที่ทิศตะวันตกไม่ต่อเนื่อง


4
วิธีง่าย ๆ ในการ "แปลงพวกมันเป็นมุม" คือใช้งานatan2แม้ว่าโปรดจำไว้ว่า 0 องศาน่าจะเป็นทิศตะวันออกและไม่ใช่ทิศเหนือ
Tetrad

1
คุณไม่จำเป็นต้องangle >=ตรวจสอบในรหัสข้างต้น; ตัวอย่างเช่นถ้ามุมน้อยกว่า 45 แล้วทิศเหนือจะถูกส่งคืนแล้วดังนั้นคุณไม่จำเป็นต้องตรวจสอบว่ามุม> = 45 สำหรับมุมตะวันออก ในทำนองเดียวกันคุณไม่จำเป็นต้องมีการตรวจสอบใด ๆ ก่อนที่จะกลับไปทางตะวันตก - มันเป็นความเป็นไปได้เท่านั้นที่เหลืออยู่
MrKWatkins

4
ฉันจะไม่เรียกสิ่งนี้ว่ากระชับเพื่อให้ได้ทิศทาง ดูเหมือนว่าค่อนข้างจะค่อนข้างวุ่นวายและจะต้องมีการเปลี่ยนแปลงมากมายเพื่อปรับให้เข้ากับ "ความละเอียด" ที่แตกต่างกัน อย่าพูดถึงifคำแถลงมากมายหากคุณต้องการไป 16 ทิศทางหรือมากกว่านั้น
bummzack

2
ไม่จำเป็นต้องทำให้เวกเตอร์เป็นปกติ: มุมยังคงเหมือนเดิมกับการเปลี่ยนแปลงของขนาด
Kylotan

ขอบคุณ @bummzack ฉันได้แก้ไขโพสต์เพื่อให้กระชับและง่ายต่อการเพิ่มความแม่นยำเพียงเพิ่มค่า enum เพิ่มเติม
MichaelHouse

8

เมื่อใดก็ตามที่คุณกำลังจัดการกับเวกเตอร์ให้พิจารณาการดำเนินการเวกเตอร์ขั้นพื้นฐานแทนการแปลงเป็นมุมในเฟรมเฉพาะบางอย่าง

เมื่อระบุเวกเตอร์แบบสอบถามvและชุดเวกเตอร์หน่วยเวกเตอร์sที่จัดเรียงมากที่สุดคือเวกเตอร์s_iที่ขยายให้ใหญ่สุดdot(v,s_i)ที่เพิ่มนี่เป็นเพราะผลิตภัณฑ์จุดที่กำหนดความยาวคงที่สำหรับพารามิเตอร์มีค่าสูงสุดสำหรับเวกเตอร์ที่มีทิศทางเดียวกันและต่ำสุดสำหรับเวกเตอร์ที่มีทิศทางตรงข้ามเปลี่ยนไปอย่างราบรื่นในระหว่าง

สิ่งนี้ทำให้ภาพรวมมีมิติมากกว่าสองเล็กน้อยโดยทั่วไปสามารถขยายได้ตามทิศทางที่กำหนดเองและไม่เกิดปัญหาเฉพาะเฟรมเช่นการไล่ระดับสีไม่สิ้นสุด

การนำไปปฏิบัติอย่างชาญฉลาดสิ่งนี้จะทำให้ความสัมพันธ์จากเวกเตอร์ในแต่ละทิศทางสำคัญกับตัวระบุ (enum, string, อะไรก็ตามที่คุณต้องการ) แสดงถึงทิศทางนั้น จากนั้นคุณจะวนซ้ำชุดทิศทางของคุณค้นหาทิศทางที่มีผลิตภัณฑ์จุดสูงสุด

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
การใช้งานนี้สามารถเขียนได้แบบไร้สาขาและแบบเวกเตอร์โดยไม่มีปัญหามากเกินไป
Promit

1
A mapด้วยfloat2กุญแจ? มันไม่ได้ดูจริงจังมาก
sam hocevar

มันคือ "หลอกรหัส" ในลักษณะการสอน หากคุณต้องการการใช้งานที่ปรับให้เหมาะกับความตื่นตระหนก GDSE น่าจะไม่ใช่สถานที่สำหรับคัดลอกพาสต้าของคุณ สำหรับการใช้ float2 เป็นกุญแจสำคัญ float สามารถแทนตัวเลขทั้งหมดที่เราใช้ที่นี่และคุณสามารถสร้างตัวเปรียบเทียบที่ดีอย่างสมบูรณ์แบบสำหรับพวกเขา ปุ่มจุดลอยตัวจะไม่เหมาะสมก็ต่อเมื่อมีค่าพิเศษหรือคุณพยายามค้นหาผลลัพธ์ที่คำนวณ การวนซ้ำในลำดับการเชื่อมโยงนั้นใช้ได้ ฉันสามารถใช้การค้นหาแบบเชิงเส้นในอาร์เรย์ได้ แต่มันอาจจะไม่เป็นระเบียบ
Lars Viklund

3

วิธีหนึ่งที่ไม่ได้กล่าวถึงในที่นี้คือการรักษาเวกเตอร์ให้เป็นจำนวนเชิงซ้อน พวกเขาไม่ต้องการตรีโกณมิติและสามารถใช้งานได้ง่ายในการเพิ่มการหมุนการคูณหรือการปัดเศษโดยเฉพาะอย่างยิ่งเมื่อคุณมีส่วนหัวของคุณแสดงเป็นคู่ของตัวเลข

ในกรณีที่คุณไม่คุ้นเคยกับพวกเขาทิศทางจะถูกแสดงในรูปของ a + b (i) โดยมีองค์ประกอบที่แท้จริงและ 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 ดังนั้นทิศทางที่ใกล้ที่สุดคือทิศตะวันออก

ข้างต้นไม่จำเป็นต้องใช้โครงสร้างตัวเลขที่ซับซ้อน แต่การคิดถึงสิ่งเหล่านี้ทำให้การหาทิศทางสำคัญได้เร็วขึ้น คุณสามารถทำคณิตศาสตร์เวกเตอร์ได้ตามปกติถ้าคุณอยากได้หัวเรื่องสุทธิของเวกเตอร์สองตัวหรือมากกว่านั้น (ในจำนวนที่ซับซ้อนคุณจะไม่เพิ่ม แต่จะเพิ่มทวีคูณตามผลลัพธ์)


1
มันยอดเยี่ยม แต่ก็ทำผิดพลาดคล้าย ๆ กับที่ฉันทำในความพยายามของตัวเอง คำตอบนั้นใกล้ แต่ไม่ถูกต้อง มุมขอบเขตระหว่าง E และ NE เท่ากับ 22.5 องศา แต่สิ่งนี้จะตัดที่ 26.6 องศา
izb

Max(x, y)ควรMax(Abs(x, y))ทำงานให้กับจตุภาคลบ ฉันลองแล้วได้ผลลัพธ์เช่นเดียวกับ izb - นี่สลับทิศทางเข็มทิศในมุมที่ผิด ฉันเดาว่ามันจะเปลี่ยนเมื่อ header.y / Heading.x ข้าม 0.5 (ดังนั้นค่าที่ปัดเศษจาก 0 ถึง 1) ซึ่งก็คือ arctan (0.5) = 26.565 °
amitp

วิธีที่แตกต่างในการใช้จำนวนเชิงซ้อนที่นี่คือการสังเกตว่าการคูณจำนวนเชิงซ้อนเกี่ยวข้องกับการหมุน หากคุณสร้างจำนวนเชิงซ้อนที่แทน 1 / 8th ของการหมุนรอบวงกลมดังนั้นทุกครั้งที่คุณคูณด้วยมันคุณจะเลื่อนหนึ่ง octant คุณอาจถามว่า: เราสามารถนับจำนวนทวีคูณที่จะไปจากตะวันออกไปยังหัวเรื่องปัจจุบันได้หรือไม่? คำตอบ "กี่ครั้งที่เราต้องคูณด้วยนี้" คือลอการิทึม ถ้าคุณหาลอการิทึมหาจำนวนเชิงซ้อน ... มันใช้ 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 + เครื่องหมาย (x)) * (1-sign (y ^ 2)) - (2 + เครื่องหมาย (x)) * เครื่องหมาย (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)
theodore panagos

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

สิ่งนี้จะช่วยให้คุณมีค่าคงที่โดยใช้ bitfields:

// 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 ที่สอดคล้องกันแต่ฉันละเว้นจากการทำเช่นนั้นเพราะมันเป็นอันตรายต่อการอ่าน


2
ขออภัยที่ไม่ได้ให้คำตอบที่ฉันต้องการ ด้วยรหัสนั้นมันจะให้ "N" ถ้าเวกเตอร์อยู่เหนืออย่างแม่นยำและ NE หรือ NW ถ้า x เป็นค่าอื่น ๆ สิ่งที่ฉันต้องการคือทิศทางเข็มทิศที่ใกล้ที่สุดเช่นถ้าเวกเตอร์อยู่ใกล้กับ N มากกว่า NW ก็จะให้ N
izb

สิ่งนี้จะให้ทิศทางที่ใกล้ที่สุดหรือไม่ ดูเหมือนว่าเวกเตอร์ของ (0.00001,100) จะให้ทิศตะวันออกเฉียงเหนือแก่คุณ แก้ไข: คุณเอาชนะฉันกับมัน izb
CiscoIPPhone

คุณไม่ได้บอกว่าคุณต้องการทิศทางที่ใกล้เคียงที่สุด
ฟิลิปป์

1
ขออภัยฉันซ่อนไว้ในชื่อ ควรมีความชัดเจนในเนื้อหาของคำถาม
izb

1
สิ่งที่เกี่ยวกับการใช้บรรทัดฐานไม่มีที่สิ้นสุด? การหารด้วย max (abs (vector.components)) ให้เวกเตอร์ที่ได้มาตรฐานกับคุณตามบรรทัดฐานนั้น ตอนนี้คุณสามารถเขียนตารางตรวจสุขภาพขนาดเล็กตามif (x > 0.9) dir |= DIR_Eและส่วนที่เหลือทั้งหมด ควรดีกว่ารหัสต้นฉบับของ Phillipp และราคาถูกกว่าการใช้ L2 norm และ atan2 เล็กน้อย อาจจะ .. หรืออาจจะไม่
teodron
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.