【问题标题】:How to calculate an angle from three points? [closed]如何从三个点计算角度? [关闭]
【发布时间】:2009-07-31 07:55:04
【问题描述】:

假设你有这个:

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

假设P1 是圆的中心点。它总是一样的。 我想要由P2P3 组成的角度,或者换句话说,是P1 旁边的角度。准确地说是内角。它始终是锐角,因此小于 -90 度。

我想:伙计,那是简单的几何数学。但是我现在已经寻找了大约 6 个小时的公式,只发现人们在谈论复杂的 NASA 东西,比如 arccos 和矢量标量产品的东西。我的头感觉就像在冰箱里。

这里有一些数学大师认为这是一个简单的问题?我认为编程语言在这里并不重要,但对于那些认为重要的人来说:java 和objective-c。我两个都需要它,但还没有为这些标记它。

【问题讨论】:

    标签: algorithm math geometry


    【解决方案1】:

    如果您的意思是 P1 是顶点的角度,那么使用 Law of Cosines 应该可以:

    arccos((P122 + P132 - P232) / (2 * P12 * P13))

    其中 P12 是从 P1 到 P2 的段的长度,计算公式为

    sqrt((P1x - P2x)2 + (P1y - P2y)2)

    【讨论】:

    • @Rafa Firenze cos^-1 是 acos 的常用符号,但 acos 不那么含糊。 en.wikipedia.org/wiki/Inverse_trigonometric_functions
    • 我会留下编辑,因为它不会伤害任何东西,但是拥有数学/CS/EE 学位,cos^-1 肯定是最常见的符号。
    • 只有少数语言使用插入符号表示“power of”,所以如果您不想称它为 arcos,请输入 cos⁻¹。 (如果您使用的是难以键入指数的商业操作系统,我希望您可以购买键帽应用程序,或者您可以安装浏览器插件。或者您可以进行网络搜索并复制和粘贴。)
    • @MichaelScheper,我只在 html 受限的 cmets 中使用插入符号。我当然会在任何实际答案中使用子/上标符号。
    【解决方案2】:

    如果你认为它是两个向量,一个从点 P1 到 P2,一个从 P1 到 P3,那就很简单了

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

    然后您可以反转点积公式:

    获取角度:

    请记住, 仅表示: a1*b1 + a2*b2(这里只有 2 维...)

    【讨论】:

    • 向量的大小啊
    • 检查 atan2 解决方案。
    【解决方案3】:

    处理角度计算的最佳方法是使用atan2(y, x),给定一个点x, y 返回与该点的角度以及相对于原点的X+ 轴。

    考虑到计算是

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

    即你基本上通过-P1 翻译这两个点(换句话说,你翻译所有内容,使P1 最终到达原点)然后你考虑P3P2 的绝对角度的差异。

    atan2 的优点是表示完整的圆(您可以得到 -π 和 π 之间的任何数字),而 acos 您需要根据符号处理几种情况以计算正确的结果。

    atan2 的唯一奇异点是 (0, 0)... 这意味着 P2P3 必须与 P1 不同,因为在这种情况下谈论角度是没有意义的。

    【讨论】:

    • 感谢您的回答。这正是我想要的。简单的解决方案,如果我在值为负时添加2pi,您可以轻松获得逆时针角度。
    • @marcpt: atan2 正是这个问题所需要的,但看起来大多数人遇到这个问题只是无法阅读或无法理解为什么基于acos 的解决方案是坏的。幸运的是,我多年前就离开了“互联网上有人错了”(xkcd.com/386)阶段,我不会开始为捍卫显而易见的事情而战:-)
    • 感谢您指出这一点,但是您可以这样处理 3D 吗?
    • @nicoco:在三个维度中你如何定义角度?更具体地说,角度可以是负数还是大于 pi(180 度)? 3d 中的两个非平行向量定义了一个平面,但是可以从两侧“看到”该平面:从一侧看 A 将出现在 B 的“左侧”,从另一侧看,它将出现在“右侧”。 .
    • @6505 感谢您的回答,我在考虑我的问题之前发布了。不过我现在明白了。
    【解决方案4】:

    让我举一个 JavaScript 的例子,我为此付出了很多努力:

    /**
     * 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));
    }
    

    奖金:Example with HTML5-canvas

    【讨论】:

    • 您可以通过减少sqrt 和平方来提高效率。请参阅my answer here(用 Ruby 编写),或在此 updated demo(JavaScript)中。
    • 您可以使用 atan2 来获得更简单的解决方案。
    【解决方案5】:

    基本上你有两个向量,一个从 P1 到 P2,另一个从 P1 到 P3。所以你只需要一个公式来计算两个向量之间的角度。

    查看here 以获得很好的解释和公式。

    【讨论】:

      【解决方案6】:

      如果您将 P1 视为圆心,则您的想法太复杂了。 你有一个简单的三角形,所以你的问题可以用law of cosines 解决。不需要任何极坐标转换或类似的东西。假设距离为 P1-P2 = A、P2-P3 = B 和 P3-P1 = C:

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

      您需要做的就是计算距离 A、B 和 C 的长度。 这些很容易从您的点的 x 和 y 坐标中获得,并且 Pythagoras' theorem

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

      【讨论】:

      • 我有点困惑如何实际实现这一点,因为您将 P1 等视为单个值而不是 (x,y)
      • @Dominic Tobias:符号P1-P2 = A 不应读作“要计算 A,从 P1 中减去 P2”,而是“我将 A 定义为从 P1 到 P2 的距离”,即然后可以使用第二个等式计算。我只是想为距离定义一个简写,以使方程式更具可读性。
      【解决方案7】:

      我最近遇到了类似的问题,只是我需要区分正角和负角。如果这对任何人有用,我推荐我从this mailing list 获取的关于检测 Android 触摸事件旋转的代码 sn-p:

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

      【讨论】:

        【解决方案8】:

        带有解释的非常简单的几何解

        几天前,a 遇到了同样的问题,不得不坐在数学书上。我通过组合和简化一些基本公式解决了这个问题。


        让我们考虑这个数字-

        我们想知道ϴ,所以我们需要先找出αβ。现在,对于任何直线 -

        y = m * x + c
        

        让- A = (ax, ay), B = (bx, by), O = (ox, oy) .所以对于 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)
        

        同理,对于OB-

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

        现在,我们需要ϴ = β - α。在三角学中,我们有一个公式-

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

        在替换 eqn-4 中 tan α(来自 eqn-2)和 tan b(来自 eqn-3)的值后,并应用简化我们得到 -

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

        所以,

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

        就是这样!


        现在,看下图-

        这个 C# 或 Java 方法计算角度 (ϴ)-

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

        【讨论】:

        • 这种方法如何用于等边三角形?
        • 嗯,你的答案现在工作正常。这是我一周前代码中的一些逻辑问题。
        【解决方案9】:

        在 Objective-C 中你可以这样做

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

        或阅读更多here

        【讨论】:

        • 呃,没有。有三个点,中心不在(0,0),这给出了直角三角形的角度,而不是顶点的角度。角度的“xpoint”是什么名称?
        【解决方案10】:

        你提到了一个有符号的角度(-90)。在许多应用中,角度可能有符号(正负,参见http://en.wikipedia.org/wiki/Angle)。如果这些点是(例如)P2(1,0)、P1(0,0)、P3(0,1),则角度 P3-P1-P2 通常为正 (PI/2),而角度 P2-P1- P3 为负。使用边的长度不会区分 + 和 - 所以如果这很重要,您将需要使用向量或函数,例如 Math.atan2(a, b)。

        角度也可以超出 2*PI,虽然这与当前问题无关,但我编写了自己的角度类(同时确保度数和弧度不会混淆)非常重要。关于角度 1 是否小于角度 2 的问题主要取决于角度的定义方式。决定一行 (-1,0)(0,0)(1,0) 是表示为 Math.PI 还是 -Math.PI

        也很重要

        【讨论】:

          【解决方案11】:

          最近,我也有同样的问题......在Delphi中 它与 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;
          

          【讨论】:

            【解决方案12】:

            这是一个 C# 方法,用于返回圆上某个点与水平方向逆时针的角度 (0-360)。

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

            干杯, 保罗

            【讨论】:

              【解决方案13】:

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

              【讨论】:

                【解决方案14】:

                使用高中数学有一个简单的答案..

                假设你有 3 分

                从A点到B点的角度

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

                从B点到C点的角度

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

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

                我在最近做的项目中刚刚使用了这段代码,把B改成P1..你也可以去掉“180+”

                【讨论】:

                  【解决方案15】:

                  好吧,其他答案似乎涵盖了所需的一切,所以如果您使用的是 JMonkeyEngine,我想补充一下:

                  Vector3f.angleBetween(otherVector)

                  因为这就是我来这里寻找的:)

                  【讨论】:

                    【解决方案16】:
                          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;
                    }
                    

                    }

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2021-04-30
                      • 2013-06-23
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2023-03-10
                      相关资源
                      最近更新 更多