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

248

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

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

никакие слова не могут описать это

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

orlp
источник
Смотрите также: тот же вопрос по JavaScript
Мартин Тома,

Ответы:

388

Сначала найдите разницу между начальной точкой и конечной точкой (здесь это скорее направленный отрезок, а не «линия», поскольку линии простираются бесконечно и не начинаются в определенной точке).

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

РЕДАКТИРОВАТЬ (22 февраля 2017 г.): В целом, однако, призыв atan2(deltaY,deltaX)только для того, чтобы получить правильный угол cosи sinможет быть не элегантным. В этих случаях вы часто можете сделать следующее:

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

РЕДАКТИРОВАТЬ (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

Питер О.
источник
35
Если вы обнаружили это и используете JAVASCRiPT, очень важно отметить, что Math.sin и Math.cos принимают радианы, поэтому вам не нужно преобразовывать результат в градусы! Поэтому игнорируйте бит * 180 / PI. Мне понадобилось 4 часа, чтобы выяснить это. :)
Сидональдсон
Что следует использовать для расчета угла по вертикальной оси?
ZeMoon
3
@akashg: 90 - angleInDegrees ?
Jbaums
Почему нам нужно сделать 90 - angleInDegrees, есть ли для этого какая-то причина? Пожалуйста, уточните то же самое.
Правин Матанам
2
@sidonaldson Это больше, чем просто Javascript, это C, C #, C ++, Java и т. д. На самом деле, я осмелюсь сказать, что большинство языков имеют свои математические библиотеки, работающие в основном с радианами. Я еще не видел язык, который по умолчанию поддерживает только градусы.
Pharap
50

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

user1641082
источник
3
Я изменил это, как вы предложили, и мое вращение было назад.
Адвокат дьявола
1
В моем коде я исправляю это следующим образом: double arc = Math.atan2(mouse.y - obj.getPy(), mouse.x - obj.getPx()); degrees = Math.toDegrees(arc); if (degrees < 0) degrees += 360; else if (degrees > 360) degrees -= 360;
Маркус Беккер
Это зависит от четверти круга, в котором находится ваш угол: если вы в первой четверти (до 90 градусов) используете положительные значения для deltaX и deltaY (Math.abs), во второй (90-180) используйте отрицание абстрактного значения deltaX, в третьем (180-270) отрицание и deltaX, и deltaY, и int в четвертом (270-360) отрицание только deltaY - см. мой ответ ниже
mamashare
1

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

from math import atan2,degrees

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

print GetAngleOfLineBetweenTwoPoints(1,3)
dctremblay
источник
1

Учитывая точный вопрос, поместив нас в «специальную» систему координат, где положительная ось означает перемещение ВНИЗ (например, экран или вид интерфейса), вам нужно адаптировать эту функцию следующим образом, и отрицательные координаты 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
Филипп
источник
0

На основе ссылки "Питер О" .. Вот версия 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)); }
Венкатешвара Рао
источник
0

функция 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
Бенас
источник
0

Формула для угла от 0 до 2pi.

Существует х = х2-х1 и у = у2-у1. Формула работает для

любое значение х и у. Для x = y = 0 результат не определен.

Р (х, у) = р () - пи () / 2 * (1 знак + (х)) * (1-знак (у ^ 2))

     -pi()/4*(2+sign(x))*sign(y)

     -sign(x*y)*atan((abs(x)-abs(y))/(abs(x)+abs(y)))
Теодор Панагос
источник
0
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;
mamashare
источник
Ваш код не имеет смысла: еще (270-360) .. что?
WDUK
0
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
Мартин Тома
источник