Как рассчитать угол между линией и горизонтальной осью?

В языке программирования (Python, С# и т.д.) мне нужно определить, как вычислить угол между линией и горизонтальной осью?

Я думаю, что изображение лучше всего описывает то, что я хочу:

no words can describe this

Учитывая (P1 x, P1 y) и (P2 x, P2 y), что такое лучший способ рассчитать этот угол? Происхождение находится в topleft и используется только положительный квадрант.

Ответ 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

EDIT (22 февраля 2017 года): В общем, однако, вызов atan2(deltaY,deltaX) только для получения правильного угла для cos и sin может быть неэлегантным. В таких случаях вы можете часто делать следующее:

  • Относитесь к (deltaX, deltaY) как к вектору.
  • Нормализовать этот вектор на единичный вектор. Для этого разделите deltaX и deltaY на длину вектора (sqrt(deltaX*deltaX+deltaY*deltaY)), если длина не равна 0.
  • После этого deltaX теперь будет косинусом угла между вектором и горизонтальной осью (в направлении от положительного X до положительной оси Y в точке P1).
  • И deltaY теперь будет синусом этого угла.
  • Если длина вектора равна 0, у него не будет угла между ним и горизонтальной осью (поэтому он не будет иметь значимого синуса и косинуса).

EDIT (28 февраля 2017 г.): Даже без нормализации (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 с использованием радианов (предоставленная 19 июля 2015 г. Эриком Лещински, который отредактировал мой ответ):

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

Извините, но я уверен, что ответ Питера неверен. Обратите внимание, что ось y идет вниз по странице (обычная в графике). Таким образом, дельта-вычисление должно быть отменено или вы получите неправильный ответ.

Рассмотрим:

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 градусов для показанного примера, что неверно. Измените порядок вычисления дельта и правильно работайте.

Ответ 3

Я нашел решение в Python, которое работает хорошо!

from math import atan2,degrees

def GetAngleOfLineBetweenTwoPoints(p1, p2):
    return degrees(atan2(p2 - p1, 1))

print GetAngleOfLineBetweenTwoPoints(1,3)

Ответ 4

Учитывая точный вопрос, поставив нас в "специальную" систему координат, где положительная ось означает перемещение DOWN (например, экран или интерфейс), вам необходимо адаптировать эту функцию следующим образом и отрицать координаты 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

Ответ 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

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;

Ответ 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