如何计算一条线与水平轴之间的角度?


247

在编程语言(Python,C#等)中,我需要确定如何计算直线和水平轴之间的角度?

我认为一张图片最能说明我想要的内容:

没有语言可以形容这

给定(P1 x,P1 y)和(P2 x,P2 y),计算此角度的最佳方法是什么?原点在左上角,仅使用正象限。


Answers:


386

首先找到起点和终点之间的差异(在这里,这更多的是有向线段,而不是“线”,因为线无限延伸且不在特定点处开始)。

deltaY = P2_y - P1_y
deltaX = P2_x - P1_x

然后计算角度(从的正X轴P1到的正Y轴延伸P1)。

angleInDegrees = arctan(deltaY / deltaX) * 180 / PI

但是arctan可能并不理想,因为用这种方式划分差异将消除区分角度所在象限所需的区分(请参见下文)。如果您的语言包含atan2函数,请改用以下代码:

angleInDegrees = atan2(deltaY, deltaX) * 180 / PI

编辑(2017年2月22日):但是,总的来说,打电话atan2(deltaY,deltaX)只是为了获得适当的角度cossin可能不太优雅。在这种情况下,通常可以执行以下操作:

  1. 治疗(deltaX, deltaY)作为载体。
  2. 将该向量归一化为单位向量。为此,分频deltaXdeltaY由向量的长度(sqrt(deltaX*deltaX+deltaY*deltaY)),除非长度为0。
  3. 此后,deltaX现在将是矢量和水平轴之间的角度(在处从正X轴到正Y轴的方向P1)的余弦值。
  4. deltaY现在将是角度的正弦值。
  5. 如果向量的长度为0,则它​​与水平轴之间将没有角度(因此,它将没有有意义的正弦和余弦)。

编辑(2017年2月28日):即使未规范化(deltaX, deltaY)

  • 的符号deltaX将告诉您步骤3中描述的余弦是正还是负。
  • 的符号deltaY将告诉您步骤4中描述的正弦是正还是负。
  • 的迹象deltaX,并deltaY会告诉你哪个象限的角度是,相对于在正X轴P1
    • +deltaX+deltaY:0至90度。
    • -deltaX+deltaY:90至180度。
    • -deltaX-deltaY180〜270度(-180到-90度)。
    • +deltaX-deltaY:270至360度(-90至0度)。

使用弧度的Python实现(2015年7月19日,由Eric Leschinski提供,他编辑了我的答案):

from math import *
def angle_trunc(a):
    while a < 0.0:
        a += pi * 2
    return a

def getAngleBetweenPoints(x_orig, y_orig, x_landmark, y_landmark):
    deltaY = y_landmark - y_orig
    deltaX = x_landmark - x_orig
    return angle_trunc(atan2(deltaY, deltaX))

angle = getAngleBetweenPoints(5, 2, 1,4)
assert angle >= 0, "angle must be >= 0"
angle = getAngleBetweenPoints(1, 1, 2, 1)
assert angle == 0, "expecting angle to be 0"
angle = getAngleBetweenPoints(2, 1, 1, 1)
assert abs(pi - angle) <= 0.01, "expecting angle to be pi, it is: " + str(angle)
angle = getAngleBetweenPoints(2, 1, 2, 3)
assert abs(angle - pi/2) <= 0.01, "expecting angle to be pi/2, it is: " + str(angle)
angle = getAngleBetweenPoints(2, 1, 2, 0)
assert abs(angle - (pi+pi/2)) <= 0.01, "expecting angle to be pi+pi/2, it is: " + str(angle)
angle = getAngleBetweenPoints(1, 1, 2, 2)
assert abs(angle - (pi/4)) <= 0.01, "expecting angle to be pi/4, it is: " + str(angle)
angle = getAngleBetweenPoints(-1, -1, -2, -2)
assert abs(angle - (pi+pi/4)) <= 0.01, "expecting angle to be pi+pi/4, it is: " + str(angle)
angle = getAngleBetweenPoints(-1, -1, -1, 2)
assert abs(angle - (pi/2)) <= 0.01, "expecting angle to be pi/2, it is: " + str(angle)

所有测试均通过。参见https://en.wikipedia.org/wiki/Unit_circle


35
如果找到了这个并且您正在使用JAVASCRiPT,请务必注意Math.sin和Math.cos采用弧度,因此您无需将结果转换为度数就非常重要!因此,忽略* 180 / PI位。我花了四个小时才找到答案。:)
sidonaldson

应该使用什么来计算沿垂直轴的角度?
ZeMoon 2014年

3
@akashg :90 - angleInDegrees
jbaums

为什么我们需要做90-angleInDegrees,有什么理由吗?请说明相同。
Praveen Matanam 2014年

2
@sidonaldson它不仅是Javascript,还包括C,C#,C ++,Java等。实际上,我敢说大多数语言的数学库都主要使用弧度。我还没有看到默认情况下仅支持度数的语言。
法拉普2014年

50

抱歉,但是我很确定Peter的回答是错误的。请注意,y轴位于页面下方(在图形中常见)。因此,必须逆转deltaY计算,否则您将得到错误的答案。

考虑:

System.out.println (Math.toDegrees(Math.atan2(1,1)));
System.out.println (Math.toDegrees(Math.atan2(-1,1)));
System.out.println (Math.toDegrees(Math.atan2(1,-1)));
System.out.println (Math.toDegrees(Math.atan2(-1,-1)));

45.0
-45.0
135.0
-135.0

因此,如果在上面的示例中P1为(1,1),P2为(2,2)[因为Y向下扩展页面],则上面的代码将为所示示例给出45.0度,这是错误的。更改deltaY计算的顺序,即可正​​常工作。


3
我按照您的建议将其反转了,但我的旋转方向是向后的。
魔鬼的代言人2012年

1
在我的代码中,我使用以下方法解决此问题:double arc = Math.atan2(mouse.y - obj.getPy(), mouse.x - obj.getPx()); degrees = Math.toDegrees(arc); if (degrees < 0) degrees += 360; else if (degrees > 360) degrees -= 360;
Marcus Becker

这取决于您的角度所在的圆弧的四分之一:如果您在第一个四分之一(最大90度)中为deltaX和deltaY(Math.abs)使用正值,在第二个(90-180)中使用正值否定deltaX的抽象值,在第三个(180-270)中对deltaX和deltaY取反,而在第四个(270-360)中对deltaY取反
-mamashare

1

我已经找到了运行良好的Python解决方案!

from math import atan2,degrees

def GetAngleOfLineBetweenTwoPoints(p1, p2):
    return degrees(atan2(p2 - p1, 1))

print GetAngleOfLineBetweenTwoPoints(1,3)

1

考虑到确切的问题,将我们置于一个“特殊”坐标系中,其中正轴表示向下移动(例如屏幕或界面视图),您需要像这样调整该功能,而将Y坐标设为负:

Swift 2.0中的示例

func angle_between_two_points(pa:CGPoint,pb:CGPoint)->Double{
    let deltaY:Double = (Double(-pb.y) - Double(-pa.y))
    let deltaX:Double = (Double(pb.x) - Double(pa.x))
    var a = atan2(deltaY,deltaX)
    while a < 0.0 {
        a = a + M_PI*2
    }
    return a
}

此功能可为问题提供正确答案。答案以弧度为单位,因此,以度为单位查看角度的用法是:

let p1 = CGPoint(x: 1.5, y: 2) //estimated coords of p1 in question
let p2 = CGPoint(x: 2, y : 3) //estimated coords of p2 in question

print(angle_between_two_points(p1, pb: p2) / (M_PI/180))
//returns 296.56

0

基于参考“ Peter O”。.这是java版本

private static final float angleBetweenPoints(PointF a, PointF b) {
float deltaY = b.y - a.y;
float deltaX = b.x - a.x;
return (float) (Math.atan2(deltaY, deltaX)); }

0

matlab函数:

function [lineAngle] = getLineAngle(x1, y1, x2, y2) 
    deltaY = y2 - y1;
    deltaX = x2 - x1;

    lineAngle = rad2deg(atan2(deltaY, deltaX));

    if deltaY < 0
        lineAngle = lineAngle + 360;
    end
end

0

角度从0到2pi的公式。

有x = x2-x1和y = y2-y1。

x和y的任何值。对于x = y = 0,结果是不确定的。

f(x,y)= pi()-pi()/ 2 *(1+符号(x))*(1-符号(y ^ 2))

     -pi()/4*(2+sign(x))*sign(y)

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

0
deltaY = Math.Abs(P2.y - P1.y);
deltaX = Math.Abs(P2.x - P1.x);

angleInDegrees = Math.atan2(deltaY, deltaX) * 180 / PI

if(p2.y > p1.y) // Second point is lower than first, angle goes down (180-360)
{
  if(p2.x < p1.x)//Second point is to the left of first (180-270)
    angleInDegrees += 180;
  else //(270-360)
    angleInDegrees += 270;
}
else if (p2.x < p1.x) //Second point is top left of first (90-180)
  angleInDegrees += 90;

您的代码没有意义:否则(270-360)..什么?
WDUK

0
import math
from collections import namedtuple


Point = namedtuple("Point", ["x", "y"])


def get_angle(p1: Point, p2: Point) -> float:
    """Get the angle of this line with the horizontal axis."""
    dx = p2.x - p1.x
    dy = p2.y - p1.y
    theta = math.atan2(dy, dx)
    angle = math.degrees(theta)  # angle is in (-180, 180]
    if angle < 0:
        angle = 360 + angle
    return angle

测验

为了进行测试,我让假设生成了测试案例。

在此处输入图片说明

import hypothesis.strategies as s
from hypothesis import given


@given(s.floats(min_value=0.0, max_value=360.0))
def test_angle(angle: float):
    epsilon = 0.0001
    x = math.cos(math.radians(angle))
    y = math.sin(math.radians(angle))
    p1 = Point(0, 0)
    p2 = Point(x, y)
    assert abs(get_angle(p1, p2) - angle) < epsilon
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.