如何计算一条直线与水平轴的角度?
在编程语言(比如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)
来获取cos
和sin
的正确角度可能显得不够优雅。在这种情况下,你可以尝试以下方法:
- 把
(deltaX, deltaY)
看作一个向量。 - 将这个向量归一化为单位向量。也就是说,把
deltaX
和deltaY
分别除以这个向量的长度(sqrt(deltaX*deltaX+deltaY*deltaY)
),前提是长度不为0。 - 这样,
deltaX
就会变成这个向量和水平轴之间的角度的余弦值(方向是从正X轴到P1
的正Y轴)。 - 而
deltaY
就会变成这个角度的正弦值。 - 如果向量的长度是0,那么它和水平轴之间就没有角度(所以也就没有有意义的正弦和余弦值)。
编辑(2017年2月28日):即使不对(deltaX, deltaY)
进行归一化:
deltaX
的符号会告诉你第3步中描述的余弦值是正还是负。deltaY
的符号会告诉你第4步中描述的正弦值是正还是负。deltaX
和deltaY
的符号会告诉你这个角度相对于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