Üç noktadan bir açı nasıl hesaplanır? [kapalı]


120

Buna sahip olduğunuzu söyleyelim:

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

Bunun P1bir dairenin merkez noktası olduğunu varsayalım . Hep aynıdır. Ben tarafından oluşur açısını istiyorum P2ve P3yanındaki açı veya başka bir deyişle P1. Kesin olarak iç açı. Her zaman dar bir açı olacak, yani -90 dereceden az olacaktır.

Düşündüm: Adamım, bu basit bir geometri matematiği. Ama şimdi yaklaşık 6 saattir bir formül aradım ve sadece arccos ve vektör skaler çarpım gibi karmaşık NASA şeylerinden bahseden insanlar buldum. Kafam buzdolabında gibi geliyor.

Bunun basit bir problem olduğunu düşünen bazı matematik uzmanları var mı? Burada programlama dilinin önemli olduğunu sanmıyorum, ancak önemli olduğunu düşünenler için: java ve amaç-c. İkisi için de ihtiyacım var ama bunun için etiketlemedim.

Yanıtlar:


87

P1'in tepe noktası olduğu açıyı kastediyorsanız, Kosinüs Yasasını kullanmak işe yaramalıdır :

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

burada P 12 , P1'den P2'ye olan segmentin uzunluğudur.

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



@Rafa Firenze cos ^ -1, acos için yaygın bir gösterimdir, ancak acos daha az belirsizdir. en.wikipedia.org/wiki/Inverse_trigonometric_functions
geon

Hiçbir şeye zarar vermediği için düzenlemeyi bırakacağım, ancak Math / CS / EE derecelerine sahip olmak, cos ^ -1 kesinlikle en yaygın gösterimdir.
Lance Roberts

1
Sadece bir avuç dil 'power of' için bir düzeltme işareti kullanır, bu nedenle arcos olarak adlandırmak istemiyorsanız, lütfen sadece cos⁻¹ yazın. (Üsleri yazmayı zorlaştıran ticari bir işletim sistemi kullanıyorsanız, satın alabileceğiniz klavye tuşları uygulamaları veya kurabileceğiniz bir tarayıcı eklentisi olmasını beklerim. Veya web'de arama yapıp kopyalayıp yapıştırabilirsiniz.)
Michael Scheper

1
@MichaelScheper, html'nin sınırlı olduğu yorumlarda sadece imleci kullanıyordum. Herhangi bir gerçek cevapta kesinlikle sadece alt / üst simge gösterimini kullanırdım.
Lance Roberts

47

Biri P1'den P2'ye ve diğeri P1'den P3'e iki vektör olarak düşünürseniz çok basitleşir.

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

Ardından iç çarpım formülünü ters çevirebilirsiniz:
nokta ürün
açıyı elde etmek için:
iki vektör arasındaki açı

Bunun nokta ürünşu anlama geldiğini unutmayın : a1 * b1 + a2 * b2 (burada sadece 2 boyut ...)


1
Ah vektörün büyüklüğü
Daniel Little

Atan2 çözümünü kontrol edin.
Luc Boissaye

25

Açı hesaplamasının üstesinden gelmenin en iyi yolu, atan2(y, x)bir noktanın x, yo noktadan açıyı X+ve orijine göre ekseni döndürmesini kullanmaktır .

Hesaplamanın olduğu göz önüne alındığında

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

yani, temelde iki noktayı çevirirsiniz -P1(başka bir deyişle, her şeyi P1başlangıçta bitecek şekilde çevirirsiniz) ve sonra mutlak açıların farkını düşünürsünüz P3.P2 .

Bunun avantajı atan2, tam çemberin temsil edilmesidir (-π ve between arasında herhangi bir sayı elde edebilirsiniz) bunun yerine acosdoğru sonucu hesaplamak için işaretlere bağlı olarak birkaç durumu ele almanız gerekir.

Sadece tekil nokta atan2olduğunu (0, 0)hem anlam ... P2ve P3farklı olmalıdır P1bir açı hakkında konuşmak için mantıklı değil bu durumda olduğu gibi.


Cevabınız için teşekkürler. Tam olarak aradığım buydu. Basit çözüm ve değer negatif olduğunda sadece 2pi eklersem saat yönünün tersine açıyı kolayca elde edebilirsiniz.
Mario

@marcpt: atan2tam olarak bu problem için gerekli olan şeydir, ancak bu soruya ulaşan çoğu insan okuyamıyor veya neden acostemelli çözümün kötü olduğunu anlayamıyor gibi görünüyor . Neyse ki benim için yeterince uzun yıllar önce "internette birisi yanlış" ( xkcd.com/386 ) aşamasından ayrıldım ve bariz olanı savunmak için bir kavga başlatmayacağım :-)
6502

Buna işaret ettiğiniz için teşekkürler, ancak 3B'yi bu şekilde halledebilir misiniz?
nicoco

1
@nicoco: üç boyutlu olarak açıyı nasıl tanımlarsınız? Daha spesifik olarak, açı negatif veya pi'den (180 derece) fazla olabilir mi? 3d'deki paralel olmayan iki vektör bir düzlemi tanımlar, ancak düzlem iki taraftan "görülebilir": bir taraftan bakıldığında A, B'nin "solunda" görünecek ve diğer taraftan "sağda" görünecektir .. .
6502

@ 6505 Cevabınız için teşekkürler, sorunumu iyice düşünmeden önce gönderdim. Yine de şimdi anladım.
nicoco

19

JavaScript'te bir örnek vereyim, bununla çok mücadele ettim:

/**
 * 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: HTML5 tuval örneği


5
Daha az yaparak sqrtve kare alarak bunu daha verimli hale getirebilirsiniz . Bkz burada cevabımı (Ruby ile yazılmış) veya bu güncellenmiş demo (JavaScript).
Phrogz

Daha basit bir çözüm için atan2'yi kullanabilirsiniz.
Luc Boissaye

15

Temel olarak elinizde iki vektör var, biri P1'den P2'ye ve diğeri P1'den P3'e. Yani tek ihtiyacınız olan, iki vektör arasındaki açıyı hesaplamak için bir formül.

İyi bir açıklama ve formül için buraya bir göz atın .

alternatif metin


12

P1'i bir çemberin merkezi olarak düşünüyorsanız, çok karmaşık düşünüyorsunuz. Basit bir üçgeniniz var, bu nedenle probleminiz kosinüsler yasasıyla çözülebilir . Herhangi bir kutupsal koordinat dönüşümüne veya benzeri bir şeye gerek yok. Mesafelerin P1-P2 = A, P2-P3 = B ve P3-P1 = C olduğunu söyleyin:

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

Yapmanız gereken tek şey A, B ve C mesafelerinin uzunluğunu hesaplamaktır.Bunlar, noktalarınızın x ve y koordinatlarından ve Pisagor teoreminden kolayca elde edilebilir.

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


P1 vs'yi (x, y) yerine bireysel değerler olarak ele aldığınız için bunu nasıl uygulayacağımı biraz kafam karıştı
Dominic

@Dominic Tobias: Notasyon P1-P2 = A, "A'yı hesaplamak için, P2'yi P1'den çıkar " şeklinde okunmamalıdır, " A'yı P1'den P2'ye mesafe olarak tanımlıyorum" olarak okunmalıdır, bu daha sonra ikinci denklem kullanılarak hesaplanabilir. Denklemleri daha okunaklı hale getirmek için mesafeler için bir kısaltma tanımlamak istedim.
Treb

8

Son zamanlarda benzer bir problemle karşılaştım, sadece pozitif ve negatif açıları ayırt etmem gerekiyordu. Bunun herhangi birinin işine yaraması durumunda, Android için bir dokunma olayına göre dönüşü algılamak için bu posta listesinden aldığım kod parçacığını öneririm :

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

7

Açıklamalı Çok Basit Geometrik Çözüm

Birkaç gün önce, a aynı soruna düştü ve matematik kitabıyla oturmak zorunda kaldı. Bazı temel formülleri birleştirip basitleştirerek sorunu çözdüm.


Bu rakamı düşünelim-

açı

ϴ'yi bilmek istiyoruz , bu yüzden önce α ve β'yi bulmalıyız . Şimdi, herhangi bir düz çizgi için-

y = m * x + c

Let- A = (ax, ay) , B = (bx, by) ve O = (ox, oy) . OA hattı için -

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)

Aynı şekilde OB hattı için -

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

Şimdi ihtiyacımız var ϴ = β - α. Trigonometride bir formülümüz var.

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

Eqn-4'teki ( tan αeqn-2'den) ve tan b(eqn-3'ten) değerlerini değiştirdikten ve sadeleştirmeyi uyguladıktan sonra şunu elde ederiz:

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

Yani,

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

İşte bu!


Şimdi, aşağıdaki şekli al:

açı

Bu C # veya Java yöntemi açıyı hesaplar ( calcul ) -

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

Bu yöntem bir eşkenar üçgen için nasıl kullanılabilir?
Vikrant

1
Cevabınız şimdi iyi çalışıyor. Önceki hafta kodumda bir mantık sorunu vardı.
Vikrant

6

Objective-C'de bunu şu şekilde yapabilirsiniz:

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

Veya buradan daha fazlasını okuyun


7
Oh hayır. Üç nokta vardır, merkez (0,0) 'da değildir ve bu, tepenin açısını değil, bir dik üçgenin açısını verir. Ve bir açı için "xpoint" nasıl bir isimdir?
Jim Balter

4

İşaretli bir açıdan (-90) bahsettiniz. Birçok uygulamada açıların işaretleri olabilir (pozitif ve negatif, bkz. Http://en.wikipedia.org/wiki/Angle ). Noktalar (örneğin) P2 (1,0), P1 (0,0), P3 (0,1) ise, bu durumda P3-P1-P2 açısı geleneksel olarak pozitiftir (PI / 2), buna karşılık P2-P1- açısı P3 negatiftir. Kenarların uzunluklarını kullanmak + ve - arasında ayrım yapmayacaktır, bu nedenle bu önemliyse vektörleri veya Math.atan2 (a, b) gibi bir işlevi kullanmanız gerekecektir.

Açılar 2 * PI'nin ötesine de uzanabilir ve bu mevcut soruyla ilgili olmasa da kendi Açı sınıfımı yazmam yeterince önemliydi (ayrıca derecelerin ve radyanların karışmadığından emin olmak için). Açı1'in açı2'den küçük olup olmadığı hakkındaki sorular kritik olarak açıların nasıl tanımlandığına bağlıdır. Bir (-1,0) (0,0) (1,0) satırının Math.PI veya -Math.PI olarak temsil edilip edilmediğine karar vermek de önemli olabilir.


4

açı demo programım

Son zamanlarda bende de aynı sorun var ... Delphi'de Objective-C'ye çok benziyor.

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;

2

Bir daire üzerindeki bir nokta için açıyı (0-360) yataydan saat yönünün tersine döndürmek için bir C # yöntemi.

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

Şerefe, Paul


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


0

lise matematiğini kullanarak bunun basit bir cevabı var ..

Diyelim ki 3 puanınız var

A noktasından B'ye açı elde etmek için

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

B noktasından C'ye açı elde etmek için

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

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

Bu kodu az önce yaptığım son projede kullandım, B'yi P1 olarak değiştirin .. İsterseniz "180 +" kodunu da kaldırabilirsiniz


-1

diğer cevaplar gereken her şeyi kapsıyor gibi görünüyor, bu yüzden JMonkeyEngine kullanıyorsanız bunu eklemek istiyorum:

Vector3f.angleBetween(otherVector)

çünkü buraya aramaya geldim :)


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

}

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.