如何计算直线与水平轴之间的角度?
- 2025-02-10 08:57:00
- admin 原创
- 53
问题描述:
在编程语言(Python、C# 等)中,我需要确定如何计算线和水平轴之间的角度?
我认为一张图片最能描述我想要的东西:
给定 (P1 x ,P1 y ) 和 (P2 x ,P2 y ),计算该角度的最佳方法是什么?原点位于左上角,仅使用正象限。
解决方案 1:
首先找到起点和终点之间的差异(这里,这更像是一条有向线段,而不是“线”,因为线无限延伸并且不从特定点开始)。
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)
仅仅为了获得和的正确角度cos
而调用sin
可能不够优雅。在这些情况下,您通常可以改为执行以下操作:
(deltaX, deltaY)
当作向量处理。将该向量标准化为单位向量。为此,将
deltaX
和deltaY
除以向量的长度(sqrt(deltaX*deltaX+deltaY*deltaY)
),除非长度为 0。之后,
deltaX
现在将是向量与水平轴(从正 X 轴到正 Y 轴的方向)之间角度的余弦P1
。现在
deltaY
将是该角度的正弦。如果矢量的长度为 0,则它与水平轴之间没有角度(因此它没有有意义的正弦和余弦)。
编辑(2017 年 2 月 28 日):即使没有规范化(deltaX, deltaY)
:
的符号
deltaX
将告诉您步骤 3 中描述的余弦是正数还是负数。的符号
deltaY
将告诉您步骤 4 中描述的正弦是正值还是负值。deltaX
和的符号deltaY
表示该角位于相对于 处的正 X 轴的哪个象限P1
:
+ `+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
解决方案 2:
抱歉,但我确信 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:
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
解决方案 4:
我在 Python 中找到了一个很好的解决方案!
from math import atan2,degrees
def GetAngleOfLineBetweenTwoPoints(p1, p2):
return degrees(atan2(p2 - p1, 1))
print GetAngleOfLineBetweenTwoPoints(1,3)
解决方案 5:
根据参考“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)); }
解决方案 6:
考虑到确切的问题,将我们置于一个“特殊”的坐标系中,其中正轴表示向下移动(如屏幕或界面视图),您需要像这样调整此功能,并将 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
解决方案 7:
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
解决方案 8:
从 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)))
解决方案 9:
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;