Bagaimana cara menghitung sudut antara garis dan sumbu horizontal?

247

Dalam bahasa pemrograman (Python, C #, dll) saya perlu menentukan cara menghitung sudut antara garis dan sumbu horizontal?

Saya pikir gambar menggambarkan yang terbaik yang saya inginkan:

tidak ada kata yang bisa menggambarkan ini

Diberikan (P1 x , P1 y ) dan (P2 x , P2 y ) apa cara terbaik untuk menghitung sudut ini? Asalnya ada di topleft dan hanya kuadran positif yang digunakan.

orlp
sumber
Lihat juga: Pertanyaan yang sama untuk JavaScript
Martin Thoma

Jawaban:

387

Pertama-tama temukan perbedaan antara titik awal dan titik akhir (di sini, ini lebih merupakan segmen garis terarah, bukan "garis", karena garis memanjang tanpa batas dan tidak memulai pada titik tertentu).

deltaY = P2_y - P1_y
deltaX = P2_x - P1_x

Kemudian hitung sudutnya (yang berjalan dari sumbu X positif di P1ke sumbu Y positif di P1).

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

Tetapi arctanmungkin tidak ideal, karena membagi perbedaan dengan cara ini akan menghapus perbedaan yang diperlukan untuk membedakan kuadran mana sudut berada (lihat di bawah). Gunakan yang berikut sebagai gantinya jika bahasa Anda menyertakan suatu atan2fungsi:

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

EDIT (22 Februari 2017): Namun, secara umum, panggilan atan2(deltaY,deltaX)hanya untuk mendapatkan sudut yang tepat cosdan sinmungkin tidak tepat. Dalam kasus tersebut, Anda dapat sering melakukan hal berikut sebagai gantinya:

  1. Perlakukan (deltaX, deltaY)sebagai vektor.
  2. Normalisasi vektor itu menjadi vektor satuan. Untuk melakukannya, bagi deltaXdan deltaYdengan panjang vektor ( sqrt(deltaX*deltaX+deltaY*deltaY)), kecuali panjangnya adalah 0.
  3. Setelah itu, deltaXsekarang akan menjadi cosinus sudut antara vektor dan sumbu horizontal (dalam arah dari X positif ke sumbu Y positif di P1).
  4. Dan deltaYsekarang akan menjadi sinus sudut itu.
  5. Jika panjang vektor adalah 0, ia tidak akan memiliki sudut antara itu dan sumbu horizontal (sehingga tidak akan memiliki sinus dan kosinus yang berarti).

EDIT (28 Februari 2017): Bahkan tanpa menormalkan (deltaX, deltaY):

  • Tanda deltaXakan memberi tahu Anda apakah kosinus yang dijelaskan pada langkah 3 positif atau negatif.
  • Tanda deltaYakan memberi tahu Anda apakah sinus yang dijelaskan pada langkah 4 positif atau negatif.
  • Tanda-tanda deltaXdan deltaYakan memberi tahu Anda kuadran mana sudut berada, dalam kaitannya dengan sumbu X positif di P1:
    • +deltaX, +deltaY: 0 hingga 90 derajat.
    • -deltaX, +deltaY: 90 hingga 180 derajat.
    • -deltaX, -deltaY: 180 hingga 270 derajat (-180 hingga -90 derajat).
    • +deltaX, -deltaY: 270 hingga 360 derajat (-90 hingga 0 derajat).

Implementasi di Python menggunakan radian (disediakan pada 19 Juli 2015 oleh Eric Leschinski, yang mengedit jawaban saya):

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)

Semua tes lulus. Lihat https://en.wikipedia.org/wiki/Unit_circle

Peter O.
sumber
35
Jika Anda menemukan ini dan Anda menggunakan JAVASCRiPT, sangat penting untuk mencatat bahwa Math.sin dan Math.cos mengambil radian sehingga Anda tidak perlu mengubah hasilnya menjadi derajat! Jadi abaikan bit * 180 / PI. Butuh waktu 4 jam untuk mengetahuinya. :)
sidonaldson
Apa yang harus digunakan untuk menghitung sudut sepanjang sumbu vertikal?
ZeMoon
3
@akashg: 90 - angleInDegrees ?
jbaums
Mengapa kita perlu melakukan 90 - angleInDegrees, apakah ada alasan untuk itu? Tolong jelaskan hal yang sama.
Praveen Matanam
2
@idonaldson Ini lebih dari sekadar Javascript, C, C #, C ++, Java, dll. Sebenarnya saya berani mengatakan bahwa sebagian besar bahasa memiliki perpustakaan matematika yang bekerja terutama dengan radian. Saya belum melihat bahasa yang hanya mendukung derajat secara default.
Pharap
50

Maaf, tapi saya cukup yakin jawaban Peter salah. Perhatikan bahwa sumbu y menuruni halaman (umum dalam grafik). Dengan demikian perhitungan deltaY harus dibalik, atau Anda mendapatkan jawaban yang salah.

Mempertimbangkan:

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

memberi

45.0
-45.0
135.0
-135.0

Jadi jika dalam contoh di atas, P1 adalah (1,1) dan P2 adalah (2,2) [karena Y naik turun halaman], kode di atas akan memberikan 45,0 derajat untuk contoh yang ditunjukkan, yang salah. Ubah urutan perhitungan deltaY dan itu berfungsi dengan baik.

pengguna1641082
sumber
3
Saya membalikkannya seperti yang Anda sarankan dan rotasi saya mundur.
Pengacara Setan
1
Dalam kode saya, saya memperbaikinya dengan: 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
Itu tergantung pada seperempat lingkaran di mana sudut Anda berada: Jika Anda di kuartal pertama (hingga 90 derajat) menggunakan nilai positif untuk deltaX dan deltaY (Math.abs), pada yang kedua (90-180) gunakan a meniadakan nilai abstrak deltaX, pada yang ketiga (180-270) meniadakan baik deltaX dan deltaY dan int yang keempat (270-360) meniadakan hanya deltaY - lihat jawaban saya di bawah ini
mashashare
1

Saya telah menemukan solusi dalam Python yang berfungsi dengan baik!

from math import atan2,degrees

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

print GetAngleOfLineBetweenTwoPoints(1,3)
dctremblay
sumber
1

Mempertimbangkan pertanyaan yang tepat, menempatkan kami dalam sistem koordinat "spesial" di mana sumbu positif berarti bergerak BAWAH (seperti tampilan layar atau antarmuka), Anda perlu menyesuaikan fungsi ini seperti ini, dan negatif dengan koordinat Y:

Contoh dalam 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
}

Fungsi ini memberikan jawaban yang benar untuk pertanyaan itu. Jawaban ada dalam radian, jadi penggunaannya, untuk melihat sudut dalam derajat, adalah:

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
philippe
sumber
0

Berdasarkan referensi "Peter O" .. Ini adalah versi 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)); }
Venkateswara Rao
sumber
0

fungsi 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
Benas
sumber
0

Rumus untuk sudut dari 0 hingga 2pi.

Ada x = x2-x1 dan y = y2-y1. Formula berfungsi untuk

nilai x dan y. Untuk x = y = 0 hasilnya tidak ditentukan.

f (x, y) = pi () - pi () / 2 * (1 + tanda (x)) * (1-tanda (y ^ 2))

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

     -sign(x*y)*atan((abs(x)-abs(y))/(abs(x)+abs(y)))
theodore panagos
sumber
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
sumber
Kode Anda tidak masuk akal: lain (270-360) .. apa?
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

Pengujian

Untuk pengujian saya membiarkan hipotesis menghasilkan kasus uji.

masukkan deskripsi gambar di sini

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
Martin Thoma
sumber