如何将atan2()映射到0-360度?

134

atan2(y, x)在180°处有不连续点,当逆时针旋转时变为-180°..0°。

我如何将值范围映射到0°..360°?

这是我的代码:

CGSize deltaPoint = CGSizeMake(endPoint.x - startPoint.x, endPoint.y - startPoint.y);
float swipeBearing = atan2f(deltaPoint.height, deltaPoint.width);

我正在计算滑动触摸事件的方向,给定startPointendPoint,它们都是XY点结构体。这段代码用于iPhone,但支持atan2f()的任何语言都可以。


注意:发布的更新方法不会返回零度,而是从略高于0到360.0的值。 - chux - Reinstate Monica
2
如何从两个位置获取角度 - MAnoj Sarnaik
这个函数运行得很好,但是“bearingDegrees”计算的角度是反转的。例如,45度通常应该在第一象限,但是在第四象限。135度通常应该在第二象限,但是这个函数返回它在第三象限。我可以简单地取函数返回值x并将其从360中否定以获得正确的角度值,但我很想知道为什么会发生这种情况? - goelv
16个回答

132

使用取余运算的解决方案

一个简单的解决方案,适用于所有情况。

degrees = (degrees + 360) % 360;  // +360 for implementations where mod returns negative numbers

解释

正数:1到180

如果您对介于1到180之间的任何正数进行模运算360,您将得到与输入相同的确切数字。在这里进行模运算只是确保返回这些正数作为相同的值。

负数:-180到-1

在此使用模运算将返回在180到359度范围内的值。

特殊情况:0和360

使用模运算意味着返回0,从而使其成为安全的0-359度解决方案。


5
我不认为需要加上360。-1 % 360仍然是359 :) (注:该句话表达了作者的观点,认为在某种情况下并不需要增加360,因为在这种情况下,即使对负数取模后也可以得到正确的结果,即359。) - yobiscus
15
我认为这在所有语言中都不正确。在Javascript中,-1%360=-1 - Startec
1
在Java中也不是可行的方法。 - Hulk
3
@pleasemorebacon 错了。在某些语言中,-1%360 的结果是-1。 - Pharap

75
(x > 0 ? x : (2*PI + x)) * 360 / (2*PI)

8
在x = 0的情况下,可能也希望x >= 0。 - bpw1621
15
对于不熟悉这种符号表示法的人,可以在不进行角度转换的情况下进行以下调整: 如果(x>0),则弧度值等于x;否则,弧度值等于2*PI+x。 因此,如果结果小于0,我们只需将2PI添加到结果中。 - David Doria
3
或者像这样 (x < 0 ? 2*PI + x : x) * 180/PI,而不是 (x >= 0 ? x : (2*PI + x)) * 180/PI - user3342816

52
如果atan2的答案小于0度,则添加360度。

13
如果你有那种日子的话,"加上2 * PI"与之相同。 - Chris O
只需将代码添加到dave4420所说的内容中:x = (x < 0) ? (x + 360) : x; - javaEntu

44

如果您不喜欢使用分支结构,可以对两个参数取反并将答案加上180°。

(将返回值加上180°使其恰好在0-360度范围内,但会翻转角度。对两个输入参数取反可将其翻转回来。)


2
我宁愿修改我的代码来使用非规范化角度(<0,>= 360),但总有人追求那种虚假的“优化”感觉;这就是为什么我想添加这个功能。(或者是因为这是绕过我使用的一些临时调试代码的更快捷的方法?嗯) - aib
2
毫无疑问,这并不容易理解,我在两年多的时间里也深有体会。因此:将180°添加到返回值中可以很好地将其放置在0-360范围内,但会翻转角度。否定两个输入参数会将其翻转回来。 - aib
如果 $x = 0$ 而且 $y > 0$,就可能会出现一些问题(如我所记得的)。 - Trinidad

24

@erikkallen的答案接近但并不完全正确。

theta_rad = atan2(y,x);
theta_deg = (theta_rad/M_PI*180) + (theta_rad > 0 ? 0 : 360);

这应该适用于C++:(取决于fmod的实现方式,它可能比条件表达式更快或更慢)

theta_deg = fmod(atan2(y,x)/M_PI*180,360);

或者你可以这样做:

theta_deg = atan2(-y,-x)/M_PI*180 + 180;

因为点 (x,y) 和 (-x,-y) 在角度上相差 180 度。


1
如果我理解正确的话,在Fortran中应该是atan2(-y,-x)* 180 / PI + 180。对吗? - gansub
1
抱歉,我不懂FORTRAN。但是你的数学看起来没问题。 - Jason S

14

我有两种解决方案似乎适用于所有正数和负数x和y的组合。

1) 滥用 atan2()

根据文档,atan2按顺序接受参数y和x。然而,如果你反转它们,你可以这样做:

double radians = std::atan2(x, y);
double degrees = radians * 180 / M_PI;
if (radians < 0)
{
    degrees += 360; 
}

2) 正确使用 atan2(),然后进行转换

double degrees = std::atan2(y, x) * 180 / M_PI;
if (degrees > 90)
{
    degrees = 450 - degrees;
}
else
{
    degrees = 90 - degrees;
}

1
您真是个救星。我在Unity中使用了这种方法,效果非常好。 - porfiriopartida

10

@Jason S:你的“fmod”变体在符合标准的实现上无法工作。 C标准明确而清晰(7.12.10.1,“fmod函数”):

如果y不为零,则结果与x具有相同的符号。

因此,

fmod(atan2(y,x)/M_PI*180,360)

实际上只是一个冗长的改写:

atan2(y,x)/M_PI*180

然而,你的第三个建议非常到位。


10

这里有一些 JavaScript 代码。只需输入 x 和 y 值。

var angle = (Math.atan2(x,y) * (180/Math.PI) + 360) % 360;

7

这是我通常做的事情:

float rads = atan2(y, x);
if (y < 0) rads = M_PI*2.f + rads;
float degrees = rads*180.f/M_PI;

4

另一种解决方案是使用定义为:

function mod(a, b) {return a - Math.floor (a / b) * b;}

然后,使用以下函数获取 ini(x,y)end(x,y) 之间的角度。该角度用度数表示,标准化为 [0, 360] 度,并参照北方为 360 度。

    function angleInDegrees(ini, end) {
        var radian = Math.atan2((end.y - ini.y), (end.x - ini.x));//radian [-PI,PI]
        return mod(radian * 180 / Math.PI + 90, 360);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接