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

248

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:

aucun mot ne peut décrire cela

Étant donné (P1 x , P1 y ) et (P2 x , P2 y ) quelle est la meilleure façon de calculer cet angle? L'origine est dans le topleft et seul le quadrant positif est utilisé.

orlp
la source
Voir aussi: Même question pour JavaScript
Martin Thoma

Réponses:

388

Trouvez d'abord la différence entre le point de départ et le point d'arrivée (ici, il s'agit plus d'un segment de ligne dirigé, pas d'une "ligne", car 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 va de l'axe X positif à P1à l'axe Y positif à P1).

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

Mais arctanpeut ne pas être idéal, car la division des différences de cette façon effacera la distinction nécessaire pour distinguer dans quel quadrant l'angle se trouve (voir ci-dessous). Utilisez plutôt ce qui suit si votre langue comprend une atan2fonction:

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

EDIT (22 février 2017): En général, cependant, appeler atan2(deltaY,deltaX)juste pour obtenir l'angle approprié coset sinpeut être inélégant. Dans ces cas, vous pouvez souvent effectuer les opérations suivantes à la place:

  1. Traitez-le (deltaX, deltaY)comme un vecteur.
  2. Normalisez ce vecteur en un vecteur unitaire. Pour ce faire, divisez deltaXet deltaYpar la longueur du vecteur ( sqrt(deltaX*deltaX+deltaY*deltaY)), sauf si la longueur est 0.
  3. Après cela, deltaXsera maintenant le cosinus de l'angle entre le vecteur et l'axe horizontal (dans la direction du X positif vers l'axe Y positif en P1).
  4. Et deltaYsera maintenant le sinus de cet angle.
  5. Si la longueur du vecteur est 0, il n'aura pas d'angle entre lui et l'axe horizontal (il n'aura donc pas de sinus et cosinus significatif).

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

  • Le signe de deltaXvous indiquera si le cosinus décrit à l'étape 3 est positif ou négatif.
  • Le signe de deltaYvous indiquera si le sinus décrit à l'étape 4 est positif ou négatif.
  • Les signes deltaXet deltaYvous indiqueront dans quel quadrant l'angle se trouve, par rapport à l'axe X positif à P1:
    • +deltaX, +deltaY: 0 à 90 degrés.
    • -deltaX, +deltaY: 90 à 180 degrés.
    • -deltaX, -deltaY: 180 à 270 degrés (-180 à -90 degrés).
    • +deltaX, -deltaY: 270 à 360 degrés (-90 à 0 degrés).

Une implémentation en Python utilisant des 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 réussissent. Voir https://en.wikipedia.org/wiki/Unit_circle

Peter O.
la source
35
Si vous l'avez trouvé et que vous utilisez JAVASCRiPT, il est très important de noter que Math.sin et Math.cos prennent des radians, vous n'avez donc pas besoin de convertir le résultat en degrés! Ignorez donc le bit * 180 / PI. Il m'a fallu 4 heures pour le découvrir. :)
sidonaldson
Que faut-il utiliser pour calculer l'angle le long de l'axe vertical?
ZeMoon
3
@akashg: 90 - angleInDegrees ?
jbaums
Pourquoi nous devons faire 90 - angleInDegrees, y a-t-il une raison à cela? Veuillez clarifier la même chose.
Praveen Matanam
2
@sidonaldson C'est plus que juste Javascript, c'est C, C #, C ++, Java etc. En fait, j'ose dire que la majorité des langages ont leur bibliothèque de mathématiques fonctionnant principalement avec des radians. Je n'ai pas encore vu une langue qui ne supporte que les degrés par défaut.
Pharap
50

Désolé, mais je suis sûr que la réponse de Peter est fausse. Notez que l'axe y descend la page (commun dans les graphiques). En tant que tel, le calcul deltaY doit être inversé, ou vous obtenez la mauvaise réponse.

Considérer:

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

Donc, 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 illustré, ce qui est faux. Modifiez l'ordre du calcul deltaY et cela fonctionne correctement.

user1641082
la source
3
Je l'ai inversé comme vous l'avez suggéré et ma rotation a été inversée.
Devil's Advocate
1
Dans mon code, je résous ce problème 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;
Marcus Becker
Cela dépend du quart de cercle dans lequel votre angle est: Si vous êtes au premier trimestre (jusqu'à 90 degrés), utilisez des valeurs positives pour deltaX et deltaY (Math.abs), dans le second (90-180), utilisez a nie la valeur abstraite de deltaX, dans le troisième (180-270) nie à la fois deltaX et deltaY et int le quatrième (270-360) nie seulement deltaY - voir ma réponse ci
mamashare
1

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)
dctremblay
la source
1

Considérant la question exacte, nous plaçant dans un système de coordonnées "spécial" où l'axe positif signifie descendre (comme un écran ou une vue d'interface), vous devez adapter cette fonction comme ceci, et négativement les coordonnées Y:

Exemple dans 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
}

Cette fonction donne une réponse correcte à la question. La réponse est en radians, donc l'utilisation, pour visualiser les angles en degrés, est:

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
la source
0

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)); }
Venkateswara Rao
la source
0

fonction 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
la source
0

Une formule pour un angle de 0 à 2pi.

Il y a x = x2-x1 et y = y2-y1.La formule fonctionne pour

toute valeur de x et y. Pour x = y = 0, le résultat n'est pas défini.

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

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

     -sign(x*y)*atan((abs(x)-abs(y))/(abs(x)+abs(y)))
Theodore Panagos
la source
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
la source
Votre code n'a aucun sens: sinon (270-360) .. quoi?
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

Essai

Pour les tests, je laisse l' hypothèse générer des cas de test.

entrez la description de l'image ici

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
la source