Come calcolare l'angolo tra una linea e l'asse orizzontale?


247

In un linguaggio di programmazione (Python, C #, ecc.) Devo determinare come calcolare l'angolo tra una linea e l'asse orizzontale?

Penso che un'immagine descriva al meglio ciò che voglio:

nessuna parola può descriverlo

Dato (P1 x , P1 y ) e (P2 x , P2 y ) qual è il modo migliore per calcolare questo angolo? L'origine è nella parte superiore sinistra e viene utilizzato solo il quadrante positivo.


Risposte:


387

Per prima cosa trova la differenza tra il punto iniziale e il punto finale (qui, si tratta più di un segmento di linea diretta, non di una "linea", poiché le linee si estendono all'infinito e non iniziano in un punto particolare).

deltaY = P2_y - P1_y
deltaX = P2_x - P1_x

Quindi calcola l'angolo (che corre dall'asse X P1positivo all'asse Y positivo a P1).

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

Ma arctanpotrebbe non essere l'ideale, perché dividere le differenze in questo modo cancellerà la distinzione necessaria per distinguere in quale quadrante si trova l'angolo (vedi sotto). Utilizzare invece quanto segue se la propria lingua include una atan2funzione:

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

EDIT (22 febbraio 2017): in generale, tuttavia, chiamare atan2(deltaY,deltaX)solo per ottenere l'angolazione corretta cose sinpotrebbe non essere elegante. In questi casi, è spesso possibile invece procedere come segue:

  1. Tratta (deltaX, deltaY)come un vettore.
  2. Normalizza quel vettore su un vettore unitario. Per fare ciò, dividi deltaXe deltaYper la lunghezza del vettore ( sqrt(deltaX*deltaX+deltaY*deltaY)), a meno che la lunghezza sia 0.
  3. Successivamente, deltaXsarà ora il coseno dell'angolo tra il vettore e l'asse orizzontale (nella direzione dall'asse X positivo all'asse Y positivo a P1).
  4. E deltaYora sarà il seno di quell'angolo.
  5. Se la lunghezza del vettore è 0, non avrà un angolo tra esso e l'asse orizzontale (quindi non avrà un seno e un coseno significativi).

EDIT (28 febbraio 2017): Anche senza normalizzare (deltaX, deltaY):

  • Il segno di deltaXti dirà se il coseno descritto nel passaggio 3 è positivo o negativo.
  • Il segno di deltaYti dirà se il seno descritto al punto 4 è positivo o negativo.
  • I segni di deltaXe deltaYti diranno in quale quadrante si trova l'angolo, in relazione all'asse X positivo a P1:
    • +deltaX, +deltaY: Da 0 a 90 gradi.
    • -deltaX, +deltaY: Da 90 a 180 gradi.
    • -deltaX, -deltaY: Da 180 a 270 gradi (da -180 a -90 gradi).
    • +deltaX, -deltaY: Da 270 a 360 gradi (da -90 a 0 gradi).

Un'implementazione in Python usando i radianti (fornita il 19 luglio 2015 da Eric Leschinski, che ha modificato la mia risposta):

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)

Tutti i test passano. Vedi https://en.wikipedia.org/wiki/Unit_circle


35
Se l'hai trovato e stai usando JAVASCRiPT, è molto importante notare che Math.sin e Math.cos prendono i radianti, quindi non è necessario convertire il risultato in gradi! Quindi ignora il bit * 180 / PI. Mi ci sono volute 4 ore per scoprirlo. :)
sidonaldson,

Cosa si dovrebbe usare per calcolare l'angolo lungo l'asse verticale?
ZeMoon

3
@akashg: 90 - angleInDegrees ?
jbaums,

Perché dobbiamo fare 90 - angleInDegrees, c'è qualche motivo per farlo? Si prega di chiarire lo stesso.
Praveen Matanam,

2
@sidonaldson È più di un semplice Javascript, è C, C #, C ++, Java ecc. In effetti, oso dire che la maggior parte delle lingue ha una libreria matematica che lavora principalmente con i radianti. Devo ancora vedere una lingua che supporta solo i gradi di default.
Pharap,

50

Scusa, ma sono abbastanza sicuro che la risposta di Peter sia sbagliata. Si noti che l'asse y scende nella pagina (comune nella grafica). Pertanto, il calcolo deltaY deve essere invertito o si ottiene la risposta sbagliata.

Tener conto di:

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

Quindi, se nell'esempio sopra, P1 è (1,1) e P2 è (2,2) [perché Y aumenta nella pagina], il codice sopra fornirà 45.0 gradi per l'esempio mostrato, il che è sbagliato. Cambia l'ordine del calcolo deltaY e funziona correttamente.


3
L'ho invertito come mi hai suggerito e la mia rotazione era all'indietro.
Devil's Advocate,

1
Nel mio codice lo risolvo con: 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,

Dipende dal quarto del cerchio in cui si trova l'angolo: se sei nel primo trimestre (fino a 90 gradi) usa valori positivi per deltaX e deltaY (Math.abs), nel secondo (90-180) usa a nega il valore astratto di deltaX, nel terzo (180-270) nega sia deltaX che deltaY e int il quarto (270-360) nega solo il deltaY - vedi la mia risposta di seguito
mamashare

1

Ho trovato una soluzione in Python che funziona bene!

from math import atan2,degrees

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

print GetAngleOfLineBetweenTwoPoints(1,3)

1

Considerando la domanda esatta, mettendoci in un sistema di coordinate "speciale" in cui l'asse positivo significa spostarsi verso il basso (come una schermata o una vista dell'interfaccia), è necessario adattare questa funzione in questo modo e negare le coordinate Y:

Esempio 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
}

Questa funzione fornisce una risposta corretta alla domanda. La risposta è in radianti, quindi l'uso, per visualizzare gli angoli in gradi, è:

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

Basato sul riferimento "Peter O" .. Ecco la versione 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

funzione 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

Una formula per un angolo da 0 a 2pi.

Esistono x = x2-x1 e y = y2-y1 per cui la formula funziona

qualsiasi valore di xe y. Per x = y = 0 il risultato non è definito.

f (x, y) = pi () - pi () / 2 * (1 + segno (x)) * (1-sign (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;

Il tuo codice non ha senso: else (270-360) .. cosa?
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

analisi

Per i test ho lasciato che l' ipotesi generasse casi di test.

inserisci qui la descrizione dell'immagine

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.