有向线段之间的符号角
Signed angle between directed line segments
我有一个算法,我需要计算图中边之间的符号角(-180 到 180)。我做了一些研究并找到了很多具体的答案,但无法弄清楚如何将它们与我的情况联系起来(例如 使用 atan2
的问题,但是 OP 只想要正角度)
我已经尝试实施几种不同的方式(使用 atan2 或 arccos),但我很难将这些示例与我的具体问题联系起来。我试过将边作为向量处理,但得到了奇怪的结果。
给定一个包含点 (A、B、C、D、E) 的图表,以及这些点的平均值 (avg)...我如何找到其中一个点(例如 A)之间的符号角和其他点(例如 B、C、D、E),取当前原点 (A) 到 'avg' 点的角度等于 0 度。下面的示例...
...在本例中,从 (A, avg) 到 (A, B) 的逆时针角度为正值(介于 0 和 180 之间),而从 (A, avg) 到(A, E) 会是负值(介于 0 和 -180 之间)。
理想情况下,我想要一个公式,我也可以将其应用于将任何点定义为原点,例如以点 C 为原点。'zero angle' 将是 (C, avg) 并且(C, avg) 和 (C, A) 之间的角度为负(0 到 -180),(C, avg) 和 (C, E) 之间的角度为正(0 到 180)。
我没有学过高中以外的数学,所以我发现很难用我不理解的符号来破译方程式。
更新:我想我会整理一下以使结论更清楚。
我对已接受的答案做了两处小改动,生成了以下代码片段:
def angle(vertex, start, dest):
AhAB = math.atan2((dest.y - vertex.y), (dest.x - vertex.x))
AhAO = math.atan2((start.y - vertex.y), (start.x - vertex.x))
AB = AhAB - AhAO
# in between 0-math.pi = do nothing, more than math.pi = +(-2 * math.pi), less than zero = do nothing
AB = math.degrees(AB + (-2 * math.pi if AB > math.pi else (2 * math.pi if AB < 0 - math.pi else 0)))
return AB
...经过几个月的不工作,最后的一行代码可能有点难以理解,所以我把它变成了它自己的函数,将 AB = AhAB - AhAO
的结果作为它的争论...
def calc(ab):
if ab > math.pi:
return ab + (-2 * math.pi)
else:
if ab < 0 - math.pi:
return ab + (2 * math.pi)
else:
return ab + 0
虽然行数较多,但我认为这更易读。
完整的最终函数:
def angle(vertex, start, dest):
"""Calculates the signed angle between two edges with the same origin.
Origin is the 'vertex' argument, 'start' is the bounding point of the edge to calculate the angle from.
Positively signed result means anti-clockwise rotation about the vertex."""
def calc_radians(ab):
if ab > math.pi:
return ab + (-2 * math.pi)
else:
if ab < 0 - math.pi:
return ab + (2 * math.pi)
else:
return ab + 0
AhAB = math.atan2((dest.y - vertex.y), (dest.x - vertex.x))
AhAO = math.atan2((start.y - vertex.y), (start.x - vertex.x))
res = calc_radians(AhAB - AhAO)
return math.degrees(res)
注意:该函数假定三个参数都是典型的 Point
class 的实例,其中 x
和 y
属性。
此外,上面的示例图只有正值,但我相当确定这也适用于涉及负值的图。
正角和负角的定义在很大程度上取决于参考系统或参考点。尽管有 'correct' 定义,基本计算可以根据 slope between two points and the resulting angle of incline which can be calculated by applying the inverse tan 到斜率来完成。
在编程中应用 inverse tan 可能有点烦人,因为许多编程语言为此提供了两种不同的功能:
arctan
or atan
which is implemented in Python's math.atan()
or numpy.atan()
arctan2
or atan2
which is delivered by math.atan2()
or numpy.atan2()
这两个函数,无论在math
模块或numpy
包中实现,return计算的角度(基本上是基于数字Pi而不是弧度)度,这使得有必要进行一些进一步的转换。这可以手动完成,也可以通过应用 numpy.rad2deg()
之类的函数来完成。为了对数据点有一个基本的了解并对计算结果进行一些直观的估计,我建议使用 matplotlib
.
绘制数据点
将前面提到的所有注意事项粘合到代码中可能如下所示:
import pandas as pd
import matplotlib
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
# Define some sample data points
coords = {
'A': (1.5, 3.0),
'B': (3.0, 5.0),
'C': (5.5, 4.5),
'D': (5.8, 2.2),
'E': (2.8, 1.2)
}
# Extract data values from `coords` dict
values = np.array(list(coords.values()))
# Calculate the averaged point of all data points
avg = np.mean(values, axis=0)
# Plot sample data for better overview
for k, v in coords.items():
plt.plot(*v, marker='o', linestyle='')
plt.text(*v, k)
plt.plot(*avg, marker='o', linestyle='')
plt.text(*avg, 'avg')
plt.show()
# For further information about slope and angle of incline
# see Wikipedia (https://en.wikipedia.org/wiki/Slope).
# Calculating the angle from `avg` to each point. Please adopt
# to your own needs if needed for other pairs of points.
# Calculate the distance in x- and y-direction from each point to point `avg`
distances_x_y = (values - avg)
# Depending on your definition of the 'reference point' consider using
# distances_x_y = (avg - values)
# For further explanation on `atan` and `atan2` see
# and
# https://en.wikipedia.org/wiki/Atan2 .
# Using a for loop instead of numpy's array/vectors is not very elegant,
# but easy to understand and therefore has potential for improvements.
# Calculate angle from point `avg` to each other point based on distances
angle_radians = np.array([np.arctan2(element[1], element[0]) for element in distances_x_y])
# since `.arctan2()` or `.arctan()` return the angle in radians,
# we need to convert to degrees
angle_degrees = np.rad2deg(angle_radians)
# print results
print(angle_degrees)
我读了你的问题陈述如下:给定2个点A和B,以及一个中心O,求向量A到B的角度,逆时针为正,矢量A→O 和 A→B.
如果我的前提是正确的,那么你可以
- 求A→B与一条向右的水平线穿过A,
的夹角
- 求A→O与一条向右的水平线穿过A,
的夹角
- 求出A与B的角差,
- 将结果范围归一化,使其介于 -π 和 +π 之间。
我说的可以形象化如下
或在代码中(假设 Point
class 具有属性 x
和 y
)
AhAB = math.atan2((B.y-A.y), (B.x-A.x)) # -π < AhAB ≤ +π
AhAO = math.atan2((O.y-A.y), (O.x-A.x)) # -π < AhA) ≤ +π
AB = AhAB - AhAO # -2π < AB ≤ +2π
AB = AB + ( 2*math.pi if AB < math.pi else (-2*math.pi if AB> math.pi else 0))
附录
这是一个小代码示例,点的位置与您在图片中看到的相似
In [18]: from math import atan2, pi
In [21]: class Point():
...: def __init__(self, x, y):
...: self.x, self.y = x, y
...: def __repr__(self):
...: return '(%s, %s)'%(self.x, self.y)
In [22]: A = Point(0.0, 0.0)
In [23]: B = Point(-2.0, 2.0)
In [24]: O = Point(0.0, -3.0)
In [25]: AhAB = atan2((B.y-A.y), (B.x-A.x)) ; print(3/4, AhAB/pi)
0.75 0.75
In [26]: AhAO = atan2((O.y-A.y), (O.x-A.x)) ; print(-1/2, AhAO/pi)
-0.5 -0.5
In [27]: AB = AhAB - AhAO ; print(5/4, AB/pi)
1.25 1.25
In [28]: AB = AB + ( 2*pi if AB < pi else (-2*pi if AB> pi else 0)) ; print(AB/pi)
-0.75
In [29]:
最后一行将结果 AB
归一化到正确的范围 -π < AB ≤ π
,添加或减去 2π
不会改变测量角度的含义。
如果考虑坐标x0=xavg-xA
、y0=yavg-yA
和x=xPoint-xA,y=yPoint-yA
,公式f(x,y)
给出逆时针为正的有符号角。
f(x,y)=pi()/2*((1+sign(x0))* (1-sign(y0^2))-(1+sign(x))* (1-sign(y^2)))
+pi()/4*((2+sign(x0))*sign(y0)-(2+sign(x))*sign(y))
+sign(x0*y0)*atan((abs(x0)-abs(y0))/(abs(x0)+abs(y0)))
-sign(x*y)*atan((abs(x)-abs(y))/(abs(x)+abs(y)))
我有一个算法,我需要计算图中边之间的符号角(-180 到 180)。我做了一些研究并找到了很多具体的答案,但无法弄清楚如何将它们与我的情况联系起来(例如 atan2
的问题,但是 OP 只想要正角度)
我已经尝试实施几种不同的方式(使用 atan2 或 arccos),但我很难将这些示例与我的具体问题联系起来。我试过将边作为向量处理,但得到了奇怪的结果。
给定一个包含点 (A、B、C、D、E) 的图表,以及这些点的平均值 (avg)...我如何找到其中一个点(例如 A)之间的符号角和其他点(例如 B、C、D、E),取当前原点 (A) 到 'avg' 点的角度等于 0 度。下面的示例...
...在本例中,从 (A, avg) 到 (A, B) 的逆时针角度为正值(介于 0 和 180 之间),而从 (A, avg) 到(A, E) 会是负值(介于 0 和 -180 之间)。
理想情况下,我想要一个公式,我也可以将其应用于将任何点定义为原点,例如以点 C 为原点。'zero angle' 将是 (C, avg) 并且(C, avg) 和 (C, A) 之间的角度为负(0 到 -180),(C, avg) 和 (C, E) 之间的角度为正(0 到 180)。
我没有学过高中以外的数学,所以我发现很难用我不理解的符号来破译方程式。
更新:我想我会整理一下以使结论更清楚。 我对已接受的答案做了两处小改动,生成了以下代码片段:
def angle(vertex, start, dest):
AhAB = math.atan2((dest.y - vertex.y), (dest.x - vertex.x))
AhAO = math.atan2((start.y - vertex.y), (start.x - vertex.x))
AB = AhAB - AhAO
# in between 0-math.pi = do nothing, more than math.pi = +(-2 * math.pi), less than zero = do nothing
AB = math.degrees(AB + (-2 * math.pi if AB > math.pi else (2 * math.pi if AB < 0 - math.pi else 0)))
return AB
...经过几个月的不工作,最后的一行代码可能有点难以理解,所以我把它变成了它自己的函数,将 AB = AhAB - AhAO
的结果作为它的争论...
def calc(ab):
if ab > math.pi:
return ab + (-2 * math.pi)
else:
if ab < 0 - math.pi:
return ab + (2 * math.pi)
else:
return ab + 0
虽然行数较多,但我认为这更易读。
完整的最终函数:
def angle(vertex, start, dest):
"""Calculates the signed angle between two edges with the same origin.
Origin is the 'vertex' argument, 'start' is the bounding point of the edge to calculate the angle from.
Positively signed result means anti-clockwise rotation about the vertex."""
def calc_radians(ab):
if ab > math.pi:
return ab + (-2 * math.pi)
else:
if ab < 0 - math.pi:
return ab + (2 * math.pi)
else:
return ab + 0
AhAB = math.atan2((dest.y - vertex.y), (dest.x - vertex.x))
AhAO = math.atan2((start.y - vertex.y), (start.x - vertex.x))
res = calc_radians(AhAB - AhAO)
return math.degrees(res)
注意:该函数假定三个参数都是典型的 Point
class 的实例,其中 x
和 y
属性。
此外,上面的示例图只有正值,但我相当确定这也适用于涉及负值的图。
正角和负角的定义在很大程度上取决于参考系统或参考点。尽管有 'correct' 定义,基本计算可以根据 slope between two points and the resulting angle of incline which can be calculated by applying the inverse tan 到斜率来完成。
在编程中应用 inverse tan 可能有点烦人,因为许多编程语言为此提供了两种不同的功能:
arctan
oratan
which is implemented in Python'smath.atan()
ornumpy.atan()
arctan2
oratan2
which is delivered bymath.atan2()
ornumpy.atan2()
这两个函数,无论在math
模块或numpy
包中实现,return计算的角度(基本上是基于数字Pi而不是弧度)度,这使得有必要进行一些进一步的转换。这可以手动完成,也可以通过应用 numpy.rad2deg()
之类的函数来完成。为了对数据点有一个基本的了解并对计算结果进行一些直观的估计,我建议使用 matplotlib
.
将前面提到的所有注意事项粘合到代码中可能如下所示:
import pandas as pd
import matplotlib
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
# Define some sample data points
coords = {
'A': (1.5, 3.0),
'B': (3.0, 5.0),
'C': (5.5, 4.5),
'D': (5.8, 2.2),
'E': (2.8, 1.2)
}
# Extract data values from `coords` dict
values = np.array(list(coords.values()))
# Calculate the averaged point of all data points
avg = np.mean(values, axis=0)
# Plot sample data for better overview
for k, v in coords.items():
plt.plot(*v, marker='o', linestyle='')
plt.text(*v, k)
plt.plot(*avg, marker='o', linestyle='')
plt.text(*avg, 'avg')
plt.show()
# For further information about slope and angle of incline
# see Wikipedia (https://en.wikipedia.org/wiki/Slope).
# Calculating the angle from `avg` to each point. Please adopt
# to your own needs if needed for other pairs of points.
# Calculate the distance in x- and y-direction from each point to point `avg`
distances_x_y = (values - avg)
# Depending on your definition of the 'reference point' consider using
# distances_x_y = (avg - values)
# For further explanation on `atan` and `atan2` see
# and
# https://en.wikipedia.org/wiki/Atan2 .
# Using a for loop instead of numpy's array/vectors is not very elegant,
# but easy to understand and therefore has potential for improvements.
# Calculate angle from point `avg` to each other point based on distances
angle_radians = np.array([np.arctan2(element[1], element[0]) for element in distances_x_y])
# since `.arctan2()` or `.arctan()` return the angle in radians,
# we need to convert to degrees
angle_degrees = np.rad2deg(angle_radians)
# print results
print(angle_degrees)
我读了你的问题陈述如下:给定2个点A和B,以及一个中心O,求向量A到B的角度,逆时针为正,矢量A→O 和 A→B.
如果我的前提是正确的,那么你可以
- 求A→B与一条向右的水平线穿过A, 的夹角
- 求A→O与一条向右的水平线穿过A, 的夹角
- 求出A与B的角差,
- 将结果范围归一化,使其介于 -π 和 +π 之间。
我说的可以形象化如下
Point
class 具有属性 x
和 y
)
AhAB = math.atan2((B.y-A.y), (B.x-A.x)) # -π < AhAB ≤ +π
AhAO = math.atan2((O.y-A.y), (O.x-A.x)) # -π < AhA) ≤ +π
AB = AhAB - AhAO # -2π < AB ≤ +2π
AB = AB + ( 2*math.pi if AB < math.pi else (-2*math.pi if AB> math.pi else 0))
附录
这是一个小代码示例,点的位置与您在图片中看到的相似
In [18]: from math import atan2, pi
In [21]: class Point():
...: def __init__(self, x, y):
...: self.x, self.y = x, y
...: def __repr__(self):
...: return '(%s, %s)'%(self.x, self.y)
In [22]: A = Point(0.0, 0.0)
In [23]: B = Point(-2.0, 2.0)
In [24]: O = Point(0.0, -3.0)
In [25]: AhAB = atan2((B.y-A.y), (B.x-A.x)) ; print(3/4, AhAB/pi)
0.75 0.75
In [26]: AhAO = atan2((O.y-A.y), (O.x-A.x)) ; print(-1/2, AhAO/pi)
-0.5 -0.5
In [27]: AB = AhAB - AhAO ; print(5/4, AB/pi)
1.25 1.25
In [28]: AB = AB + ( 2*pi if AB < pi else (-2*pi if AB> pi else 0)) ; print(AB/pi)
-0.75
In [29]:
最后一行将结果 AB
归一化到正确的范围 -π < AB ≤ π
,添加或减去 2π
不会改变测量角度的含义。
如果考虑坐标x0=xavg-xA
、y0=yavg-yA
和x=xPoint-xA,y=yPoint-yA
,公式f(x,y)
给出逆时针为正的有符号角。
f(x,y)=pi()/2*((1+sign(x0))* (1-sign(y0^2))-(1+sign(x))* (1-sign(y^2)))
+pi()/4*((2+sign(x0))*sign(y0)-(2+sign(x))*sign(y))
+sign(x0*y0)*atan((abs(x0)-abs(y0))/(abs(x0)+abs(y0)))
-sign(x*y)*atan((abs(x)-abs(y))/(abs(x)+abs(y)))