得到最近的一条线

时间:2010-06-25 18:09:46

标签: c# math geometry

我希望有一个直接的C#函数来获得最近点(从点P)到线段AB。抽象函数可能如下所示。我搜索了SO,但没有找到可用的(由我)解决方案。

public Point getClosestPointFromLine(Point A, Point B, Point P);

13 个答案:

答案 0 :(得分:36)

这里的Ruby伪装成伪代码,假设Point个对象都有xy字段。

def GetClosestPoint(A, B, P)

  a_to_p = [P.x - A.x, P.y - A.y]     # Storing vector A->P
  a_to_b = [B.x - A.x, B.y - A.y]     # Storing vector A->B

  atb2 = a_to_b[0]**2 + a_to_b[1]**2  # **2 means "squared"
                                      #   Basically finding the squared magnitude
                                      #   of a_to_b

  atp_dot_atb = a_to_p[0]*a_to_b[0] + a_to_p[1]*a_to_b[1]
                                      # The dot product of a_to_p and a_to_b

  t = atp_dot_atb / atb2              # The normalized "distance" from a to
                                      #   your closest point

  return Point.new( :x => A.x + a_to_b[0]*t,
                    :y => A.y + a_to_b[1]*t )
                                      # Add the distance to A, moving
                                      #   towards B

end

可替换地:

来自维基百科的Line-Line Intersection。首先,找到Q,这是从“正确的方向”P开始迈出的第二个要点。这给了我们四点。

def getClosestPointFromLine(A, B, P)

  a_to_b = [B.x - A.x, B.y - A.y]   # Finding the vector from A to B
                                        This step can be combined with the next
  perpendicular = [ -a_to_b[1], a_to_b[0] ]
                                    # The vector perpendicular to a_to_b;
                                        This step can also be combined with the next

  Q = Point.new(:x => P.x + perpendicular[0], :y => P.y + perpendicular[1])
                                    # Finding Q, the point "in the right direction"
                                    # If you want a mess, you can also combine this
                                    # with the next step.

  return Point.new (:x => ((A.x*B.y - A.y*B.x)*(P.x - Q.x) - (A.x-B.x)*(P.x*Q.y - P.y*Q.x)) / ((A.x - B.x)*(P.y-Q.y) - (A.y - B.y)*(P.y-Q.y)),
                    :y => ((A.x*B.y - A.y*B.x)*(P.y - Q.y) - (A.y-B.y)*(P.x*Q.y - P.y*Q.x)) / ((A.x - B.x)*(P.y-Q.y) - (A.y - B.y)*(P.y-Q.y)) )

end

出于性能原因,可以使用缓存,跳过步骤等。

答案 1 :(得分:30)

如果有人对基于上述内容的C#XNA功能感兴趣:

    public static Vector2 GetClosestPointOnLineSegment(Vector2 A, Vector2 B, Vector2 P)
    {
        Vector2 AP = P - A;       //Vector from A to P   
        Vector2 AB = B - A;       //Vector from A to B  

        float magnitudeAB = AB.LengthSquared();     //Magnitude of AB vector (it's length squared)     
        float ABAPproduct = Vector2.Dot(AP, AB);    //The DOT product of a_to_p and a_to_b     
        float distance = ABAPproduct / magnitudeAB; //The normalized "distance" from a to your closest point  

        if (distance < 0)     //Check if P projection is over vectorAB     
        {
            return A;

        }
        else if (distance > 1)             {
            return B;
        }
        else
        {
            return A + AB * distance;
        }
    }

答案 2 :(得分:10)

您的观点(X)将是点AB的线性组合:

X = k A + (1-k) B

要使X实际上在线段上,参数k必须介于0和1之间(包括0和1)。您可以按如下方式计算k:

k_raw = (P-B).(A-B)  /  (A-B).(A-B)

(其中句点表示点积)

然后,确保该点实际上在线段上:

if k_raw < 0:
    k= 0
elif k_raw > 1:
    k= 1
else:
    k= k_raw

答案 3 :(得分:6)

Justin L.的答案几乎没问题,但它没有检查归一化距离是否小于0或高于AB矢量幅度。然后当P矢量提取超出界限(来自线段AB)时,它将不能很好地工作。 这是更正后的伪代码:

    function GetClosestPoint(A, B, P)
{
  vectorAP = (p.x - a.x, p.y - a.y)     //Vector from A to P
  vectorAB = (b.x - a.x, b.y - a.y)     //Vector from A to B

  magnitudeAB = vectorAB[0]^2 + vectorAB[1]^2  
  //Magnitude of AB vector (it's length)


  ABAPproduct = vectorAB[0]*vectorAP[0] + vectorAB[1]*vectorAP[1] 
  //The product of a_to_p and a_to_b


  distance = ABAPproduct / magnitudeAB       
  //The normalized "distance" from a to your closest point

  if ( distance < 0)     //Check if P projection is over vectorAB
    {
        returnPoint.x = a.x
        returnPoint.y = a.y
    }   
  else if (distance > magnitudeAB)
    {
        returnPoint.x = b.x
        returnPoint.y = b.y
    }
  else
    {
        returnPoint.x = a.x + vectorAB[0]*distance
        returnPoint.y = a.y + vectorAB[1]*distance
    }

}

答案 4 :(得分:4)

我很久以前写过这篇文章,它与其他人所说的没什么不同,但如果你有一个名为{{1的类(或结构),它就是C#中的复制/粘贴解决方案成员PointFX

Y

更新:查看评论看起来我已根据接受的答案中提到的相同源代码将其改编为C#。

答案 5 :(得分:3)

通过将y差除以x差来求出AB的斜率a1;然后绘制一条垂直线(斜率a2 = -1 / a1,你需要通过将P坐标放入y = a2 * x + b2来求解偏移量(b2));然后你有两条线(即两个线性方程),你需要解决交叉点。这将是你最接近的一点。

正确的数学运算,编写函数非常简单。

详细说明一下:

Original line:
y = a1 * x + b1
a1 = (By - Ay) / (Bx - Ax)   <--
b1 = Ay - a1 * Ax            <--

Perpendicular line:
y = a2 * x + b2
a2 = -1/a1                   <--
b2 = Py - a2 * Px            <--

Now you have P which lies on both lines:
y = a1 * x + b1
y = a2 * x + b2
--------------- subtract:
0 = (a1 - a2) * Px + (b1 - b2)
x = - (b1 - b2) / (a1 - a2)  <--
y = a1 * x + b1              <--

希望我没有在某处搞砸:) 更新当然我做到了。为我做正确的事,因为他没有先在纸上解决问题。我应该得到所有的支持,但我希望有人能够纠正我。固定(我希望)。

箭头指明了方向。

更新啊,角落的情况。是的,有些语言不能很好地处理无穷大。我确实说解决方案没有语言......

您可以查看特殊情况,它们非常简单。第一个是x差为0时。这意味着线是垂直的,最近的点是水平垂线。因此,x = Ax, y = Px

第二个是y差为0时,反之亦然。因此,x = Px, y = Ay

答案 6 :(得分:3)

这个答案是基于投影几何的想法。

计算叉积(Ax,Ay,1)×(Bx,By,1)=(u,v,w)。得到的矢量描述连接A和B的线:它具有等式ux + vy + w =​​ 0。但是你也可以将(u,v,0)解释为在与该线垂直的方向上无限远的点。做另一个交叉积,你得到连接帽点的线到P:(u,v,0)×(Px,Py,1)。并且要将该线与AB线相交,您会做另一个交叉乘积:((u,v,0)×(Px,Py,1))×(u,v,w)。结果将是一个同质坐标向量(x,y,z),您可以从中读取此最近点的坐标为(x / z,y / z)。

把所有东西放在一起,你得到以下公式:

{\scriptsize\begin{pmatrix}x\y\z\end{pmatrix}}=\Bigl(\bigl({\scriptsize\begin{pmatrix}1&0&0\0&1&0\0&0&0\end{pmatrix}}(A\times B)\bigr)\times P\Bigr)\times(A\times B)

使用计算机代数系统,您可以找到以下结果坐标:

x = ((Ax - Bx)*Px + (Ay - By)*Py)*(Ax - Bx) + (Ay*Bx - Ax*By)*(Ay - By)
y = -(Ay*Bx - Ax*By)*(Ax - Bx) + ((Ax - Bx)*Px + (Ay - By)*Py)*(Ay - By)
z = (Ax - Bx)^2 + (Ay - By)^2

正如您所注意到的,有很多反复出现的术语。为这些创建(几乎是任意的)名称,您可以获得以伪代码编写的以下最终结果:

dx = A.x - B.x
dy = A.y - B.y
det = A.y*B.x - A.x*B.y
dot = dx*P.x + dy*P.y
x = dot*dx + det*dy
y = dot*dy - det*dx
z = dx*dx + dy*dy
zinv = 1/z
return new Point(x*zinv, y*zinv)

这种方法的好处:

  • 无案例区别
  • 没有方根
  • 只有一个部门

答案 7 :(得分:1)

最近的点C将在一条线上,该线的斜率是AB的倒数并且与P相交。听起来这可能是家庭作业,但我会给出一些非常强烈的提示,以增加剧透警报级别:

  • 只能有一条这样的线。

  • 这是一个由两个线方程组成的系统。只需解决xy

  • AB之间绘制一条线段;请拨打此LL的等式为y = mx + b,其中m是y坐标与x坐标的比率。使用表达式中的bA解析B

  • 执行与上述相同的操作,但适用于CP。现在求解同时线性方程组。

  • Google搜索会为您提供 a bevy of examples 供您选择。

答案 8 :(得分:1)

以下是可以解决这个问题的扩展方法:

public static double DistanceTo(this Point from, Point to)
    {
        return Math.Sqrt(Math.Pow(from.X - to.X, 2) + Math.Pow(from.Y - to.Y, 2));
    }

public static double DistanceTo(this Point point, Point lineStart, Point lineEnd)
    {
        double tI = ((lineEnd.X - lineStart.X) * (point.X - lineStart.X) + (lineEnd.Y - lineStart.Y) * (point.Y - lineStart.Y)) / Math.Pow(lineStart.DistanceTo(lineEnd), 2);
        double dP = ((lineEnd.X - lineStart.X) * (point.Y - lineStart.Y) - (lineEnd.Y - lineStart.Y) * (point.X - lineStart.X)) / lineStart.DistanceTo(lineEnd);

        if (tI >= 0d && tI <= 1d)
            return Math.Abs(dP);
        else
            return Math.Min(point.DistanceTo(lineStart), point.DistanceTo(lineEnd));
    }

然后打电话:

P.DistanceTo(A, B);

从线| AB |获得点“P”的距离。应该很容易为PointF修改此内容。

找到最近的点只是搜索最小距离的问题。 LINQ有相应的方法。

答案 9 :(得分:0)

如果有人正在寻找使用Java + LibGdx执行此操作的方法:

Intersector.nearestSegmentPoint

答案 10 :(得分:0)

这是从某个点(已测试)(vb.net)获取线段最近点的正确算法

s2 = ClosestPointToSegment(point_x, Point_y, Segment_start_x, Segment_start_y, Segment_end_X, Segment_end_Y)

Public Shared Function DistanceTo(x1 As Double, y1 As Double, x2 As Double, y2 As Double) As Double
    Return Math.Sqrt(Math.Pow(x1 - x2, 2) + Math.Pow(y1 - y2, 2))
End Function


Public Shared Function DistanceTo(point_x As Double, point_y As Double, lineStart_x As Double, lineStart_y As Double, lineEnd_x As Double, lineEnd_y As Double) As Double
    Dim tI As Double = ((lineEnd_x - lineStart_x) * (point_x - lineStart_x) + (lineEnd_y - lineStart_y) * (point_y - lineStart_x)) / Math.Pow(DistanceTo(lineStart_x, lineStart_y, lineEnd_x, lineEnd_y), 2)
    Dim dP As Double = ((lineEnd_x - lineStart_x) * (point_y - lineStart_y) - (lineEnd_y - lineStart_y) * (point_x - lineStart_x)) / DistanceTo(lineStart_x, lineStart_y, lineEnd_x, lineEnd_y)

    If tI >= 0R AndAlso tI <= 1.0R Then
        Return Math.Abs(dP)
    Else
        Return Math.Min(DistanceTo(point_x, point_y, lineStart_x, lineStart_y), DistanceTo(point_x, point_y, lineEnd_x, lineEnd_y))
    End If
End Function
Private Shared Function ClosestPointToSegment(P_x As Double, p_y As Double, A_x As Double, a_y As Double, B_x As Double, b_y As Double) As Double()
    Dim a_to_p As PointF = New PointF(), a_to_b As PointF = New PointF()
    Dim rikthex As Double, rikthey As Double
    Dim s1(1) As Double
    Dim p1_v1_X As Double, p1_v1_y As Double, distanca1 As Double, distanca2 As Double
    a_to_p.X = P_x - A_x
    a_to_p.Y = p_y - a_y
    a_to_b.X = B_x - A_x
    a_to_b.Y = b_y - a_y
    Dim atb2 As Single = a_to_b.X * a_to_b.X + a_to_b.Y * a_to_b.Y
    Dim atp_dot_atb As Single = a_to_p.X * a_to_b.X + a_to_p.Y * a_to_b.Y
    Dim t As Single = atp_dot_atb / atb2
    rikthex = A_x + a_to_b.X * t
    rikthey = a_y + a_to_b.Y * t
    If A_x > B_x Then
        If rikthex < A_x And rikthex > B_x Then 'pika duhet ne rregulll
            If a_y > b_y Then
                If rikthey < a_y And rikthey > b_y Then 'pika duhet ne rregulll

                Else
                    distanca1 = DistanceTo(P_x, p_y, A_x, a_y)
                    distanca2 = DistanceTo(P_x, p_y, B_x, b_y)
                    If distanca1 < distanca2 Then
                        rikthex = A_x
                        rikthey = a_y
                    Else
                        rikthex = B_x
                        rikthey = b_y
                    End If

                End If
            Else
                If rikthey > a_y And rikthey < b_y Then 'pika duhet ne rregulll

                Else
                    distanca1 = DistanceTo(P_x, p_y, A_x, a_y)
                    distanca2 = DistanceTo(P_x, p_y, B_x, b_y)
                    If distanca1 < distanca2 Then
                        rikthex = A_x
                        rikthey = a_y
                    Else
                        rikthex = B_x
                        rikthey = b_y
                    End If

                End If

            End If
        Else
            distanca1 = DistanceTo(P_x, p_y, A_x, a_y)
            distanca2 = DistanceTo(P_x, p_y, B_x, b_y)
            If distanca1 < distanca2 Then
                rikthex = A_x
                rikthey = a_y
            Else
                rikthex = B_x
                rikthey = b_y
            End If
        End If
    Else
        If rikthex > A_x And rikthex < B_x Then 'pika duhet ne rregulll
            If a_y > b_y Then
                If rikthey < a_y And rikthey > b_y Then 'pika duhet ne rregulll

                Else
                    distanca1 = DistanceTo(P_x, p_y, A_x, a_y)
                    distanca2 = DistanceTo(P_x, p_y, B_x, b_y)
                    If distanca1 < distanca2 Then
                        rikthex = A_x
                        rikthey = a_y
                    Else
                        rikthex = B_x
                        rikthey = b_y
                    End If

                End If
            Else
                If rikthey > a_y And rikthey < b_y Then 'pika duhet ne rregulll

                Else
                    distanca1 = DistanceTo(P_x, p_y, A_x, a_y)
                    distanca2 = DistanceTo(P_x, p_y, B_x, b_y)
                    If distanca1 < distanca2 Then
                        rikthex = A_x
                        rikthey = a_y
                    Else
                        rikthex = B_x
                        rikthey = b_y
                    End If

                End If

            End If
        Else
            distanca1 = DistanceTo(P_x, p_y, A_x, a_y)
            distanca2 = DistanceTo(P_x, p_y, B_x, b_y)
            If distanca1 < distanca2 Then
                rikthex = A_x
                rikthey = a_y
            Else
                rikthex = B_x
                rikthey = b_y
            End If
        End If
    End If
    s1(0) = rikthex
    s1(1) = rikthey
    Return s1

End Function

答案 11 :(得分:0)

如果有人在寻找 python 实现,这里是代码:

p1 和 p2 是线,p3 是点

def p4(p1, p2, p3):
     x1, y1 = p1
     x2, y2 = p2
     x3, y3 = p3
     dx, dy = x2-x1, y2-y1
     det = dx*dx + dy*dy
     a = (dy*(y3-y1)+dx*(x3-x1))/det
     x= x1+a*dx, y1+a*dy
     # print(x)
     if x[0]<x1 or x[1]<y1:
         return p1
     elif x[0]>x2 or x[1]>y2:
         return p2
     else:
         return x

这是从另一个线程中提取的并稍作修改。 Python: point on a line closest to third point

答案 12 :(得分:-3)

算法非常简单:

你有3分 - 三角形。从那里你应该能够找到AB,AC,BC。

问这个: http://www.topcoder.com/tc?d1=tutorials&d2=geometry1&module=Static#line_point_distance