¿Cómo calcular el ángulo entre una línea y el eje horizontal?

247

En un lenguaje de programación (Python, C #, etc.) necesito determinar cómo calcular el ángulo entre una línea y el eje horizontal.

Creo que una imagen describe mejor lo que quiero:

no hay palabras para describir esto

Dado (P1 x , P1 y ) y (P2 x , P2 y ), ¿cuál es la mejor manera de calcular este ángulo? El origen está en el topleft y solo se utiliza el cuadrante positivo.

orlp
fuente
Ver también: misma pregunta para JavaScript
Martin Thoma

Respuestas:

387

Primero encuentre la diferencia entre el punto inicial y el punto final (aquí, esto es más un segmento de línea dirigida, no una "línea", ya que las líneas se extienden infinitamente y no comienzan en un punto en particular).

deltaY = P2_y - P1_y
deltaX = P2_x - P1_x

Luego calcule el ángulo (que se extiende desde el eje X positivo en P1el eje Y positivo en P1).

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

Pero arctanpuede no ser ideal, porque dividir las diferencias de esta manera borrará la distinción necesaria para distinguir en qué cuadrante se encuentra el ángulo (ver más abajo). Utilice lo siguiente si su idioma incluye una atan2función:

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

EDITAR (22 de febrero de 2017): en general, sin embargo, llamar atan2(deltaY,deltaX)solo para obtener el ángulo adecuado cosy sinpuede ser poco elegante. En esos casos, a menudo puede hacer lo siguiente:

  1. Tratar (deltaX, deltaY)como un vector.
  2. Normalice ese vector a un vector unitario. Para hacerlo, divida deltaXy deltaYpor la longitud del vector ( sqrt(deltaX*deltaX+deltaY*deltaY)), a menos que la longitud sea 0.
  3. Después de eso, deltaXahora será el coseno del ángulo entre el vector y el eje horizontal (en la dirección del eje X positivo al eje Y positivo en P1).
  4. Y deltaYahora será el seno de ese ángulo.
  5. Si la longitud del vector es 0, no tendrá un ángulo entre él y el eje horizontal (por lo que no tendrá un seno y un coseno significativos).

EDITAR (28 de febrero de 2017): incluso sin normalizar (deltaX, deltaY):

  • El signo de deltaXle indicará si el coseno descrito en el paso 3 es positivo o negativo.
  • El signo de deltaYle indicará si el seno descrito en el paso 4 es positivo o negativo.
  • Los signos de deltaXy deltaYle indicarán en qué cuadrante se encuentra el ángulo, en relación con el eje X positivo en P1:
    • +deltaX, +deltaY: 0 a 90 grados.
    • -deltaX, +deltaY: 90 a 180 grados.
    • -deltaX, -deltaY: 180 a 270 grados (-180 a -90 grados).
    • +deltaX, -deltaY: 270 a 360 grados (-90 a 0 grados).

Una implementación en Python usando radianes (proporcionada el 19 de julio de 2015 por Eric Leschinski, quien editó mi respuesta):

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)

Todas las pruebas pasan. Ver https://en.wikipedia.org/wiki/Unit_circle

Peter O.
fuente
35
Si encontró esto y está usando JAVASCRiPT, es muy importante tener en cuenta que Math.sin y Math.cos toman radianes, por lo que no necesita convertir el resultado en grados. Ignora el bit * 180 / PI. Me llevó 4 horas descubrirlo. :)
sidonaldson
¿Qué se debe usar para calcular el ángulo a lo largo del eje vertical?
ZeMoon
3
@akashg 90 - angleInDegrees :?
jbaums
¿Por qué necesitamos hacer 90 - angleInDegrees, hay alguna razón para ello? Por favor aclarar lo mismo.
Praveen Matanam
2
@sidonaldson Es más que solo Javascript, es C, C #, C ++, Java, etc. De hecho, me atrevo a decir que la mayoría de los idiomas tienen su biblioteca de matemáticas trabajando principalmente con radianes. Todavía tengo que ver un idioma que solo admite grados de forma predeterminada.
Pharap
50

Lo siento, pero estoy bastante seguro de que la respuesta de Peter es incorrecta. Tenga en cuenta que el eje y desciende por la página (común en gráficos). Como tal, el cálculo deltaY tiene que revertirse, o se obtiene la respuesta incorrecta.

Considerar:

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

da

45.0
-45.0
135.0
-135.0

Entonces, si en el ejemplo anterior, P1 es (1,1) y P2 es (2,2) [porque Y aumenta en la página], el código anterior dará 45.0 grados para el ejemplo que se muestra, lo cual es incorrecto. Cambie el orden del cálculo deltaY y funciona correctamente.

usuario1641082
fuente
3
Lo invertí como sugeriste y mi rotación fue hacia atrás.
Abogado del Diablo
1
En mi código soluciono esto 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
Depende del cuarto del círculo en el que se encuentre su ángulo: si está en el primer trimestre (hasta 90 grados) use valores positivos para deltaX y deltaY (Math.abs), en el segundo (90-180) use a negar el valor abstracto de deltaX, en el tercero (180-270) niega tanto deltaX como deltaY e int en el cuarto (270-360) niega solo deltaY - vea mi respuesta a continuación
mamashare
1

¡Encontré una solución en Python que funciona bien!

from math import atan2,degrees

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

print GetAngleOfLineBetweenTwoPoints(1,3)
dctremblay
fuente
1

Teniendo en cuenta la pregunta exacta, al colocarnos en un sistema de coordenadas "especial" donde el eje positivo significa moverse hacia ABAJO (como una pantalla o una vista de interfaz), debe adaptar esta función de esta manera y las coordenadas Y negativas:

Ejemplo en 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
}

Esta función da una respuesta correcta a la pregunta. La respuesta está en radianes, por lo que el uso para ver ángulos en grados es:

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

Basado en la referencia "Peter O". Aquí está la versión de 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
fuente
0

función 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
fuente
0

Una fórmula para un ángulo de 0 a 2pi.

Hay x = x2-x1 e y = y2-y1. La fórmula está funcionando para

cualquier valor de x e y. Para x = y = 0 el resultado es indefinido.

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

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

     -sign(x*y)*atan((abs(x)-abs(y))/(abs(x)+abs(y)))
Theodore Panagos
fuente
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
fuente
Su código no tiene sentido: más (270-360) ... ¿qué?
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

Pruebas

Para probar, dejo que la hipótesis genere casos de prueba.

ingrese la descripción de la imagen aquí

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
fuente