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

264 投票
9 回答
247481 浏览
提问于 2025-04-17 03:16

在编程语言(比如Python、C#等)中,我想知道如何计算一条线和水平轴之间的角度?

我觉得用一张图片来说明我想要的效果最好:

没有文字可以描述这个

给定两个点 (P1x, P1y) 和 (P2x, P2y),计算这个角度的最佳方法是什么?坐标原点在左上角,并且只使用正的象限。

9 个回答

4

测试

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

为了进行测试,我使用了hypothesis来生成测试用例。

在这里输入图片描述

52

抱歉,我觉得彼得的回答是错的。注意,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的计算顺序,它就能正常工作了。

398

首先,找出起点和终点之间的差距(这里更像是一个有方向的线段,而不是“线”,因为线是无限延伸的,不会从某个特定点开始)。

deltaY = P2_y - P1_y
deltaX = P2_x - P1_x

然后计算角度(这个角度是从正X轴到正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轴到P1的正Y轴)。
  4. deltaY就会变成这个角度的正弦值。
  5. 如果向量的长度是0,那么它和水平轴之间就没有角度(所以也就没有有意义的正弦和余弦值)。

编辑(2017年2月28日):即使不对(deltaX, deltaY)进行归一化:

  • deltaX的符号会告诉你第3步中描述的余弦值是正还是负。
  • deltaY的符号会告诉你第4步中描述的正弦值是正还是负。
  • deltaXdeltaY的符号会告诉你这个角度相对于P1的正X轴处于哪个象限:
    • +deltaX, +deltaY: 0到90度。
    • -deltaX, +deltaY: 90到180度。
    • -deltaX, -deltaY: 180到270度(-180到-90度)。
    • +deltaX, -deltaY: 270到360度(-90到0度)。

以下是一个使用弧度的Python实现(由Eric Leschinski在2015年7月19日提供,编辑了我的回答):

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

撰写回答