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.


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


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

dá

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.


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)

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

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

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

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

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;

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.