Làm thế nào để tính góc giữa một đường thẳng và trục hoành?


247

Trong một ngôn ngữ lập trình (Python, C #, v.v.) tôi cần xác định cách tính góc giữa một đường thẳng và trục hoành?

Tôi nghĩ rằng một hình ảnh mô tả tốt nhất những gì tôi muốn:

không có từ nào có thể mô tả điều này

Cho (P1 x , P1 y ) và (P2 x , P2 y ) cách tốt nhất để tính góc này là gì? Nguồn gốc nằm trong topleft và chỉ có góc phần tư dương được sử dụng.


Câu trả lời:


387

Trước tiên, hãy tìm sự khác biệt giữa điểm bắt đầu và điểm kết thúc (ở đây, đây là phần của một đoạn đường có hướng, không phải là "đường", vì các đường kéo dài vô tận và không bắt đầu tại một điểm cụ thể).

deltaY = P2_y - P1_y
deltaX = P2_x - P1_x

Sau đó tính góc (chạy từ trục X dương P1sang trục Y dương tại P1).

angleInDegrees = arctan(deltaY / deltaX) * 180 / PI

Nhưng arctancó thể không lý tưởng, bởi vì phân chia sự khác biệt theo cách này sẽ xóa đi sự khác biệt cần thiết để phân biệt góc phần tư nằm trong góc nào (xem bên dưới). Sử dụng sau đây nếu ngôn ngữ của bạn bao gồm một atan2chức năng:

angleInDegrees = atan2(deltaY, deltaX) * 180 / PI

EDIT (ngày 22 tháng 2 năm 2017): Tuy nhiên, nói chung, gọi atan2(deltaY,deltaX)chỉ để có được góc thích hợp cho cossincó thể không phù hợp . Trong những trường hợp đó, bạn thường có thể làm như sau:

  1. Coi (deltaX, deltaY)như một vectơ
  2. Bình thường hóa vectơ đó thành một vectơ đơn vị. Để làm như vậy, chia deltaXdeltaYcho chiều dài của vectơ ( sqrt(deltaX*deltaX+deltaY*deltaY)), trừ khi độ dài bằng 0.
  3. Sau đó, deltaXbây giờ sẽ là cosin của góc giữa vectơ và trục hoành (theo hướng từ X dương đến trục Y dương tại P1).
  4. deltaYbây giờ sẽ là sin của góc đó.
  5. Nếu độ dài của vectơ bằng 0, nó sẽ không có góc giữa nó và trục hoành (vì vậy nó sẽ không có sin và cos có ý nghĩa).

EDIT (28 tháng 2 năm 2017): Ngay cả khi không bình thường hóa (deltaX, deltaY):

  • Dấu hiệu deltaXsẽ cho bạn biết liệu cosin được mô tả trong bước 3 là dương hay âm.
  • Dấu hiệu deltaYsẽ cho bạn biết liệu sin được mô tả trong bước 4 là dương hay âm.
  • Các dấu hiệu của deltaXdeltaYsẽ cho bạn biết góc phần tư nằm trong góc nào, liên quan đến trục X dương tại P1:
    • +deltaX, +deltaY: 0 đến 90 độ.
    • -deltaX, +deltaY: 90 đến 180 độ.
    • -deltaX, -deltaY: 180 đến 270 độ (-180 đến -90 độ).
    • +deltaX, -deltaY: 270 đến 360 độ (-90 đến 0 độ).

Một triển khai trong Python sử dụng radian (được cung cấp vào ngày 19 tháng 7 năm 2015 bởi Eric Leschinski, người đã chỉnh sửa câu trả lời của tôi):

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)

Tất cả các bài kiểm tra vượt qua. Xem https://en.wikipedia.org/wiki/Unit_circle


35
Nếu bạn tìm thấy điều này và bạn đang sử dụng JAVASCRiPT, điều rất quan trọng cần lưu ý là Math.sin và Math.cos lấy radian để bạn không cần phải chuyển đổi kết quả thành độ! Vì vậy, bỏ qua bit * 180 / PI. Tôi mất 4 giờ để tìm ra điều đó. :)
sidonaldson

Nên dùng gì để tính góc dọc trục dọc?
ZeMoon

3
@akashg: 90 - angleInDegrees ?
jbaums

Tại sao chúng ta cần phải làm 90 - angleInDegrees, có lý do nào cho nó không? Hãy làm rõ như vậy.
Praveen Matanam

2
@sidonaldson Không chỉ là Javascript, đó là C, C #, C ++, Java, v.v. Trên thực tế tôi dám nói rằng phần lớn các ngôn ngữ có thư viện toán học của họ hoạt động chủ yếu với radian. Tôi chưa thấy một ngôn ngữ chỉ hỗ trợ độ theo mặc định.
Pharap

50

Xin lỗi, nhưng tôi khá chắc chắn câu trả lời của Peter là sai. Lưu ý rằng trục y đi xuống trang (phổ biến trong đồ họa). Do đó, phép tính deltaY phải được đảo ngược hoặc bạn nhận được câu trả lời sai.

Xem xét:

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)));

cho

45.0
-45.0
135.0
-135.0

Vì vậy, nếu trong ví dụ trên, P1 là (1,1) và P2 là (2,2) [vì Y tăng xuống trang], mã ở trên sẽ cung cấp 45,0 độ cho ví dụ được hiển thị, đó là sai. Thay đổi thứ tự tính toán deltaY và nó hoạt động đúng.


3
Tôi đã đảo ngược nó như bạn đề nghị và vòng quay của tôi đã bị ngược.
Người ủng hộ của quỷ

1
Trong mã của tôi, tôi sửa lỗi này với: 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;
Marcus Becker

Nó phụ thuộc vào một phần tư của vòng tròn mà góc của bạn nằm trong: Nếu bạn trong quý đầu tiên (tối đa 90 độ) sử dụng các giá trị dương cho deltaX và deltaY (Math.abs), trong lần sử dụng thứ hai (90-180) phủ định giá trị trừu tượng của deltaX, trong phần ba (180-270) phủ định cả deltaX và deltaY và int thứ tư (270-360) chỉ phủ định deltaY - xem câu trả lời của tôi dưới đây
mamashare 7/2/2017

1

Tôi đã tìm thấy một giải pháp trong Python đang hoạt động tốt!

from math import atan2,degrees

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

print GetAngleOfLineBetweenTwoPoints(1,3)

1

Xem xét câu hỏi chính xác, đưa chúng ta vào một hệ tọa độ "đặc biệt" trong đó trục dương có nghĩa là di chuyển XUỐNG (như màn hình hoặc giao diện), bạn cần điều chỉnh chức năng này như thế này và phủ định tọa độ Y:

Ví dụ trong 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
}

Hàm này cho câu trả lời đúng cho câu hỏi. Câu trả lời là bằng radian, vì vậy cách sử dụng, để xem các góc theo độ, là:

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

Dựa trên tài liệu tham khảo "Peter O" .. Đây là phiên bản 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

chức năng 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

Một công thức cho một góc từ 0 đến 2pi.

Có x = x2-x1 và y = y2-y1. Công thức đang hoạt động cho

bất kỳ giá trị nào của x và y. Với x = y = 0, kết quả không xác định.

f (x, y) = pi () - pi () / 2 * (1 + dấu (x)) * (1 ký (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;

Mã của bạn không có ý nghĩa: khác (270-360) .. gì?
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

Kiểm tra

Để thử nghiệm tôi để giả thuyết tạo ra các trường hợp thử nghiệm.

nhập mô tả hình ảnh ở đây

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.