Знаковый угол между направленными отрезками - PullRequest
0 голосов
/ 29 сентября 2018

У меня есть алгоритм, в котором мне нужно определить угол со знаком (от -180 до 180) между ребрами на графике.Я провел некоторое исследование и нашел множество конкретных ответов, но не могу понять, как связать их с моей ситуацией (например, этот вопрос, который использует atan2, однако ОП хотел только положительные углы) IЯ пытался реализовать несколько разных способов (используя 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)

Примечание : функция предполагает, что все три аргумента будут экземплярами типичного класса Point с атрибутами x и y,Кроме того, приведенный выше примерный график имеет только положительные значения, но я вполне уверен, что это работает с графиками, которые также содержат отрицательные значения.

Ответы [ 3 ]

0 голосов
/ 30 сентября 2018

Я прочитал вашу постановку задачи следующим образом: учитывая 2 точки A и B и центр O , найдите угол A до B как угол, положительный, если против часовой стрелки, между векторами A → O и A → B .

Если мои помещенияправильно, тогда вы можете

  • найти угол между A → B и горизонтальной прямой, проходящей через A ,
  • найтиугол между A → O и горизонтальной прямой линией, проходящей в A ,
  • , найти угол от A до B как разность указанных углов,
  • нормализует диапазон результатов так, чтобы он находился между -π и + π.

То, что я сказал, можно визуализировать следующим образом

enter image description here или в коде (в предположении Point класса с атрибутами 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 ≤ π, добавляя или вычитая это не меняет значение измеренного угла.

0 голосов
/ 16 января 2019

Если учесть координаты 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)))
0 голосов
/ 30 сентября 2018

Определение положительных и отрицательных углов сильно зависит от системы отсчета или контрольной точки.Несмотря на свое «правильное» определение, базовый расчет может быть в значительной степени выполнен на основе наклона между двумя точками и полученного угла наклона, который можно рассчитать, применяя обратное 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)
...