Como calcular o ângulo entre uma linha e o eixo horizontal?

247

Em uma linguagem de programação (Python, C #, etc), preciso determinar como calcular o ângulo entre uma linha e o eixo horizontal?

Eu acho que uma imagem descreve melhor o que eu quero:

nenhuma palavra pode descrever isso

Dado (P1 x , P1 y ) e (P2 x , P2 y ) qual é a melhor maneira de calcular esse ângulo? A origem está no topleft e apenas o quadrante positivo é usado.

orlp
fonte
Veja também: A mesma pergunta para JavaScript
Martin Thoma

Respostas:

387

Primeiro encontre a diferença entre o ponto inicial e o ponto final (aqui, este é mais um segmento de linha direcionado, não uma "linha", pois as linhas se estendem infinitamente e não iniciam em um ponto específico).

deltaY = P2_y - P1_y
deltaX = P2_x - P1_x

Em seguida, calcule o ângulo (que vai do eixo X positivo em P1ao eixo Y positivo em P1).

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

Mas arctanpode não ser o ideal, porque dividir as diferenças dessa maneira apagará a distinção necessária para distinguir em qual quadrante o ângulo está (veja abaixo). Use o seguinte se o seu idioma incluir uma atan2função:

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

EDIT (22 de fevereiro de 2017): No entanto, em geral, ligar atan2(deltaY,deltaX)apenas para obter o ângulo adequado cose sinpode ser deselegante. Nesses casos, muitas vezes você pode fazer o seguinte:

  1. Trate (deltaX, deltaY)como um vetor.
  2. Normalize esse vetor para um vetor de unidade. Para fazer isso, divida deltaXe deltaYpelo comprimento do vetor ( sqrt(deltaX*deltaX+deltaY*deltaY)), a menos que o comprimento seja 0.
  3. Depois disso, deltaXserá agora o cosseno do ângulo entre o vetor e o eixo horizontal (na direção do eixo X positivo para o eixo Y positivo em P1).
  4. E deltaYagora será o seno desse ângulo.
  5. Se o comprimento do vetor for 0, ele não terá um ângulo entre ele e o eixo horizontal (portanto, não terá um seno e cosseno significativos).

EDIT (28 de fevereiro de 2017): Mesmo sem normalizar (deltaX, deltaY):

  • O sinal de deltaXindica se o cosseno descrito na etapa 3 é positivo ou negativo.
  • O sinal de deltaYindica se o seno descrito na etapa 4 é positivo ou negativo.
  • Os sinais de deltaXe deltaYlhe dirão em que quadrante o ângulo está, em relação ao eixo X positivo em P1:
    • +deltaX, +deltaY: 0 a 90 graus.
    • -deltaX, +deltaY: 90 a 180 graus.
    • -deltaX, -deltaY: 180 a 270 graus (-180 a -90 graus).
    • +deltaX, -deltaY: 270 a 360 graus (-90 a 0 graus).

Uma implementação em Python usando radianos (fornecida em 19 de julho de 2015 por Eric Leschinski, que editou minha resposta):

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)

Todos os testes são aprovados. Consulte https://en.wikipedia.org/wiki/Unit_circle

Peter O.
fonte
35
Se você encontrou isso e está usando o JAVASCRiPT, é muito importante observar que Math.sin e Math.cos recebem radianos para que você não precise converter o resultado em graus! Portanto, ignore o bit * 180 / PI. Levei 4 horas para descobrir isso. :)
sidonaldson
O que se deve usar para calcular o ângulo ao longo do eixo vertical?
ZeMoon 31/01
3
@akashg 90 - angleInDegrees :?
jbaums
Por que precisamos fazer 90 - angleInDegrees, existe alguma razão para isso? Por favor, esclareça o mesmo.
Praveen Matanam
2
@sidonaldson É mais do que apenas Javascript, é C, C #, C ++, Java etc. Na verdade, ouso dizer que a maioria das línguas tem sua biblioteca de matemática trabalhando principalmente com radianos. Ainda não vi um idioma que suporte apenas graus por padrão.
Pharap
50

Desculpe, mas tenho certeza de que a resposta de Peter está errada. Observe que o eixo y desce a página (comum em gráficos). Como tal, o cálculo deltaY deve ser revertido ou você recebe a resposta errada.

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

45.0
-45.0
135.0
-135.0

Portanto, se no exemplo acima, P1 é (1,1) e P2 é (2,2) [porque Y aumenta a página], o código acima dará 45,0 graus para o exemplo mostrado, o que está errado. Altere a ordem do cálculo deltaY e ele funcionará corretamente.

user1641082
fonte
3
Invertai como você sugeriu e minha rotação foi para trás.
Advogado do dia
1
No meu código, eu corrijo isso com: 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 do quarto do círculo em que você está o ângulo: se você estiver no primeiro trimestre (até 90 graus) use valores positivos para deltaX e deltaY (Math.abs), no segundo (90-180) use um nega o valor sumário de deltaX, no terceiro (180-270) anular tanto deltaX e deltaY e int o quarto negate (270-360), apenas deltaY - ver abaixo a minha resposta
mamashare
1

Eu encontrei uma solução em Python que está funcionando bem!

from math import atan2,degrees

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

print GetAngleOfLineBetweenTwoPoints(1,3)
dctremblay
fonte
1

Considerando a pergunta exata, colocando-nos em um sistema de coordenadas "especial" em que eixo positivo significa mover para baixo (como uma tela ou uma exibição de interface), você precisa adaptar esta função assim e negar as coordenadas Y:

Exemplo no 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 função fornece uma resposta correta para a pergunta. A resposta está em radianos, portanto, o uso, para visualizar ângulos em graus, é:

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

Com base na referência "Peter O" .. Aqui está a versão 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
fonte
0

função 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
fonte
0

Uma fórmula para um ângulo de 0 a 2pi.

Se x = x2-x1 e y = y2-y1, a fórmula está funcionando para

qualquer valor de x e y. Para x = y = 0, o resultado é indefinido.

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

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

     -sign(x*y)*atan((abs(x)-abs(y))/(abs(x)+abs(y)))
theodore panagos
fonte
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
fonte
Seu código não faz sentido: else (270-360) .. o quê?
WDUK 05/11/19
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

Teste

Para testar eu deixei hipótese gerar casos de teste.

insira a descrição da imagem aqui

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
fonte