วิธีการคำนวณมุมระหว่างเส้นและแกนนอน?


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

แก้ไข (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 กรกฎาคม 2558 โดย Eric Leschinski ผู้แก้ไขคำตอบของฉัน):

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 เป็นต้นอันที่จริงฉันกล้าพูดว่าภาษาส่วนใหญ่มีห้องสมุดคณิตศาสตร์ของพวกเขาทำงานเป็นหลักกับเรเดียน ฉันยังไม่เห็นภาษาที่รองรับองศาโดยปริยาย
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 องศาสำหรับตัวอย่างที่แสดงซึ่งผิด เปลี่ยนลำดับของการคำนวณเดลต้าและทำงานอย่างถูกต้อง


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;
Marcus Becker

ขึ้นอยู่กับหนึ่งในสี่ของวงกลมที่คุณอยู่มุม: ถ้าคุณอยู่ในช่วงไตรมาสแรก (สูงสุด 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)

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

อ้างอิงจากการอ้างอิง "Peter O" .. นี่คือเวอร์ชั่นของจาวา

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

มี x = x2-x1 และ y = y2-y1 สูตรใช้งานได้

ค่าใด ๆ ของ x และ y สำหรับ x = y = 0 ผลลัพธ์จะไม่ถูกกำหนด

f (x, y) = 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.