2 डी वेक्टर को निकटतम 8-तरफ़ा कम्पास दिशा में बदलने का सबसे अच्छा तरीका क्या है?


17

यदि आपके पास एक 2D वेक्टर है जिसे x और y के रूप में व्यक्त किया गया है, तो इसे निकटतम कम्पास दिशा में बदलने का एक अच्छा तरीका क्या है?

जैसे

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

क्या आप इसे एक स्ट्रिंग या एक एनम के रूप में चाहते हैं? (हाँ, यह मायने रखता है)
फिलिप

या तो, क्योंकि यह दोनों तरह से इस्तेमाल किया जाएगा :) हालांकि अगर मुझे चुनना था, तो मैं एक स्ट्रिंग लूंगा।
izb

1
क्या आप प्रदर्शन के बारे में चिंतित हैं, या केवल संक्षिप्तता के बारे में?
Marcin Seredynski

2
var कोण = Math.atan2 (y, x); वापसी मैं इस एक का उपयोग करता हूं
काइकिमारू

संक्षिप्त: अभिव्यक्ति या कथन की संक्षिप्तता द्वारा चिह्नित: सभी विस्तार और शानदार विस्तार से मुक्त। बस वहाँ से बाहर फेंकने ...
डायलॉक

जवाबों:


25

सबसे सरल तरीका संभवतः वेक्टर के कोण का उपयोग करना है atan2(), जैसा कि टेट्रड टिप्पणियों में बताता है, और फिर पैमाने और इसे गोल करना, जैसे (स्यूडोकोड):

// 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 से विभाजित πatan2() यह धर्मान्तरित एक पूर्ण चक्र के अंशों को रेडियंस से, और 8 से गुणा फिर किसी मंडली के आठवें भाग, में बदल देता है जो हम तो निकटतम पूर्णांक तक दौर। अंत में, हम रैप-अराउंड की देखभाल के लिए इसे मोडुलो 8 को कम कर देते हैं, ताकि 0 और 8 दोनों पूर्व में सही ढंग से मैप किए जा सकें।

इसका कारण + 8, जिसके बारे में मैंने ऊपर बताया है, वह यह है कि कुछ भाषाओं atan2()में नकारात्मक परिणाम (यानी - π से + π के बजाय 0 से 2 which ) %तक लौट सकते हैं और modulo ऑपरेटर ( ) को नकारात्मक मानों को लौटाने के लिए परिभाषित किया जा सकता है। नकारात्मक तर्क (या नकारात्मक तर्क के लिए इसका व्यवहार अपरिभाषित हो सकता है)। 8कटौती से पहले इनपुट में (यानी एक पूर्ण मोड़) जोड़ना सुनिश्चित करता है कि तर्क हमेशा सकारात्मक होते हैं, परिणाम को किसी अन्य तरीके से प्रभावित किए बिना।

यदि आपकी भाषा एक सुविधाजनक दौर-से-निकटतम फ़ंक्शन प्रदान करने के लिए नहीं होती है, तो आप इसके बजाय एक पूर्णांक पूर्णांक रूपांतरण का उपयोग कर सकते हैं और इस तरह केवल तर्क में 0.5 जोड़ सकते हैं:

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

ध्यान दें, कुछ भाषाओं में, डिफ़ॉल्ट फ्लोट-टू-पूर्णांक रूपांतरण राउंड नकारात्मक इनपुट को नीचे की बजाय शून्य की ओर बढ़ाता है, जो यह सुनिश्चित करने का एक और कारण है कि इनपुट हमेशा सकारात्मक होता है।

बेशक, आप 8उस लाइन के सभी घटनाओं को किसी अन्य संख्या (जैसे 4 या 16, या यहां तक ​​कि 6 या 12 यदि आप एक हेक्स मानचित्र पर हैं) से सर्कल को उस कई दिशाओं में विभाजित करने के लिए बदल सकते हैं। बस तदनुसार एनुम / सरणी समायोजित करें।


ध्यान दें कि यह आमतौर पर है atan2(y,x), नहीं atan2(x,y)
सैम होसेवर

@ शम: उफ़, सही। बेशक, atan2(x,y)यह भी काम करेगा, अगर कोई बस उत्तर की ओर से शुरू होने वाले दक्षिणावर्त क्रम में कम्पास हेडिंग सूचीबद्ध करता है।
इल्मरी करोनें

2
वैसे, मुझे लगता है कि यह सबसे सीधा और कठोर जवाब है।
सैम होसेवर

1
@ द लीला:octant = round(8 * angle / 360 + 8) % 8
इल्मरी करोनें

1
ध्यान दें कि यह आसानी से 4-तरफ़ा कम्पास में परिवर्तित हो सकता है: quadtant = round(4 * angle / (2*PI) + 4) % 4और एनम का उपयोग करके { E, N, W, S }:।
Spoike

10

आपके पास 8 विकल्प हैं (या 16 या अधिक यदि आप भी महीन परिशुद्धता चाहते हैं)।

यहां छवि विवरण दर्ज करें

atan2(y,x)अपने वेक्टर के लिए कोण प्राप्त करने के लिए उपयोग करें ।

atan2() निम्नलिखित तरीके से काम करता है:

यहां छवि विवरण दर्ज करें

तो x = 1, y = 0 का परिणाम 0 होगा, और यह x = -1, y = 0 पर बंद होगा, जिसमें π और-y दोनों होंगे।

अब हमें बस 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 से शुरू करते हैं। हम प्रत्येक दिशा में समान स्थान शामिल करने के लिए अपने चेक को ऑफ़सेट करना चाहते हैं। कुछ इस तरह:

यहां छवि विवरण दर्ज करें

पश्चिम में दो का टूटना है क्योंकि पश्चिम में वापसी के मूल्य atan2()बंद हैं।


4
"उन्हें एक कोण में परिवर्तित करने" का एक आसान तरीका उपयोग करना है atan2, हालांकि ध्यान रखें कि 0 डिग्री संभवतः पूर्व में होगी और उत्तर में नहीं।
टेट्राद

1
आपको angle >=ऊपर दिए गए कोड में चेक की आवश्यकता नहीं है; उदाहरण के लिए यदि कोण 45 से कम है तो उत्तर को पहले ही वापस कर दिया जाएगा इसलिए आपको पूर्व चेक के लिए कोण> = 45 की जाँच करने की आवश्यकता नहीं है। इसी तरह आपको पश्चिम लौटने से पहले किसी भी चेक की आवश्यकता नहीं है - यह एकमात्र संभावना है जो शेष है।
MrKWatkins

4
मैं इसे दिशा पाने का संक्षिप्त तरीका नहीं कहूंगा। यह बल्कि भद्दा लगता है और इसे विभिन्न "प्रस्तावों" के लिए अनुकूलित करने के लिए बहुत सारे बदलावों की आवश्यकता होगी। ifयदि आप 16 दिशाओं या अधिक के लिए जाना चाहते हैं, तो एक टन के बयान की बात नहीं करें ।
बंमज़ैक

2
वेक्टर को सामान्य करने की आवश्यकता नहीं है: कोण परिमाण में परिवर्तन पर समान रहता है।
काइलोटन

धन्यवाद @bummzack, मैंने पोस्ट को अधिक संक्षिप्त और आसान बनाने के लिए एडिट किया है ताकि अधिक एनम वैल्यूज को जोड़कर परिशुद्धता को बढ़ाया जा सके।
MichaelHouse

8

जब भी आप वैक्टर के साथ काम कर रहे हों, तो किसी विशेष फ्रेम में कोणों में परिवर्तित करने के बजाय मौलिक वेक्टर संचालन पर विचार करें।

क्वेरी वेक्टर vऔर यूनिट वैक्टर के एक सेट को देखते हुए s, सबसे अधिक संरेखित वेक्टर वह वेक्टर है s_iजो अधिकतम होता है dot(v,s_i)। इसका कारण यह है कि मापदंडों के लिए निर्धारित लंबाई दिए गए डॉट उत्पाद में एक ही दिशा वाले वैक्टर के लिए अधिकतम है और विपरीत दिशाओं के साथ वैक्टर के लिए न्यूनतम है, सुचारू रूप से इनबेटीइन बदलते हुए।

यह सामान्य रूप से दो से अधिक आयामों में सामान्यीकरण करता है, मनमाने दिशा-निर्देशों के साथ विस्तार योग्य है और यह फ्रेम-विशिष्ट समस्याओं जैसे अनंत ग्रेडिएंट्स से ग्रस्त नहीं है।

कार्यान्वयन-वार, यह प्रत्येक कार्डिनल दिशा में एक वेक्टर से एक पहचानकर्ता (एनम, स्ट्रिंग, जो भी आपको ज़रूरत है) को उस दिशा का प्रतिनिधित्व करने के लिए उकसाएगा। फिर आप अपने दिशा-निर्देशों के सेट पर लूप करेंगे, जिसमें सबसे अधिक डॉट उत्पाद होगा।

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
एक mapसाथ float2कुंजी के रूप में? यह बहुत गंभीर नहीं लगता है।
sam hocevar

यह एक '' छद्म-कोड '' है। यदि आप घबराहट-अनुकूलित कार्यान्वयन चाहते हैं, तो GDSE आपके कॉपी-पास्ता के लिए जाने की जगह नहीं है। एक कुंजी के रूप में फ्लोट 2 का उपयोग करने के लिए, एक फ्लोट वास्तव में हमारे द्वारा उपयोग किए जाने वाले पूरे नंबरों का प्रतिनिधित्व कर सकता है, और आप उनके लिए पूरी तरह से अच्छा तुलनित्र बना सकते हैं। फ़्लोटिंग पॉइंट कुंजियाँ केवल अनुपयुक्त होती हैं यदि उनमें विशेष मान होते हैं या आप गणना किए गए परिणामों को देखने का प्रयास करते हैं। एक साहचर्य क्रम पर फेरबदल ठीक है। मैं एक सरणी में एक रैखिक खोज का उपयोग कर सकता था, निश्चित, लेकिन यह सिर्फ व्यर्थ अव्यवस्था होगी।
लार्स विकलंड

3

एक तरीका जिसका उल्लेख यहाँ नहीं किया गया है वह वैक्टरों को जटिल संख्याओं के रूप में मान रहा है। उन्हें त्रिकोणमिति की आवश्यकता नहीं है और घूर्णन को जोड़ने, गुणा करने या गोल करने के लिए बहुत सहज हो सकते हैं, खासकर जब से आप पहले से ही अपने शीर्षकों को संख्याओं के जोड़े के रूप में प्रतिनिधित्व करते हैं।

यदि आप उनसे परिचित नहीं हैं, तो निर्देश वास्तविक घटक होने के साथ + b (i) के रूप में व्यक्त किए जाते हैं और b (i) काल्पनिक है। यदि आप कार्टेसियन प्लेन की कल्पना करते हैं कि X वास्तविक है और Y काल्पनिक है, तो 1 पूर्व (दाएं) होगा, मैं उत्तर में होगा।

यहाँ मुख्य भाग है: 8 कार्डिनल दिशाओं को उनके वास्तविक और काल्पनिक घटकों के लिए विशेष रूप से संख्या 1, -1 या 0 के साथ दर्शाया गया है। तो आपको बस इतना करना है कि अपने एक्स को कम करना है, वाई एक अनुपात के रूप में समन्वय करता है और दिशा पाने के लिए निकटतम पूरी संख्या में दोनों को गोल करता है।

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 के समान परिणाम मिला - यह गलत दिशाओं पर कम्पास दिशाओं को स्विच करता है। मुझे लगता है कि जब हेडिंग होगी तो यह स्विच करेगा। हेडिंग / हेडिंग। एक्स 0.5 को पार करता है (इसलिए 0 से 1 तक गोल वैल्यू स्विच होता है), जो आर्कटिक (0.5) = 26.565 ° है।
एमटीपी

यहां जटिल संख्याओं का उपयोग करने का एक अलग तरीका यह है कि जटिल संख्याओं के गुणन में एक रोटेशन शामिल है। यदि आप एक जटिल संख्या का निर्माण करते हैं जो एक सर्कल के चारों ओर 1/8 वीं का प्रतिनिधित्व करता है, तो हर बार जब आप इसे गुणा करते हैं, तो आप एक ऑक्टेंट को स्थानांतरित करते हैं। तो आप पूछ सकते हैं: क्या हम गिन सकते हैं कि पूर्व से वर्तमान हेडिंग तक जाने के लिए कितनी गुणाएँ हुईं? "इसके द्वारा हमें कितनी बार गुणा करना है" का उत्तर एक लघुगणक है । यदि आप जटिल संख्याओं के लिए लघुगणक देखते हैं ... तो यह atan2 का उपयोग करता है। इसलिए यह इल्मरी के जवाब के बराबर है।
अमितप

-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"};
}

इसे क्यों वोट दिया जा रहा है?
रे तैक

सबसे अधिक संभावना है क्योंकि आपके कोड के पीछे कोई स्पष्टीकरण नहीं है। इसका समाधान क्यों है और यह कैसे काम करता है?
Vaillancourt

क्या आपने इसे चलाया?
रे तैक

नहीं, और कक्षा का नाम दिया, मैंने मान लिया कि आपने किया और यह काम किया। और यह बहुत अच्छा है। लेकिन आपने पूछा कि लोगों ने वोट क्यों दिया और मैंने जवाब दिया; मैं गर्भित कभी नहीं है कि यह काम नहीं किया :)
Vaillancourt

-2

ई = 0, पूर्वोत्तर = 1, एन = 2, NW = 3, डब्ल्यू = 4, दप = 5, एस = 6, एसई = 7

f (x, y) = mod ((4-2 * (1 + साइन (x)) * (1-साइन (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 लिखा है और पूरी तरह से काम कर रहा है।
थियोडोर पैनागोस

= MOD ((4-2 * (1 + SIGN (X1)) * * (1-SIGN (Y1 ^ 2)) - (2 + SIGN (X1)) * SIGN (Y1) - (1 + SIGN (ABS) (SIGN (एक्स 1 * Y1) * ATAN ((ABS (एक्स 1) -ABS (Y1)) / (एबीएस (एक्स 1) + एबीएस (Y1)))) - पीआई () / (8 + 10 ^ -15))) / 2 * साइन ((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

थोड़ा-सा प्रदर्शन सुधार <-चेक को संबंधित >-चेक की शाखा में डाल दिया जाएगा , लेकिन मैंने ऐसा करने से परहेज किया क्योंकि यह पठनीयता को नुकसान पहुँचाता है।


2
क्षमा करें, लेकिन यह वह उत्तर नहीं देगा, जिसकी मुझे तलाश है। उस कोड के साथ यह केवल "एन" का उत्पादन करेगा यदि वेक्टर ठीक उत्तर में है, और एनई या एनडब्ल्यू अगर एक्स कोई अन्य मूल्य है। मुझे सबसे करीबी कम्पास दिशा की आवश्यकता है, उदाहरण के लिए अगर वेक्टर एनडब्ल्यू के मुकाबले एन के करीब है तो यह एन उपज देगा
izb

क्या यह वास्तव में निकटतम दिशा देगा? ऐसा लगता है कि एक वेक्टर (0.00001,100) आपको उत्तर पूर्व में देगा। संपादित करें: आप मुझे इसे izb को हराया।
सिस्कोआईपीफोन

आपने यह नहीं कहा कि आप निकटतम दिशा चाहते हैं।
फिलिप

1
क्षमा करें, मैंने उसे शीर्षक में छिपा दिया। प्रश्न निकाय में स्पष्ट होना चाहिए था
izb

1
अनंत आदर्श का उपयोग करने के बारे में क्या? अधिकतम (एब्स (वेक्टर डॉटपर्स)) द्वारा विभाजित करना आपको उस मानक के संबंध में एक सामान्यीकृत वेक्टर देता है। अब आप एक छोटी सी चेक-अप तालिका पर if (x > 0.9) dir |= DIR_Eऔर बाकी सभी के आधार पर लिख सकते हैं । यह फिलिप के मूल कोड से बेहतर होना चाहिए और L2 मानदंड और atan2 का उपयोग करने से थोड़ा सस्ता होना चाहिए। शायद या शायद नही।
टेओड्रन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.