Як обчислити кут між прямою та горизонтальною віссю?


248

У мові програмування (Python, C # тощо) мені потрібно визначити, як обчислити кут між лінією та горизонтальною віссю?

Я думаю, що зображення найкраще описує те, що я хочу:

жодні слова не можуть це описати

З огляду на (P1 x , P1 y ) та (P2 x , P2 y ), який найкращий спосіб обчислити цей кут? Походження знаходиться в топлефті і використовується тільки позитивний квадрант.


Відповіді:


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

EDIT (22 лютого 2017 р.): Однак, загалом, закликати atan2(deltaY,deltaX)просто отримати відповідний кут cosі sinможе бути неелегантним. У цих випадках ви можете часто робити наступні дії:

  1. Трактуйте (deltaX, deltaY)як вектор.
  2. Нормалізуйте цей вектор на одиничний вектор. Для цього поділіть deltaXі deltaYна довжину вектора ( sqrt(deltaX*deltaX+deltaY*deltaY)), якщо довжина не дорівнює 0.
  3. Після цього deltaXтепер буде косинус кута між вектором та горизонтальною віссю (у напрямку від позитивного X до позитивної осі Y at P1).
  4. І deltaYтепер буде синусом цього кута.
  5. Якщо довжина вектора дорівнює 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


35
Якщо ви виявили це і використовуєте JAVASCRiPT, дуже важливо відзначити, що Math.sin і Math.cos приймають радіани, тому вам не потрібно перетворювати результат у градуси! Тому ігноруйте біт * 180 / PI. На це знадобилось 4 години. :)
sidonaldson

Що слід використовувати для обчислення кута вздовж вертикальної осі?
ZeMoon

3
@akashg 90 - angleInDegrees :?
jbaums

Чому нам потрібно робити 90-кутову ступінь, чи є причина для цього? Прошу уточнити те саме.
Praveen Matanam

2
@sidonaldson Це більше, ніж просто Javascript, це C, C #, C ++, Java тощо. Насправді, смію сказати, що більшість мов мають свою математичну бібліотеку, яка працює в основному з радіанами. Я ще не бачив мову, яка за замовчуванням підтримує лише градуси.
Фарап

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 градусів для наведеного прикладу, що невірно. Змініть порядок розрахунку deltaY, і він працює належним чином.


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, а четверте (270-360) заперечує лише дельтаY - див. мою відповідь нижче
mamashare

1

Я знайшов рішення в Python, яке добре працює!

from math import atan2,degrees

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

print GetAngleOfLineBetweenTwoPoints(1,3)

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 до 2пі.

Існує x = x2-x1 і y = y2-y1.Формула працює

будь-яке значення х і у. Для x = y = 0 результат не визначений.

f (x, y) = pi () - pi () / 2 * (1 + знак (x)) * (1-знак (y ^ 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;

Ваш код не має сенсу: інше (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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.