Comment calculer un angle à partir de trois points? [fermé]

120

Disons que vous avez ceci:

P1 = (x=2, y=50)
P2 = (x=9, y=40)
P3 = (x=5, y=20)

Supposons que ce P1soit le point central d'un cercle. C'est toujours pareil. Je veux l'angle qui est constitué par P2et P3, ou en d'autres termes l'angle qui est à côté P1. L'angle intérieur pour être précis. Ce sera toujours un angle aigu, donc inférieur à -90 degrés.

J'ai pensé: Mec, ce sont de simples mathématiques géométriques. Mais je cherche une formule depuis environ 6 heures maintenant, et je ne trouve que des gens qui parlent de trucs compliqués de la NASA comme des arccos et des produits scalaires vectoriels. J'ai l'impression d'être dans un frigo.

Certains gourous des mathématiques ici pensent que c'est un problème simple? Je ne pense pas que le langage de programmation compte ici, mais pour ceux qui le pensent: java et objective-c. J'en ai besoin pour les deux, mais je ne l'ai pas marqué pour ceux-ci.

Lance Roberts
la source

Réponses:

87

Si vous voulez dire l'angle dont P1 est le sommet, l'utilisation de la loi des cosinus devrait fonctionner:

arccos((P 12 2 + P 13 2 - P 23 2 ) / (2 * P 12 * P 13 ))

où P 12 est la longueur du segment de P1 à P2, calculée par

sqrt ((P1 x - P2 x ) 2 + (P1 y - P2 y ) 2 )

Lance Roberts
la source
@Rafa Firenze cos ^ -1 est une notation courante pour acos, mais acos est moins ambiguë. en.wikipedia.org/wiki/Inverse_trigonometric_functions
geon
Je vais quitter l'édition car cela ne fait aucun mal, mais ayant des degrés Math / CS / EE, cos ^ -1 est certainement la notation la plus courante.
Lance Roberts
1
Seule une poignée de langues utilisent un signe curseur pour «puissance de», donc si vous ne voulez pas l'appeler arcos, veuillez simplement taper cos⁻¹. (Si vous utilisez un système d'exploitation commercial qui rend difficile la saisie d'exposants, je pense que vous pourriez acheter des applications keycaps, ou peut-être un plug-in de navigateur que vous pourriez installer. Ou vous pouvez effectuer une recherche sur le Web et copier et coller.)
Michael Scheper
1
@MichaelScheper, je n'utilisais que le curseur dans les commentaires où le html est limité. J'utiliserais certainement simplement la notation sous / exposant dans toute réponse réelle.
Lance Roberts
47

Cela devient très simple si vous pensez que c'est deux vecteurs, un du point P1 à P2 et un de P1 à P3

donc:
a = (p1.x - p2.x, p1.y - p2.y)
b = (p1.x - p3.x, p1.y - p3.y)

Vous pouvez ensuite inverser la formule du produit scalaire:
produit scalaire
pour obtenir l'angle:
angle entre deux vecteurs

Rappelez-vous que cela produit scalairesignifie simplement: a1 * b1 + a2 * b2 (juste 2 dimensions ici ...)

Andrea Ambu
la source
1
Ah magnitude du vecteur
Daniel Little
Vérifiez la solution atan2.
Luc Boissaye
25

La meilleure façon de traiter le calcul de l'angle est d'utiliser le atan2(y, x)fait qu'un point donné x, yrenvoie l'angle à partir de ce point et l' X+axe par rapport à l'origine.

Étant donné que le calcul est

double result = atan2(P3.y - P1.y, P3.x - P1.x) -
                atan2(P2.y - P1.y, P2.x - P1.x);

c'est-à-dire que vous traduisez fondamentalement les deux points par -P1(en d'autres termes, vous traduisez tout pour que cela P1se termine à l'origine) et ensuite vous considérez la différence des angles absolus de P3et de P2.

L'avantage de atan2est que le cercle complet est représenté (vous pouvez obtenir n'importe quel nombre entre -π et π) alors qu'au lieu de cela, acosvous devez gérer plusieurs cas en fonction des signes pour calculer le résultat correct.

Le seul point singulier pour atan2est (0, 0)... ce qui signifie que les deux P2et P3doivent être différents de P1car dans ce cas n'a pas de sens de parler d'un angle.

6502
la source
Merci pour votre réponse. C'était exactement ce que je cherchais. Solution simple et vous pouvez obtenir facilement l'angle anti-horaire si j'ajoute simplement 2pi lorsque la valeur est négative.
Mario
@marcpt: atan2est exactement ce qui est nécessaire pour ce problème, mais il semble que la plupart des gens qui abordent cette question ne peuvent tout simplement pas lire ou ne peuvent pas comprendre pourquoi les acossolutions basées sur des solutions sont mauvaises. Heureusement pour moi, j'ai quitté la phase "quelqu'un a tort sur Internet" ( xkcd.com/386 ) il y a de nombreuses années et je ne vais pas me battre pour défendre l'évidence :-)
6502
Merci de l'avoir signalé, mais pouvez-vous gérer la 3D de cette façon?
nicoco
1
@nicoco: en trois dimensions comment définir l'angle? Plus précisément, l'angle peut-il être négatif ou supérieur à pi (180 degrés)? Deux vecteurs non parallèles en 3D définissent un plan, mais le plan peut être "vu" de deux côtés: vu d'un côté A apparaîtra "à gauche" de B et de l'autre il apparaîtra "à droite". .
6502
@ 6505 Merci pour votre réponse, j'ai posté avant de réfléchir à mon problème. Mais je l'ai compris maintenant.
nicoco
19

Permettez-moi de donner un exemple en JavaScript, je me suis beaucoup battu avec ça:

/**
 * Calculates the angle (in radians) between two vectors pointing outward from one center
 *
 * @param p0 first point
 * @param p1 second point
 * @param c center point
 */
function find_angle(p0,p1,c) {
    var p0c = Math.sqrt(Math.pow(c.x-p0.x,2)+
                        Math.pow(c.y-p0.y,2)); // p0->c (b)   
    var p1c = Math.sqrt(Math.pow(c.x-p1.x,2)+
                        Math.pow(c.y-p1.y,2)); // p1->c (a)
    var p0p1 = Math.sqrt(Math.pow(p1.x-p0.x,2)+
                         Math.pow(p1.y-p0.y,2)); // p0->p1 (c)
    return Math.acos((p1c*p1c+p0c*p0c-p0p1*p0p1)/(2*p1c*p0c));
}

Bonus: Exemple avec HTML5-canvas

shaman.sir
la source
5
Vous pouvez rendre cela plus efficace en faisant moins sqrtet en faisant la quadrature. Voir ma réponse ici (écrite en Ruby), ou dans cette démo mise à jour (JavaScript).
Phrogz
Vous pouvez utiliser atan2 pour une solution plus simple.
Luc Boissaye
15

Fondamentalement, vous avez deux vecteurs, un vecteur de P1 à P2 et un autre de P1 à P3. Il vous suffit donc d'une formule pour calculer l'angle entre deux vecteurs.

Jetez un œil ici pour une bonne explication et la formule.

texte alternatif

André Miller
la source
12

Si vous pensez à P1 comme le centre d'un cercle, vous pensez trop compliqué. Vous avez un triangle simple, donc votre problème peut être résolu avec la loi des cosinus . Pas besoin de toute transformation de coordonnées polaires ou autre. Disons que les distances sont P1-P2 = A, P2-P3 = B et P3-P1 = C:

Angle = arccos ((B ^ 2-A ^ 2-C ^ 2) / 2AC)

Tout ce que vous avez à faire est de calculer la longueur des distances A, B et C.Celles-ci sont facilement disponibles à partir des coordonnées x et y de vos points et du théorème de Pythagore

Longueur = sqrt ((X2-X1) ^ 2 + (Y2-Y1) ^ 2)

Treb
la source
Je ne sais pas comment mettre en œuvre cela car vous traitez P1, etc. comme des valeurs individuelles plutôt que (x, y)
Dominic
@Dominic Tobias: La notation P1-P2 = Ane doit pas être lue comme "Pour calculer A, soustraire P2 de P1", mais comme "Je définis A comme la distance de P1 à P2", qui peut ensuite être calculée en utilisant la deuxième équation. Je voulais juste définir un raccourci pour les distances, pour rendre les équations plus lisibles.
Treb
8

J'ai récemment rencontré un problème similaire, il me suffisait de différencier les angles positifs et négatifs. Au cas où cela serait utile à quiconque, je recommande l'extrait de code que j'ai récupéré dans cette liste de diffusion sur la détection de la rotation sur un événement tactile pour Android:

 @Override
 public boolean onTouchEvent(MotionEvent e) {
    float x = e.getX();
    float y = e.getY();
    switch (e.getAction()) {
    case MotionEvent.ACTION_MOVE:
       //find an approximate angle between them.

       float dx = x-cx;
       float dy = y-cy;
       double a=Math.atan2(dy,dx);

       float dpx= mPreviousX-cx;
       float dpy= mPreviousY-cy;
       double b=Math.atan2(dpy, dpx);

       double diff  = a-b;
       this.bearing -= Math.toDegrees(diff);
       this.invalidate();
    }
    mPreviousX = x;
    mPreviousY = y;
    return true;
 }
Marc
la source
7

Solution géométrique très simple avec explication

Il y a quelques jours, un est tombé dans le même problème et a dû s'asseoir avec le livre de mathématiques. J'ai résolu le problème en combinant et en simplifiant certaines formules de base.


Considérons ce chiffre-

angle

Nous voulons connaître ϴ , donc nous devons d'abord découvrir α et β . Maintenant, pour toute ligne droite-

y = m * x + c

Soit - A = (ax, ay) , B = (bx, by) et O = (ox, oy) . Donc pour la ligne OA -

oy = m1 * ox + c   ⇒ c = oy - m1 * ox   ...(eqn-1)

ay = m1 * ax + c   ⇒ ay = m1 * ax + oy - m1 * ox   [from eqn-1]
                   ⇒ ay = m1 * ax + oy - m1 * ox
                   ⇒ m1 = (ay - oy) / (ax - ox)
                   ⇒ tan α = (ay - oy) / (ax - ox)   [m = slope = tan ϴ]   ...(eqn-2)

De la même manière, pour l' OB de ligne -

tan β = (by - oy) / (bx - ox)   ...(eqn-3)

Maintenant, nous avons besoin ϴ = β - α. En trigonométrie, nous avons une formule-

tan (β-α) = (tan β + tan α) / (1 - tan β * tan α)   ...(eqn-4)

Après avoir remplacé la valeur de tan α(de l'éqn-2) et tan b(de l'éqn-3) dans l'éqn-4, et appliqué la simplification, nous obtenons-

tan (β-α) = ( (ax-ox)*(by-oy)+(ay-oy)*(bx-ox) ) / ( (ax-ox)*(bx-ox)-(ay-oy)*(by-oy) )

Alors,

ϴ = β-α = tan^(-1) ( ((ax-ox)*(by-oy)+(ay-oy)*(bx-ox)) / ((ax-ox)*(bx-ox)-(ay-oy)*(by-oy)) )

C'est ça!


Maintenant, prenez la figure suivante-

angle

Cette méthode C # ou Java calcule l'angle ( ϴ ) -

    private double calculateAngle(double P1X, double P1Y, double P2X, double P2Y,
            double P3X, double P3Y){

        double numerator = P2Y*(P1X-P3X) + P1Y*(P3X-P2X) + P3Y*(P2X-P1X);
        double denominator = (P2X-P1X)*(P1X-P3X) + (P2Y-P1Y)*(P1Y-P3Y);
        double ratio = numerator/denominator;

        double angleRad = Math.Atan(ratio);
        double angleDeg = (angleRad*180)/Math.PI;

        if(angleDeg<0){
            angleDeg = 180+angleDeg;
        }

        return angleDeg;
    }
Minhas Kamal
la source
Comment cette méthode peut-elle être utilisée pour un triangle équilatéral?
Vikrant
1
Eh bien, votre réponse fonctionne bien maintenant. C'était un problème de logique dans ma semaine de code plus tôt.
Vikrant
6

Dans Objective-C, vous pouvez le faire en

float xpoint = (((atan2((newPoint.x - oldPoint.x) , (newPoint.y - oldPoint.y)))*180)/M_PI);

Ou lisez plus ici

Adeem Maqsood Basraa
la source
7
Euh non. Il y a trois points, le centre n'est pas à (0,0), et cela donne un angle d'un triangle rectangle, pas l'angle du sommet. Et quelle sorte de nom est "xpoint" pour un angle?
Jim Balter
4

Vous avez évoqué un angle signé (-90). Dans de nombreuses applications, les angles peuvent avoir des signes (positifs et négatifs, voir http://en.wikipedia.org/wiki/Angle ). Si les points sont (disons) P2 (1,0), P1 (0,0), P3 (0,1) alors l'angle P3-P1-P2 est conventionnellement positif (PI / 2) alors que l'angle P2-P1- P3 est négatif. L'utilisation de la longueur des côtés ne fera pas la distinction entre + et - donc si cela est important, vous devrez utiliser des vecteurs ou une fonction telle que Math.atan2 (a, b).

Les angles peuvent également s'étendre au-delà de 2 * PI et bien que cela ne soit pas pertinent pour la question actuelle, il était suffisamment important que j'écrive ma propre classe Angle (également pour m'assurer que les degrés et les radians ne se mélangent pas). La question de savoir si l'angle1 est inférieur à l'angle2 dépend essentiellement de la façon dont les angles sont définis. Il peut également être important de décider si une ligne (-1,0) (0,0) (1,0) est représentée par Math.PI ou -Math.PI

peter.murray.rust
la source
4

mon programme de démonstration d'angle

Récemment, j'ai moi aussi le même problème ... En Delphi, c'est très similaire à Objective-C.

procedure TForm1.FormPaint(Sender: TObject);
var ARect: TRect;
    AWidth, AHeight: Integer;
    ABasePoint: TPoint;
    AAngle: Extended;
begin
  FCenter := Point(Width div 2, Height div 2);
  AWidth := Width div 4;
  AHeight := Height div 4;
  ABasePoint := Point(FCenter.X+AWidth, FCenter.Y);
  ARect := Rect(Point(FCenter.X - AWidth, FCenter.Y - AHeight),
    Point(FCenter.X + AWidth, FCenter.Y + AHeight));
  AAngle := ArcTan2(ClickPoint.Y-Center.Y, ClickPoint.X-Center.X) * 180 / pi;
  AngleLabel.Caption := Format('Angle is %5.2f', [AAngle]);
  Canvas.Ellipse(ARect);
  Canvas.MoveTo(FCenter.X, FCenter.Y);
  Canvas.LineTo(FClickPoint.X, FClickPoint.Y);
  Canvas.MoveTo(FCenter.X, FCenter.Y);
  Canvas.LineTo(ABasePoint.X, ABasePoint.Y);
end;
Antonio
la source
2

Voici une méthode C # pour renvoyer l'angle (0-360) dans le sens inverse des aiguilles d'une montre à partir de l'horizontale pour un point sur un cercle.

    public static double GetAngle(Point centre, Point point1)
    {
        // Thanks to Dave Hill
        // Turn into a vector (from the origin)
        double x = point1.X - centre.X;
        double y = point1.Y - centre.Y;
        // Dot product u dot v = mag u * mag v * cos theta
        // Therefore theta = cos -1 ((u dot v) / (mag u * mag v))
        // Horizontal v = (1, 0)
        // therefore theta = cos -1 (u.x / mag u)
        // nb, there are 2 possible angles and if u.y is positive then angle is in first quadrant, negative then second quadrant
        double magnitude = Math.Sqrt(x * x + y * y);
        double angle = 0;
        if(magnitude > 0)
            angle = Math.Acos(x / magnitude);

        angle = angle * 180 / Math.PI;
        if (y < 0)
            angle = 360 - angle;

        return angle;
    }

A bientôt, Paul

Paul
la source
2

function p(x, y) {return {x,y}}

function normaliseToInteriorAngle(angle) {
	if (angle < 0) {
		angle += (2*Math.PI)
	}
	if (angle > Math.PI) {
		angle = 2*Math.PI - angle
	}
	return angle
}

function angle(p1, center, p2) {
	const transformedP1 = p(p1.x - center.x, p1.y - center.y)
	const transformedP2 = p(p2.x - center.x, p2.y - center.y)

	const angleToP1 = Math.atan2(transformedP1.y, transformedP1.x)
	const angleToP2 = Math.atan2(transformedP2.y, transformedP2.x)

	return normaliseToInteriorAngle(angleToP2 - angleToP1)
}

function toDegrees(radians) {
	return 360 * radians / (2 * Math.PI)
}

console.log(toDegrees(angle(p(-10, 0), p(0, 0), p(0, -10))))

Dominique
la source
0

il y a une réponse simple pour cela en utilisant les mathématiques du lycée.

Disons que vous avez 3 points

Pour obtenir l'angle du point A au point B

angle = atan2(A.x - B.x, B.y - A.y)

Pour obtenir l'angle du point B au point C

angle2 = atan2(B.x - C.x, C.y - B.y)

Answer = 180 + angle2 - angle
If (answer < 0){
    return answer + 360
}else{
    return answer
}

Je viens d'utiliser ce code dans le projet récent que j'ai fait, changez le B en P1 .. vous pourriez aussi bien supprimer le "180 +" si vous le souhaitez

James Penner
la source
-1

eh bien, les autres réponses semblent couvrir tout ce qui est requis, donc je voudrais simplement ajouter ceci si vous utilisez JMonkeyEngine:

Vector3f.angleBetween(otherVector)

car c'est ce que je suis venu ici chercher :)

VeganEye
la source
-2
      Atan2        output in degrees
       PI/2              +90
         |                | 
         |                |    
   PI ---.--- 0   +180 ---.--- 0       
         |                |
         |                |
       -PI/2             +270

public static double CalculateAngleFromHorizontal(double startX, double startY, double endX, double endY)
{
    var atan = Math.Atan2(endY - startY, endX - startX); // Angle in radians
    var angleDegrees = atan * (180 / Math.PI);  // Angle in degrees (can be +/-)
    if (angleDegrees < 0.0)
    {
        angleDegrees = 360.0 + angleDegrees;
    }
    return angleDegrees;
}

// Angle from point2 to point 3 counter clockwise
public static double CalculateAngle0To360(double centerX, double centerY, double x2, double y2, double x3, double y3)
{
    var angle2 = CalculateAngleFromHorizontal(centerX, centerY, x2, y2);
    var angle3 = CalculateAngleFromHorizontal(centerX, centerY, x3, y3);
    return (360.0 + angle3 - angle2)%360;
}

// Smaller angle from point2 to point 3
public static double CalculateAngle0To180(double centerX, double centerY, double x2, double y2, double x3, double y3)
{
    var angle = CalculateAngle0To360(centerX, centerY, x2, y2, x3, y3);
    if (angle > 180.0)
    {
        angle = 360 - angle;
    }
    return angle;
}

}

Daniel Quinn
la source