web-dev-qa-db-fra.com

obtenir le point le plus proche d'une ligne

J'aimerais avoir une fonction C # directe pour obtenir un point le plus proche (d'un point P) à un segment de droite, AB. Une fonction abstraite peut ressembler à ceci. J'ai effectué une recherche dans SO mais je n'ai pas trouvé de solution utilisable (par moi).

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

Voici Ruby déguisé en pseudo-code, en supposant que les objets Point ont chacun un champ x et 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

Alternativement:

De Intersection de ligne en ligne , sur Wikipedia. Tout d’abord, trouvez Q, qui est un deuxième point qui doit être obtenu en faisant un pas de P dans la "bonne direction". Cela nous donne quatre points.

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

La mise en cache, le saut d'étapes, etc. sont possibles, pour des raisons de performances.

36
Justin L.

si quelqu'un est intéressé par une fonction C # XNA basée sur ce qui précède:

    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;
        }
    }
30
N.Schilke

Votre point (X) sera une combinaison linéaire de points A et B:

X = k A + (1-k) B

Pour que X soit réellement sur le segment de ligne, le paramètre k doit être compris entre 0 et 1 inclus. Vous pouvez calculer k comme suit:

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

(où la période désigne le produit scalaire)

Ensuite, pour vous assurer que le point est réellement sur le segment de ligne:

if k_raw < 0:
    k= 0
Elif k_raw > 1:
    k= 1
else:
    k= k_raw
8
comingstorm

La réponse de Justin L. est presque correcte, mais elle ne vérifie pas si la distance normalisée est inférieure à 0 ou supérieure à la magnitude du vecteur AB. Ensuite, cela ne fonctionnera pas bien lorsque la protection du vecteur P est hors limites (à partir du segment de ligne 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

Trouvez la pente a1 de AB en divisant la différence y par la différence x; tracez ensuite une ligne perpendiculaire (avec une pente a2 = -1/a1, vous devez résoudre le décalage (b2) en mettant les coordonnées de P dans y = a2 * x + b2); Ensuite, vous avez deux lignes (c'est-à-dire deux équations linéaires) et vous devez résoudre l'intersection. Ce sera votre point le plus proche.

Faites les calculs correctement et la fonction sera assez simple à écrire.

Pour élaborer un peu:

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

J'espère que je ne me suis pas trompé quelque part :)UPDATEBien sûr que je l'ai fait. Sers-moi bien pour ne pas avoir d'abord travaillé sur du papier. Je méritais tous les votes négatifs, mais je m'attendais à ce que quelqu'un me corrige. Fixe (j'espère).

Les flèches indiquent le chemin.

UPDATEAh, le coin des cas. Oui, certaines langues ne gèrent pas bien les infinis. J'ai dit que la solution était sans langage ...

Vous pouvez vérifier les cas spéciaux, ils sont assez faciles. Le premier est lorsque la différence x est 0. Cela signifie que la ligne est verticale et que le point le plus proche se trouve sur une perpendiculaire horizontale. Ainsi, x = Ax, y = Px.

La seconde est quand y est la différence 0, et le contraire est vrai. Ainsi, x = Px, y = Ay

3
Amadan

Cette réponse est basée sur des idées de géométrie projective.

Calculez le produit croisé (Ax, Ay, 1) × (Bx, By, 1) = (u, v, w). Le vecteur résultant décrit la ligne reliant A et B: il a l'équation ux + vy + w = ​​0. Mais vous pouvez aussi interpréter (u, v, 0) comme un point infiniment éloigné dans une direction perpendiculaire à cette ligne. En faisant un autre produit croisé, vous obtenez la ligne joignant le point du chapeau à P: (u, v, 0) × (Px, Py, 1). Et pour croiser cette ligne avec la ligne AB, vous créez un autre produit croisé: ((u, v, 0) × (Px, Py, 1)) × (u, v, w). Le résultat sera un vecteur de coordonnées homogène (x, y, z) à partir duquel vous pouvez lire les coordonnées de ce point le plus proche sous la forme (x/z, y/z).

Prenez tout ensemble et vous obtenez la formule suivante:

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

En utilisant un système de calcul formel, vous pouvez trouver les coordonnées résultantes comme suit:

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

Comme vous le constatez, il y a beaucoup de termes récurrents. Inventant des noms (plutôt arbitraires) pour ceux-ci, vous pouvez obtenir le résultat final suivant, écrit en 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)

Avantages de cette approche:

  • Aucune distinction de cas
  • Pas de racines carrées
  • Une seule division
3
MvG

J'ai écrit cela il y a longtemps, ce n'est pas très différent de ce que d'autres ont dit, mais c'est une solution copier/coller en C # si vous avez une classe (ou une structure) nommée PointF avec des membres X et Y:

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

Mise à jour : En regardant les commentaires, il semble que je l’ai adapté en C # à partir du même code source que celui mentionné dans la réponse acceptée.

3
Darien Pardinas

Le point C le plus proche sera sur une ligne dont la pente est l'inverse de AB et qui intersecte P. Cela ressemble à un devoir, mais je vais vous donner de bons conseils, par ordre croissant de niveau d'alerte:

  • Il ne peut y avoir qu'une seule ligne de ce type.

  • Ceci est un système d'équations à deux lignes. Il suffit de résoudre pour x et y.

  • Tracez un segment de droite entre A et B; appelez cette L. L'équation pour L est y = mx + b, où m est le rapport entre les coordonnées y et les coordonnées x. Résolvez pour b en utilisant soit A, soit B dans l’expression.

  • Faites comme ci-dessus, mais pour CP. Maintenant, résolvez le système d'équations linéaire simultané.

  • Une recherche sur Google vous donnera une foule d’exemples parmi lesquels choisir.

1
John Feminella

Voici les méthodes d'extension qui devraient faire l'affaire:

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

Alors appelez juste:

P.DistanceTo(A, B);

Pour obtenir la distance du point "P" de la ligne | AB |. Il devrait être facile de modifier ceci pour PointF.

Trouver le point le plus proche consiste alors simplement à rechercher une distance minimale. LINQ a des méthodes pour cela.

1
Dave_cz

Si quelqu'un cherche un moyen de le faire avec Java + LibGdx:

Intersector.nearestSegmentPoint
0
Nick Bilyk