web-dev-qa-db-ja.com

直線に最も近い点を取得

直線セグメントABに最も近い点(点Pから)を取得する単純なC#関数が欲しいのですが。抽象関数は次のようになります。 SO=を検索しましたが、(私が)使用できるソリューションは見つかりませんでした。

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

以下はRuby Pointオブジェクトにそれぞれxおよびyフィールドがあると仮定して、疑似コードに偽装したものです。

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

または:

Wikipediaの Line-Line Intersection から。まず、Qを見つけます。Qは、Pから「右方向」に一歩進んだときに必要な2番目のポイントです。これにより、4つのポイントが得られます。

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

パフォーマンス上の理由から、キャッシュ、ステップのスキップなどが可能です。

40
Justin L.

上記に基づく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;
        }
    }
33
N.Schilke

ポイント(X)は、ポイントABの線形結合になります:

X = k A + (1-k) B

Xが実際にラインセグメント上にあるためには、パラメーターkは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
10
comingstorm

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
    }

}
6
stbn

これはずっと前に書いたものですが、他の人が言ったことと大差ありませんが、PointFという名前のクラス(または構造体)にXYのメンバーがある場合、それはC#のコピー/貼り付けソリューションです。

private static PointF ClosestPointToSegment(PointF P, PointF A, PointF B)
{
    PointF a_to_p = new PointF(), a_to_b = new PointF();
    a_to_p.X = P.X - A.X;
    a_to_p.Y = P.Y - A.Y; //     # Storing vector A->P  
    a_to_b.X = B.X - A.X;
    a_to_b.Y = B.Y - A.Y; //     # Storing vector A->B

    float atb2 = a_to_b.X * a_to_b.X + a_to_b.Y * a_to_b.Y;
    float atp_dot_atb = a_to_p.X * a_to_b.X + a_to_p.Y * a_to_b.Y; // The dot product of a_to_p and a_to_b
    float t = atp_dot_atb / atb2;  //  # The normalized "distance" from a to the closest point
    return new PointF(A.X + a_to_b.X * t, A.Y + a_to_b.Y * t);
}

更新:コメントを見ると、受け入れられた回答で言及されているのと同じソースコードからC#に適合させたようです。

5
Darien Pardinas

Y差をx差で除算して、ABの勾配a1を求めます。次に、垂直線を描きます(勾配a2 = -1/a1の場合、Pの座標をy = a2 * x + b2に入れることにより、オフセット(b2)を解く必要があります)。次に、2つの線(つまり、2つの線形方程式)があり、交差を解決する必要があります。それがあなたの最も近いポイントになります。

計算を正しく行うと、関数を書くのはかなり簡単になります。

少し詳しく説明します。

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

私がどこかで台無しにしていないことを願っています:) [〜#〜] update [〜#〜]もちろん私はやった。最初に紙の上で物事を解決しないために私に正しく役立ちます。私はすべての反対票に値しましたが、誰かが私を訂正することを期待していました。修正済み(希望)。

矢印が道を示しています。

[〜#〜] update [〜#〜]ああ、コーナーケース。ええ、いくつかの言語は無限をうまく処理しません。私はその解決策が言語を含まないものであったと言いました...

あなたは特別なケースをチェックすることができ、それらは非常に簡単です。 1つ目は、xの差が0の場合です。これは、線が垂直であり、最も近い点が水平の垂線上にあることを意味します。したがって、x = Ax, y = Px

2つ目は、yの差が0で、その逆の場合です。したがって、x = Px, y = Ay

3
Amadan

この答えは、射影幾何学からのアイデアに基づいています。

外積(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/z、y/z)として読み取ることができる同種の座標ベクトル(x、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)

このアプローチの利点:

  • 大文字小文字の区別なし
  • 平方根なし
  • 単一の部門のみ
3
MvG

最も近い点Cは、傾きがABの逆数であり、Pと交差する直線上にあります。これは宿題のように聞こえますが、スポイラーアラートレベルを上げるために、かなり強力なヒントをいくつか示します。

  • このような行は1つしかありません。

  • これは2つの線の方程式のシステムです。 xyを解くだけです。

  • ABの間に線分を描画します。これをLと呼びます。 Lの方程式はy = mx + b、ここでmは、x座標に対するy座標の比率です。式でbまたはAを使用してBを解きます。

  • 上記と同じことを行いますが、CPを使用します。次に、連立線形方程式系を解きます。

  • Googleの検索では、いくつかの例から選択できます。

1
John Feminella

トリックを実行する必要がある拡張メソッドは次のとおりです。

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にはこのためのメソッドがあります。

1
Dave_cz

Java + LibGdxでこれを行う方法を探している場合:

Intersector.nearestSegmentPoint
0
Nick Bilyk

これは、ポイントから最も近いセグメントのポイントを取得するための正しいアルゴリズムです(テスト済み)(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
Bexhet Islamaj