Wie berechnet man den Winkel zwischen einer Linie und der horizontalen Achse?

247

In einer Programmiersprache (Python, C # usw.) muss ich bestimmen, wie der Winkel zwischen einer Linie und der horizontalen Achse berechnet wird.

Ich denke, ein Bild beschreibt am besten, was ich will:

Keine Worte können dies beschreiben

Was ist bei (P1 x , P1 y ) und (P2 x , P2 y ) der beste Weg, um diesen Winkel zu berechnen? Der Ursprung liegt oben links und es wird nur der positive Quadrant verwendet.

orlp
quelle
Siehe auch: Gleiche Frage für JavaScript
Martin Thoma

Antworten:

387

Finden Sie zuerst den Unterschied zwischen dem Startpunkt und dem Endpunkt (hier handelt es sich eher um ein gerichtetes Liniensegment, nicht um eine "Linie", da sich Linien unendlich erstrecken und nicht an einem bestimmten Punkt beginnen).

deltaY = P2_y - P1_y
deltaX = P2_x - P1_x

Berechnen Sie dann den Winkel (der von der positiven X-Achse bei P1zur positiven Y-Achse bei verläuft P1).

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

Dies ist jedoch arctanmöglicherweise nicht ideal, da durch die Aufteilung der Unterschiede auf diese Weise die Unterscheidung aufgehoben wird, die zur Unterscheidung des Quadranten erforderlich ist, in dem sich der Winkel befindet (siehe unten). Verwenden Sie stattdessen Folgendes, wenn Ihre Sprache eine atan2Funktion enthält:

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

BEARBEITEN (22. Februar 2017): Im Allgemeinen wird jedoch atan2(deltaY,deltaX)nur aufgerufen , um den richtigen Winkel für cosund sinmöglicherweise unelegant zu finden. In diesen Fällen können Sie stattdessen häufig Folgendes tun:

  1. Behandeln Sie (deltaX, deltaY)als Vektor.
  2. Normalisieren Sie diesen Vektor zu einem Einheitsvektor. Teilen Sie dazu deltaXund deltaYdurch die Länge des Vektors ( sqrt(deltaX*deltaX+deltaY*deltaY)), es sei denn, die Länge ist 0.
  3. Danach ist deltaXnun der Kosinus des Winkels zwischen dem Vektor und der horizontalen Achse (in Richtung von der positiven X- zur positiven Y-Achse bei P1).
  4. Und deltaYwird jetzt der Sinus dieses Winkels sein.
  5. Wenn die Länge des Vektors 0 ist, hat er keinen Winkel zwischen ihm und der horizontalen Achse (also keinen aussagekräftigen Sinus und Cosinus).

EDIT (28. Februar 2017): Auch ohne Normalisierung (deltaX, deltaY):

  • Das Vorzeichen von deltaXzeigt an, ob der in Schritt 3 beschriebene Cosinus positiv oder negativ ist.
  • Das Vorzeichen von deltaYzeigt an, ob der in Schritt 4 beschriebene Sinus positiv oder negativ ist.
  • Die Vorzeichen deltaXund deltaYgeben an, in welchem ​​Quadranten sich der Winkel in Bezug auf die positive X-Achse befindet P1:
    • +deltaX, +deltaY: 0 bis 90 Grad.
    • -deltaX, +deltaY: 90 bis 180 Grad.
    • -deltaX, -deltaY: 180 bis 270 Grad (-180 bis -90 Grad).
    • +deltaX, -deltaY: 270 bis 360 Grad (-90 bis 0 Grad).

Eine Implementierung in Python mit Bogenmaß (bereitgestellt am 19. Juli 2015 von Eric Leschinski, der meine Antwort bearbeitet hat):

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)

Alle Tests bestehen. Siehe https://en.wikipedia.org/wiki/Unit_circle

Peter O.
quelle
35
Wenn Sie dies gefunden haben und JAVASCRiPT verwenden, ist es sehr wichtig zu beachten, dass Math.sin und Math.cos Bogenmaß verwenden, damit Sie das Ergebnis nicht in Grad umrechnen müssen! Ignorieren Sie also das * 180 / PI-Bit. Ich habe 4 Stunden gebraucht, um das herauszufinden. :)
Sidonaldson
Was sollte man verwenden, um den Winkel entlang der vertikalen Achse zu berechnen?
ZeMoon
3
@akashg : 90 - angleInDegrees ?
Jbaums
Warum müssen wir 90 - angleInDegrees machen, gibt es einen Grund dafür? Bitte klären Sie das gleiche.
Praveen Matanam
2
@sidonaldson Es ist mehr als nur Javascript, es ist C, C #, C ++, Java usw. Tatsächlich wage ich zu sagen, dass die meisten Sprachen ihre Mathematikbibliothek hauptsächlich mit Bogenmaß arbeiten. Ich habe noch keine Sprache gefunden, die standardmäßig nur Abschlüsse unterstützt.
Pharap
50

Entschuldigung, aber ich bin mir ziemlich sicher, dass Peters Antwort falsch ist. Beachten Sie, dass die y-Achse die Seite entlang verläuft (häufig in Grafiken). Daher muss die DeltaY-Berechnung umgekehrt werden, sonst erhalten Sie die falsche Antwort.

Erwägen:

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

gibt

45.0
-45.0
135.0
-135.0

Wenn also im obigen Beispiel P1 (1,1) und P2 (2,2) ist [weil Y auf der Seite zunimmt], gibt der obige Code für das gezeigte Beispiel 45,0 Grad an, was falsch ist. Ändern Sie die Reihenfolge der DeltaY-Berechnung und sie funktioniert ordnungsgemäß.

user1641082
quelle
3
Ich habe es umgekehrt, wie Sie vorgeschlagen haben, und meine Drehung war rückwärts.
Devil's Advocate
1
In meinem Code behebe ich dies mit: 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
Dies hängt vom Viertel des Kreises ab, in dem sich Ihr Winkel befindet: Wenn Sie im ersten Viertel (bis zu 90 Grad) sind, verwenden Sie positive Werte für deltaX und deltaY (Math.abs), im zweiten (90-180) a negiere den abstrakten Wert von deltaX, negiere im dritten (180-270) sowohl deltaX als auch deltaY und int im vierten (270-360) negiere nur deltaY - siehe meine Antwort unten
mamashare
1

Ich habe in Python eine Lösung gefunden, die gut funktioniert!

from math import atan2,degrees

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

print GetAngleOfLineBetweenTwoPoints(1,3)
dctremblay
quelle
1

Wenn Sie die genaue Frage berücksichtigen und uns in ein "spezielles" Koordinatensystem versetzen, in dem eine positive Achse bedeutet, dass Sie sich nach UNTEN bewegen (wie ein Bildschirm oder eine Schnittstellenansicht), müssen Sie diese Funktion wie folgt anpassen und die Y-Koordinaten negativ machen:

Beispiel in 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
}

Diese Funktion gibt eine korrekte Antwort auf die Frage. Die Antwort ist im Bogenmaß, daher lautet die Verwendung zum Anzeigen von Winkeln in Grad:

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
quelle
0

Basierend auf der Referenz "Peter O". Hier ist die Java-Version

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
quelle
0

Matlab-Funktion:

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
quelle
0

Eine Formel für einen Winkel von 0 bis 2 pi.

Es gibt x = x2-x1 und y = y2-y1. Die Formel funktioniert für

ein beliebiger Wert von x und y. Für x = y = 0 ist das Ergebnis undefiniert.

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

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

     -sign(x*y)*atan((abs(x)-abs(y))/(abs(x)+abs(y)))
Theodore Panagos
quelle
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
quelle
Ihr Code macht keinen Sinn: sonst (270-360) .. was?
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

Testen

Zum Testen lasse ich die Hypothese Testfälle generieren.

Geben Sie hier die Bildbeschreibung ein

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
quelle