262 votes

Comment calculer l'angle entre une ligne et l'axe horizontal ?

Dans un langage de programmation (Python, C#, etc.), je dois déterminer comment calculer l'angle entre une ligne et l'axe horizontal ?

Je pense qu'une image décrit le mieux ce que je veux :

no words can describe this

Étant donné (P1 x ,P1 y ) et (P2 x ,P2 y ) Quelle est la meilleure façon de calculer cet angle ? L'origine se trouve dans la partie supérieure gauche et seul le quadrant positif est utilisé.

0 votes

395voto

Peter O. Points 9967

Trouvez d'abord la différence entre le point de départ et le point d'arrivée (ici, il s'agit plutôt d'un segment de ligne dirigée, et non d'une "ligne", puisque les lignes s'étendent à l'infini et ne commencent pas à un point particulier).

deltaY = P2_y - P1_y
deltaX = P2_x - P1_x

Calculez ensuite l'angle (qui part de l'axe X positif à P1 à l'axe Y positif à P1 ).

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

Mais arctan n'est peut-être pas la solution idéale, car en divisant les différences de cette manière, on efface la distinction nécessaire pour déterminer dans quel quadrant se trouve l'angle (voir ci-dessous). Utilisez plutôt ce qui suit si votre langue comprend un atan2 fonction :

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

EDIT (22 février 2017) : D'une manière générale, toutefois, l'appel atan2(deltaY,deltaX) juste pour obtenir l'angle approprié pour cos y sin peut être inélégant. Dans ce cas, il est souvent possible de procéder comme suit :

  1. Traiter (deltaX, deltaY) comme un vecteur.
  2. Normaliser ce vecteur en un vecteur unitaire. Pour ce faire, divisez deltaX y deltaY par la longueur du vecteur ( sqrt(deltaX*deltaX+deltaY*deltaY) ), sauf si la longueur est égale à 0.
  3. Après cela, deltaX sera maintenant le cosinus de l'angle entre le vecteur et l'axe horizontal (dans la direction de l'axe X positif à l'axe Y positif à P1 ).
  4. Et deltaY sera alors le sinus de cet angle.
  5. Si la longueur du vecteur est de 0, il n'y aura pas d'angle entre lui et l'axe horizontal (il n'y aura donc pas de sinus et de cosinus significatifs).

EDIT (28 février 2017) : Même sans normaliser (deltaX, deltaY) :

  • Le signe de deltaX vous dira si le cosinus décrit à l'étape 3 est positif ou négatif.
  • Le signe de deltaY vous dira si la sinusoïde décrite à l'étape 4 est positive ou négative.
  • Les signes de deltaX y deltaY vous indiquera dans quel quadrant se trouve l'angle par rapport à l'axe X positif à P1 :
    • +deltaX , +deltaY : 0 à 90 degrés.
    • -deltaX , +deltaY Le nombre d'heures de travail peut varier de 90 à 180 degrés.
    • -deltaX , -deltaY : 180 à 270 degrés (-180 à -90 degrés).
    • +deltaX , -deltaY Le nombre d'heures de travail peut varier de 270 à 360 degrés (de -90 à 0 degrés).

Une implémentation en Python utilisant les radians (fournie le 19 juillet 2015 par Eric Leschinski, qui a édité ma réponse) :

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)

Tous les tests sont réussis. Voir https://en.wikipedia.org/wiki/Unit_circle

36 votes

Si vous avez trouvé ceci et que vous utilisez JAVASCRiPT, il est très important de noter que Math.sin et Math.cos prennent des radians et qu'il n'est donc pas nécessaire de convertir le résultat en degrés ! Ignorez donc la partie * 180 / PI. Il m'a fallu 4 heures pour le découvrir :)

0 votes

Que faut-il utiliser pour calculer l'angle le long de l'axe vertical ?

3 votes

@akashg : 90 - angleInDegrees ?

51voto

user1641082 Points 345

Désolé, mais je suis presque sûr que la réponse de Peter est erronée. Notez que l'axe des y descend vers le bas de la page (ce qui est courant dans les graphiques). Le calcul du deltaY doit donc être inversé, sinon vous obtiendrez une mauvaise réponse.

Envisager :

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

donne

45.0
-45.0
135.0
-135.0

Ainsi, si dans l'exemple ci-dessus, P1 est (1,1) et P2 est (2,2) [parce que Y augmente vers le bas de la page], le code ci-dessus donnera 45,0 degrés pour l'exemple montré, ce qui est faux. Changez l'ordre de calcul du deltaY et le code fonctionnera correctement.

3 votes

Je l'ai inversé comme vous l'avez suggéré et ma rotation était inversée.

1 votes

Dans mon code, je corrige cela avec : 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;

0 votes

Cela dépend du quart de cercle dans lequel se trouve votre angle : Si vous êtes dans le premier quart (jusqu'à 90 degrés), utilisez des valeurs positives pour deltaX et deltaY (Math.abs), dans le deuxième (90-180), utilisez une négation de la valeur abstraite de deltaX, dans le troisième (180-270), annulez à la fois deltaX et deltaY et dans le quatrième (270-360), annulez seulement deltaY - voir ma réponse ci-dessous.

4voto

moose Points 4945
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

Essais

Pour les tests, j'ai laissé hypothèse générer des cas de test.

enter image description here

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

2voto

J'ai trouvé une solution en Python qui fonctionne bien !

from math import atan2,degrees

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

print GetAngleOfLineBetweenTwoPoints(1,3)

1voto

Venkateswara Rao Points 2374

Basé sur la référence "Peter O" Voici la version 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)); }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X