Wie berechnet man einen Winkel aus drei Punkten? [geschlossen]

120

Nehmen wir an, Sie haben Folgendes:

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

Angenommen, dies P1ist der Mittelpunkt eines Kreises. Es ist immer das gleiche. Ich möchte den Winkel, aus dem P2und besteht P3, oder mit anderen Worten den Winkel, der neben ist P1. Der Innenwinkel um genau zu sein. Es wird immer ein spitzer Winkel sein, also weniger als -90 Grad.

Ich dachte: Mann, das ist einfache Geometriemathematik. Aber ich habe jetzt seit ungefähr 6 Stunden nach einer Formel gesucht und finde nur Leute, die über komplizierte NASA-Sachen wie Arccos und Vektor-Skalar-Produkte reden. Mein Kopf fühlt sich an wie in einem Kühlschrank.

Einige Mathe-Gurus hier, die denken, dass dies ein einfaches Problem ist? Ich denke nicht, dass die Programmiersprache hier wichtig ist, aber für diejenigen, die glauben, dass dies der Fall ist: Java und Objective-C. Ich brauche es für beide, habe es aber nicht für diese markiert.

Lance Roberts
quelle

Antworten:

87

Wenn Sie den Winkel meinen, für den P1 der Scheitelpunkt ist, sollte die Verwendung des Kosinusgesetzes funktionieren:

Arccos((P 12 2 + P 13 2 - P 23 2 ) / (2 · P 12 · P 13 ))

wobei P 12 die Länge des Segments von P1 bis P2 ist, berechnet durch

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

Lance Roberts
quelle
@Rafa Firenze cos ^ -1 ist eine gebräuchliche Notation für acos, aber acos ist weniger mehrdeutig. en.wikipedia.org/wiki/Inverse_trigonometric_functions
Geon
Ich werde die Bearbeitung verlassen, da es nichts tut, aber mit Math / CS / EE-Abschlüssen ist cos ^ -1 sicherlich die häufigste Notation.
Lance Roberts
1
Nur eine Handvoll Sprachen verwenden ein Caret für "Power of". Wenn Sie es also nicht Arcos nennen möchten, geben Sie einfach cos⁻¹ ein. (Wenn Sie ein kommerzielles Betriebssystem verwenden, das das Eingeben von Exponenten erschwert, können Sie wahrscheinlich Keycaps-Anwendungen kaufen oder ein Browser-Plug-In installieren. Oder Sie können im Internet suchen und kopieren und einfügen.)
Michael Scheper
1
@ MichaelScheper, ich habe das Caret nur in den Kommentaren verwendet, in denen HTML begrenzt ist. Ich würde sicherlich nur die hochgestellte / hochgestellte Notation in einer tatsächlichen Antwort verwenden.
Lance Roberts
47

Es wird sehr einfach, wenn Sie es als zwei Vektoren betrachten, einen von Punkt P1 bis P2 und einen von P1 bis P3

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

Sie können dann die Punktproduktformel umkehren:
Skalarprodukt
um den Winkel zu erhalten:
Winkel zwischen zwei Vektoren

Denken Sie daran, dass dies Skalarproduktnur bedeutet: a1 * b1 + a2 * b2 (hier nur 2 Dimensionen ...)

Andrea Ambu
quelle
1
Ah Größe des Vektors
Daniel Little
Überprüfen Sie die atan2-Lösung.
Luc Boissaye
25

Der beste Weg, um mit der Winkelberechnung umzugehen, besteht darin, zu verwenden, atan2(y, x)dass bei einem gegebenen Punkt x, yder Winkel von diesem Punkt und der X+Achse in Bezug auf den Ursprung zurückgegeben wird.

Vorausgesetzt, die Berechnung ist

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

Das heißt, Sie übersetzen die beiden Punkte -P1im Grunde genommen durch (mit anderen Worten, Sie übersetzen alles so, dass P1es im Ursprung endet) und betrachten dann den Unterschied der absoluten Winkel von P3und von P2.

Der Vorteil von atan2ist, dass der volle Kreis dargestellt wird (Sie können eine beliebige Zahl zwischen -π und π erhalten), wobei Sie stattdessen acosmehrere Fälle abhängig von den Vorzeichen behandeln müssen, um das richtige Ergebnis zu berechnen.

Der einzige singuläre Punkt für atan2ist (0, 0)... was bedeutet, dass beide P2und P3sich von dem unterscheiden müssen, P1was in diesem Fall keinen Sinn macht, über einen Winkel zu sprechen.

6502
quelle
Danke für deine Antwort. Genau das habe ich gesucht. Einfache Lösung und Sie können leicht den Winkel gegen den Uhrzeigersinn erhalten, wenn ich nur 2pi addiere, wenn der Wert negativ ist.
Mario
@marcpt: atan2ist genau das, was für dieses Problem benötigt wird, aber es sieht so aus, als ob die meisten Leute, die zu dieser Frage kommen, einfach nicht lesen oder nicht verstehen können, warum eine auf acosLösungen basierende Lösung schlecht ist. Zum Glück habe ich vor vielen Jahren die Phase "Jemand ist im Internet falsch" ( xkcd.com/386 ) verlassen und werde keinen Kampf um die Verteidigung des Offensichtlichen beginnen :-)
6502
Vielen Dank für den Hinweis, aber können Sie 3D auf diese Weise handhaben?
Nicoco
1
@nicoco: Wie definierst du in drei Dimensionen den Winkel? Kann der Winkel genauer oder größer als pi (180 Grad) sein? Zwei nicht parallele Vektoren in 3d definieren eine Ebene, aber die Ebene kann von zwei Seiten "gesehen" werden: Von einer Seite A wird "links" von B und von der anderen Seite "rechts" angezeigt. .
6502
@ 6505 Danke für deine Antwort, die ich gepostet habe, bevor ich über mein Problem nachgedacht habe. Ich habe es jetzt aber herausgefunden.
Nicoco
19

Lassen Sie mich ein Beispiel in JavaScript geben, damit habe ich viel gekämpft:

/**
 * 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: Beispiel mit HTML5-Canvas

shaman.sir
quelle
5
Sie können dies effizienter gestalten, indem Sie weniger tun sqrtund quadrieren. Siehe meine Antwort hier (geschrieben in Ruby) oder in dieser aktualisierten Demo (JavaScript).
Phrogz
Sie könnten atan2 für eine einfachere Lösung verwenden.
Luc Boissaye
15

Grundsätzlich haben Sie zwei Vektoren, einen Vektor von P1 nach P2 und einen anderen von P1 nach P3. Sie benötigen also nur eine Formel, um den Winkel zwischen zwei Vektoren zu berechnen.

Werfen Sie einen Blick hier für eine gute Erklärung und die Formel.

Alt-Text

Andre Miller
quelle
12

Wenn Sie P1 als Mittelpunkt eines Kreises betrachten, denken Sie zu kompliziert. Sie haben ein einfaches Dreieck, sodass Ihr Problem mit dem Kosinusgesetz lösbar ist . Keine Polarkoordinatentransformation oder ähnliches erforderlich. Angenommen, die Abstände sind P1-P2 = A, P2-P3 = B und P3-P1 = C:

Winkel = Arccos ((B ^ 2-A ^ 2-C ^ 2) / 2AC)

Sie müssen lediglich die Länge der Abstände A, B und C berechnen. Diese sind leicht aus den x- und y-Koordinaten Ihrer Punkte und dem Satz von Pythagoras verfügbar

Länge = sqrt ((X2-X1) ^ 2 + (Y2-Y1) ^ 2)

Treb
quelle
Ich bin ein bisschen verwirrt, wie man dies tatsächlich implementiert, da man P1 usw. eher als einzelne Werte als als (x, y) behandelt
Dominic
@Dominic Tobias: Die Notation P1-P2 = Asollte nicht als "Um A zu berechnen, subtrahiere P2 von P1" gelesen werden, sondern als "Ich definiere A als den Abstand von P1 zu P2", der dann unter Verwendung der zweiten Gleichung berechnet werden kann. Ich wollte nur eine Abkürzung für die Entfernungen definieren, um die Gleichungen besser lesbar zu machen.
Treb
8

Ich bin kürzlich auf ein ähnliches Problem gestoßen, nur musste ich zwischen positiven und negativen Winkeln unterscheiden. Für den Fall, dass dies für jemanden von Nutzen ist, empfehle ich das Code-Snippet, das ich aus dieser Mailingliste zum Erkennen der Rotation über ein Touch-Ereignis für Android abgerufen habe:

 @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
quelle
7

Sehr einfache geometrische Lösung mit Erklärung

Vor ein paar Tagen fiel ein in das gleiche Problem und musste mit dem Mathematikbuch sitzen. Ich habe das Problem gelöst, indem ich einige Grundformeln kombiniert und vereinfacht habe.


Betrachten wir diese Zahl.

Winkel

Wir wollen ϴ wissen , also müssen wir zuerst α und β herausfinden . Nun zu jeder geraden Linie

y = m * x + c

Sei A = (ax, ay) , B = (bx, by) und O = (ox, oy) . Also für die Linie 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)

In gleicher Weise gilt für die Leitung OB -

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

Jetzt brauchen wir ϴ = β - α. In der Trigonometrie haben wir eine Formel-

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

Nachdem wir den Wert von tan α(aus Gleichung-2) und tan b(aus Gleichung-3) in Gleichung-4 ersetzt und die Vereinfachung angewendet haben, erhalten wir

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

So,

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

Das ist es!


Nehmen Sie nun die folgende Abbildung:

Winkel

Diese C # - oder Java-Methode berechnet den Winkel ( ϴ ) -

    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
quelle
Wie kann diese Methode für ein gleichseitiges Dreieck angewendet werden?
Vikrant
1
Nun, Ihre Antwort funktioniert jetzt gut. Es war ein logisches Problem in meiner Code-Woche zuvor.
Vikrant
6

In Objective-C können Sie dies tun, indem Sie

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

Oder lesen Sie hier mehr

Adeem Maqsood Basraa
quelle
7
Oh nein. Es gibt drei Punkte, die Mitte liegt nicht bei (0,0), und dies ergibt einen Winkel eines rechtwinkligen Dreiecks, nicht den Winkel der Spitze. Und was für ein Name ist "xpoint" für einen Winkel?
Jim Balter
4

Sie haben einen vorzeichenbehafteten Winkel (-90) erwähnt. In vielen Anwendungen können Winkel Vorzeichen haben (positiv und negativ, siehe http://en.wikipedia.org/wiki/Angle ). Wenn die Punkte (sagen wir) P2 (1,0), P1 (0,0), P3 (0,1) sind, ist der Winkel P3-P1-P2 herkömmlicherweise positiv (PI / 2), während der Winkel P2-P1- P3 ist negativ. Wenn Sie die Länge der Seiten verwenden, wird nicht zwischen + und - unterschieden. Wenn dies wichtig ist, müssen Sie Vektoren oder eine Funktion wie Math.atan2 (a, b) verwenden.

Winkel können sich auch über 2 * PI hinaus erstrecken, und obwohl dies für die aktuelle Frage nicht relevant ist, war es ausreichend wichtig, dass ich meine eigene Winkelklasse schrieb (auch um sicherzustellen, dass Grad und Bogenmaß nicht verwechselt wurden). Die Frage, ob Winkel1 kleiner als Winkel2 ist, hängt entscheidend davon ab, wie Winkel definiert werden. Es kann auch wichtig sein zu entscheiden, ob eine Linie (-1,0) (0,0) (1,0) als Math.PI oder -Math.PI dargestellt wird

peter.murray.rust
quelle
4

mein Winkel-Demo-Programm

In letzter Zeit habe auch ich das gleiche Problem ... In Delphi ist es Objective-C sehr ähnlich.

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
quelle
2

Hier ist eine C # -Methode, um den Winkel (0-360) gegen den Uhrzeigersinn von der Horizontalen für einen Punkt auf einem Kreis zurückzugeben.

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

Prost, Paul

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

Dominic
quelle
0

Es gibt eine einfache Antwort darauf mit High-School-Mathematik.

Angenommen, Sie haben 3 Punkte

Winkel von Punkt A nach B ermitteln

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

Winkel von Punkt B nach C ermitteln

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

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

Ich habe diesen Code gerade in dem kürzlich von mir erstellten Projekt verwendet. Ändern Sie das B in P1. Sie können auch das "180 +" entfernen, wenn Sie möchten

James Penner
quelle
-1

Nun, die anderen Antworten scheinen alles Notwendige abzudecken, daher möchte ich dies nur hinzufügen, wenn Sie JMonkeyEngine verwenden:

Vector3f.angleBetween(otherVector)

da bin ich auf der Suche hierher gekommen :)

VeganEye
quelle
-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
quelle