get closest point to a line

后端 未结 12 1945
逝去的感伤
逝去的感伤 2020-11-28 04:45

I\'d like to have a straight forward C# function to get a closest point (from a point P) to a line-segment, AB. An abstract function may look like this. I\'ve search through

相关标签:
12条回答
  • 2020-11-28 05:06

    Find the slope a1 of AB by dividing the y-difference with the x-difference; then draw a perpendicular line (with slope a2 = -1/a1, you need to solve for the offset (b2) by putting P's coordinates into y = a2*x + b2); then you have two lines (i.e. two linear equations), and you need to solve the intersection. That will be your closest point.

    Do the math right, and the function will be pretty trivial to write.

    To elaborate a bit:

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

    Hope I didn't mess up somewhere :) UPDATE Of course I did. Serve me right for not working things out on paper first. I deserved every downvote, but I'd've expected someone to correct me. Fixed (I hope).

    Arrows point the way.

    UPDATE Ah, the corner cases. Yeah, some languages don't handle infinities well. I did say the solution was language-free...

    You can check the special cases, they're quite easy. The first one is when the x difference is 0. That means the line is vertical, and the closest point is on a horizontal perpendicular. Thus, x = Ax, y = Px.

    The second one is when y difference is 0, and the opposite is true. Thus, x = Px, y = Ay

    0 讨论(0)
  • 2020-11-28 05:06

    In case somebody is looking for a way to do this with Java + LibGdx:

    Intersector.nearestSegmentPoint
    
    0 讨论(0)
  • 2020-11-28 05:08

    The answer from Justin L. is almost fine, but it doesn't check if the normalized distance is less than 0, or higher than the AB vector magnitude. Then it won't work well when P vector proyection is out of bounds (from the line segment AB). Here's the corrected pseudocode:

        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
        }
    
    }
    
    0 讨论(0)
  • 2020-11-28 05:10

    This is the right algorythm to get nearest point of a segment from a point(Tested)(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
    
    0 讨论(0)
  • 2020-11-28 05:11

    Here are extension methods that should do the trick:

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

    Then just call:

    P.DistanceTo(A, B);
    

    To get distance of point "P" from line |AB|. It should be easy to modify this for PointF.

    Finding the closest point then is just a matter of searching minimal distance. LINQ has methods for this.

    0 讨论(0)
  • 2020-11-28 05:12

    This answer is based on ideas from projective geometry.

    Compute the cross product (Ax,Ay,1)×(Bx,By,1)=(u,v,w). The resulting vector describes the line connecting A and B: it has the equation ux+vy+w=0. But you can also interpret (u,v,0) as a point infinitely far away in a direction perpendicular to that line. Doing another cross product you get the line joining hat point to P: (u,v,0)×(Px,Py,1). And to intersect that line with the line AB, you do another cross product: ((u,v,0)×(Px,Py,1))×(u,v,w). The result will be a homogenous coordinate vector (x,y,z) from which you can read the coordinates of this closest point as (x/z,y/z).

    Take everything together and you get the following formula:

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

    Using a computer algebra system, you can find the resulting coordinates to be the following:

    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
    

    As you notice, there are a lot of recurring terms. Inventing (pretty much arbitrary) names for these, you can get the following final result, written in pseudocode:

    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)
    

    Benefits of this approach:

    • No case distinctions
    • No square roots
    • Only a single division
    0 讨论(0)
提交回复
热议问题