直线段之间的符号角

时间:2018-09-29 19:29:23

标签: python angle

我有一种算法,需要计算出图形边缘之间的正负号(-180到180度)。我已经进行了一些研究,找到了很多具体的答案,但无法弄清楚如何将它们与我的处境联系起来(例如,this问题使用了atan2,但是OP仅需要正角度) 我尝试实现几种不同的方式(使用atan2或arccos),但是我努力将示例与具体问题联系起来。我尝试将边缘视为向量,但得到了奇怪的结果。

给出一个包含点(A,B,C,D,E)以及这些点的平均值(平均)的图形...我如何找到这些点之一(例如A)和其他点(例如B,C,D,E),则从当前原点(A)到“ avg”点的角度等于0度。下面的示例...

enter image description here

...在此示例中,从(A,avg)到(A,B)的逆时针角度为正值(介于0和180之间),并且从(A,avg)到(A ,E)为负数(0到-180之间)。

理想情况下,我想要一个公式,该公式也可用于将任何点定义为原点,例如,以C点为原点。“零角度”为(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)

注意:该函数假定这三个参数都将是具有Pointx属性的典型y类的实例。 另外,上面的示例图仅具有正值,但是我很确定这也适用于包含负值的图。

3 个答案:

答案 0 :(得分:2)

我阅读您的问题陈述如下:给定两个点 A B ,以及一个中心 O ,找到角度 A B 作为矢量 A→O A→B 之间的角度,如果逆时针为正。

如果我的前提正确,那么可以

  • 确定 A→B 与通过 A 的水平右线之间的角度,
  • 确定 A→O 与通过 A 的水平右线之间的角度,
  • 找到角度 A B 作为所述角度的差,
  • 标准化结果范围,使其在-π和+π之间。

我所说的内容可以如下显示

enter image description here 或代码中(假设Point类具有属性xy

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 ≤ π,加上或减去不会改变测量角度的含义。

答案 1 :(得分:0)

正负角的定义在很大程度上取决于参考系统或参考点。尽管定义为“正确”,但基本计算可以基于两点之间的slope以及可以通过将反tan应用于斜率来计算得出的倾斜角度来完成。

在编程中应用inverse tan可能会有些烦人,因为许多编程语言为此提供了两种不同的功能:

这两个函数都与math模块或numpy包中的实现无关,以弧度为单位返回计算的角度,该角度基本上基于数字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
# https://stackoverflow.com/q/35749246/3991125 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 :(得分:0)

如果考虑坐标x0=xavg-xAy0=yavg-yAx=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)))