web-dev-qa-db-fra.com

Comment pouvez-vous déterminer qu'un point se situe entre deux autres points d'un segment de ligne?

Supposons que vous avez un plan en deux dimensions avec 2 points (appelés a et b), représenté par un entier x et un entier y pour chaque point.

Comment pouvez-vous déterminer si un autre point c est sur le segment de droite défini par a et b?

J'utilise le plus souvent python, mais des exemples dans n'importe quelle langue seraient utiles.

81
Paul D. Eden

Vérifiez si le produit croisé de (b-a) et (c-a) est 0, comme dit Darius Bacon, vous indique si les points a, b et c sont alignés.

Mais, comme vous voulez savoir si c est entre a et b, vous devez également vérifier que le produit scalaire de (ba) et (ca) est positif et est moins que le carré de la distance entre a et b.

En pseudocode non optimisé:

def isBetween(a, b, c):
    crossproduct = (c.y - a.y) * (b.x - a.x) - (c.x - a.x) * (b.y - a.y)

    # compare versus epsilon for floating point values, or != 0 if using integers
    if abs(crossproduct) > epsilon:
        return False

    dotproduct = (c.x - a.x) * (b.x - a.x) + (c.y - a.y)*(b.y - a.y)
    if dotproduct < 0:
        return False

    squaredlengthba = (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y)
    if dotproduct > squaredlengthba:
        return False

    return True
106
Cyrille Ka

Voici comment je le ferais:

def distance(a,b):
    return sqrt((a.x - b.x)**2 + (a.y - b.y)**2)

def is_between(a,c,b):
    return distance(a,c) + distance(c,b) == distance(a,b)
38
Jules

Vérifiez si le produit croisé de b-a et c-a est0: cela signifie que tous les points sont colinéaires. Si c'est le cas, vérifiez si les coordonnées de c sont comprises entre a 's et b' s. Utilisez les coordonnées x ou y, tant que a et b sont séparés sur cet axe (ou sont identiques sur les deux).

def is_on(a, b, c):
    "Return true iff point c intersects the line segment from a to b."
    # (or the degenerate case that all 3 points are coincident)
    return (collinear(a, b, c)
            and (within(a.x, c.x, b.x) if a.x != b.x else 
                 within(a.y, c.y, b.y)))

def collinear(a, b, c):
    "Return true iff a, b, and c all lie on the same line."
    return (b.x - a.x) * (c.y - a.y) == (c.x - a.x) * (b.y - a.y)

def within(p, q, r):
    "Return true iff q is between p and r (inclusive)."
    return p <= q <= r or r <= q <= p

Cette réponse était un désordre de trois mises à jour. L’information qui leur est utile: le chapitre in Beautiful Code de Brian Hayes couvre l’espace de conception d’une fonction de test de colinéarité - un fond utile. _ { La réponse de Vincent } _ a aidé à améliorer celui-ci. Et c’est Hayes qui a suggéré de ne tester qu’une des coordonnées x ou y; à l'origine, le code avait and à la place de if a.x != b.x else.

30
Darius Bacon

Voici une autre approche: 

  • Supposons que les deux points soient A (x1, y1) et B (x2, y2)
  • L'équation de la ligne passant par ces points est (x-x1)/(y-y1) = (x2-x1)/(y2-y1).

Le point C (x3, y3) se situera entre A et B si:

  • x3, y3 vérifie l'équation ci-dessus.
  • x3 se situe entre x1 et x2 et y3 se situe entre y1 et y2 (vérification triviale)
7
Sridhar Iyer

La longueur du segment n’ayant pas d’importance, l’utilisation d’une racine carrée n’est donc pas nécessaire et doit être évitée car nous risquons de perdre de la précision.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Segment:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def is_between(self, c):
        # Check if slope of a to c is the same as a to b ;
        # that is, when moving from a.x to c.x, c.y must be proportionally
        # increased than it takes to get from a.x to b.x .

        # Then, c.x must be between a.x and b.x, and c.y must be between a.y and b.y.
        # => c is after a and before b, or the opposite
        # that is, the absolute value of cmp(a, b) + cmp(b, c) is either 0 ( 1 + -1 )
        #    or 1 ( c == a or c == b)

        a, b = self.a, self.b             

        return ((b.x - a.x) * (c.y - a.y) == (c.x - a.x) * (b.y - a.y) and 
                abs(cmp(a.x, c.x) + cmp(b.x, c.x)) <= 1 and
                abs(cmp(a.y, c.y) + cmp(b.y, c.y)) <= 1)

Quelques exemples d'utilisation aléatoires:

a = Point(0,0)
b = Point(50,100)
c = Point(25,50)
d = Point(0,8)

print Segment(a,b).is_between(c)
print Segment(a,b).is_between(d)
6
vincent

Voici une façon différente de s'y prendre, avec du code donné en C++. Étant donné deux points, l1 et l2, il est trivial d’exprimer le segment de ligne entre eux comme

l1 + A(l2 - l1)

où 0 <= A <= 1. Ceci est connu sous le nom de représentation vectorielle d'une ligne si vous êtes intéressé au-delà de son utilisation pour ce problème. Nous pouvons séparer les composants x et y de ceci, en donnant:

x = l1.x + A(l2.x - l1.x)
y = l1.y + A(l2.y - l1.y)

Prenez un point (x, y) et substituez ses composantes x et y dans ces deux expressions pour résoudre A. Le point est sur la ligne si les solutions pour A dans les deux expressions sont égales et 0 <= A <= 1. Parce que Pour résoudre le problème A nécessite une division, il existe des cas spéciaux nécessitant un traitement pour arrêter la division par zéro lorsque le segment de ligne est horizontal ou vertical. La solution finale est la suivante:

// Vec2 is a simple x/y struct - it could very well be named Point for this use

bool isBetween(double a, double b, double c) {
    // return if c is between a and b
    double larger = (a >= b) ? a : b;
    double smaller = (a != larger) ? a : b;

    return c <= larger && c >= smaller;
}

bool pointOnLine(Vec2<double> p, Vec2<double> l1, Vec2<double> l2) {
    if(l2.x - l1.x == 0) return isBetween(l1.y, l2.y, p.y); // vertical line
    if(l2.y - l1.y == 0) return isBetween(l1.x, l2.x, p.x); // horizontal line

    double Ax = (p.x - l1.x) / (l2.x - l1.x);
    double Ay = (p.y - l1.y) / (l2.y - l1.y);

    // We want Ax == Ay, so check if the difference is very small (floating
    // point comparison is fun!)

    return fabs(Ax - Ay) < 0.000001 && Ax >= 0.0 && Ax <= 1.0;
}
4
Matthew Henry

Ok, beaucoup de mentions de l'algèbre linéaire (produit croisé de vecteurs) et cela fonctionne dans un espace réel (c'est-à-dire un point continu ou à virgule flottante), mais la question précise que les deux points sont exprimés sous la forme integers et donc un produit croisé n’est pas la solution correcte, bien qu’elle puisse donner une solution approximative.

La solution correcte consiste à utiliser Algorithme de Bresenham - entre les deux points et de voir si le troisième point est l'un des points de la ligne. Si les points sont suffisamment éloignés pour que l'algorithme soit non performant (et qu'il faille le faire très gros pour que ce soit le cas), je suis sûr que vous pouvez creuser et trouver des optimisations.

3
cletus

En utilisant une approche plus géométrique, calculez les distances suivantes:

ab = sqrt((a.x-b.x)**2 + (a.y-b.y)**2)
ac = sqrt((a.x-c.x)**2 + (a.y-c.y)**2)
bc = sqrt((b.x-c.x)**2 + (b.y-c.y)**2)

et tester si ac + bc est égal à ab :

is_on_segment = abs(ac + bc - ab) < EPSILON

C'est parce qu'il y a trois possibilités:

  • Les 3 points forment un triangle => ac + bc> ab
  • Ils sont colinéaires et c est en dehors du ab segment => ac + bc> ab
  • Ils sont colinéaires et c est à l'intérieur du ab segment => ac + bc = ab
3
efotinis

Le produit scalaire entre (c-a) et (b-a) doit être égal au produit de leurs longueurs (cela signifie que les vecteurs (c-a) et (b-a) sont alignés et dans la même direction). De plus, la longueur de (c-a) doit être inférieure ou égale à celle de (b-a). Pseudocode:

# epsilon = small constant

def isBetween(a, b, c):
    lengthca2  = (c.x - a.x)*(c.x - a.x) + (c.y - a.y)*(c.y - a.y)
    lengthba2  = (b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y)
    if lengthca2 > lengthba2: return False
    dotproduct = (c.x - a.x)*(b.x - a.x) + (c.y - a.y)*(b.y - a.y)
    if dotproduct < 0.0: return False
    if abs(dotproduct*dotproduct - lengthca2*lengthba2) > epsilon: return False 
    return True
2

J'avais besoin de cela pour utiliser javascript dans un canevas html5 afin de détecter si le curseur de l'utilisateur était au-dessus ou près d'une certaine ligne. J'ai donc modifié la réponse donnée par Darius Bacon en coffeescript:

is_on = (a,b,c) ->
    # "Return true if point c intersects the line segment from a to b."
    # (or the degenerate case that all 3 points are coincident)
    return (collinear(a,b,c) and withincheck(a,b,c))

withincheck = (a,b,c) ->
    if a[0] != b[0]
        within(a[0],c[0],b[0]) 
    else 
        within(a[1],c[1],b[1])

collinear = (a,b,c) ->
    # "Return true if a, b, and c all lie on the same line."
    ((b[0]-a[0])*(c[1]-a[1]) < (c[0]-a[0])*(b[1]-a[1]) + 1000) and ((b[0]-a[0])*(c[1]-a[1]) > (c[0]-a[0])*(b[1]-a[1]) - 1000)

within = (p,q,r) ->
    # "Return true if q is between p and r (inclusive)."
    p <= q <= r or r <= q <= p
2
bfcoder

Tout point du segment de droite ( a , b ) (où a et b sont des vecteurs) peut être exprimé sous la forme d'une combinaison linéaire des deux vecteurs. a et b :

En d'autres termes, si c se trouve sur le segment de droite ( a , b ):

c = ma + (1 - m)b, where 0 <= m <= 1

En résolvant pour m, nous obtenons:

m = (c.x - b.x)/(a.x - b.x) = (c.y - b.y)/(a.y - b.y)

Donc, notre test devient (en Python):

def is_on(a, b, c):
    """Is c on the line segment ab?"""

    def _is_zero( val ):
        return -epsilon < val < epsilon

    x1 = a.x - b.x
    x2 = c.x - b.x
    y1 = a.y - b.y
    y2 = c.y - b.y

    if _is_zero(x1) and _is_zero(y1):
        # a and b are the same point:
        # so check that c is the same as a and b
        return _is_zero(x2) and _is_zero(y2)

    if _is_zero(x1):
        # a and b are on same vertical line
        m2 = y2 * 1.0 / y1
        return _is_zero(x2) and 0 <= m2 <= 1
    Elif _is_zero(y1):
        # a and b are on same horizontal line
        m1 = x2 * 1.0 / x1
        return _is_zero(y2) and 0 <= m1 <= 1
    else:
        m1 = x2 * 1.0 / x1
        if m1 < 0 or m1 > 1:
            return False
        m2 = y2 * 1.0 / y1
        return _is_zero(m2 - m1)
1
Shankster

Voici comment je l'ai fait à l'école. J'ai oublié pourquoi ce n'est pas une bonne idée.

MODIFIER: 

@Darius Bacon: cite un livre "Beautiful Code" qui contient une explication de la raison pour laquelle le code ci-dessous n'est pas une bonne idée.

#!/usr/bin/env python
from __future__ import division

epsilon = 1e-6

class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

class LineSegment:
    """
    >>> ls = LineSegment(Point(0,0), Point(2,4))
    >>> Point(1, 2) in ls
    True
    >>> Point(.5, 1) in ls
    True
    >>> Point(.5, 1.1) in ls
    False
    >>> Point(-1, -2) in ls
    False
    >>> Point(.1, 0.20000001) in ls
    True
    >>> Point(.1, 0.2001) in ls
    False
    >>> ls = LineSegment(Point(1, 1), Point(3, 5))
    >>> Point(2, 3) in ls
    True
    >>> Point(1.5, 2) in ls
    True
    >>> Point(0, -1) in ls
    False
    >>> ls = LineSegment(Point(1, 2), Point(1, 10))
    >>> Point(1, 6) in ls
    True
    >>> Point(1, 1) in ls
    False
    >>> Point(2, 6) in ls 
    False
    >>> ls = LineSegment(Point(-1, 10), Point(5, 10))
    >>> Point(3, 10) in ls
    True
    >>> Point(6, 10) in ls
    False
    >>> Point(5, 10) in ls
    True
    >>> Point(3, 11) in ls
    False
    """
    def __init__(self, a, b):
        if a.x > b.x:
            a, b = b, a
        (self.x0, self.y0, self.x1, self.y1) = (a.x, a.y, b.x, b.y)
        self.slope = (self.y1 - self.y0) / (self.x1 - self.x0) if self.x1 != self.x0 else None

    def __contains__(self, c):
        return (self.x0 <= c.x <= self.x1 and
                min(self.y0, self.y1) <= c.y <= max(self.y0, self.y1) and
                (not self.slope or -epsilon < (c.y - self.y(c.x)) < epsilon))

    def y(self, x):        
        return self.slope * (x - self.x0) + self.y0

if __== '__main__':
    import  doctest
    doctest.testmod()
1
jfs

Voici un code Java qui a fonctionné pour moi:

boolean liesOnSegment(Coordinate a, Coordinate b, Coordinate  c) {

    double dotProduct = (c.x - a.x) * (c.x - b.x) + (c.y - a.y) * (c.y - b.y);
    if (dotProduct < 0) return true;
    return false;
}
1
mihahh

c # De http://www.faqs.org/faqs/graphics/algorithms-faq/ -> Sujet 1.02: Comment trouver la distance d'un point à une ligne?

Boolean Contains(PointF from, PointF to, PointF pt, double epsilon)
        {

            double segmentLengthSqr = (to.X - from.X) * (to.X - from.X) + (to.Y - from.Y) * (to.Y - from.Y);
            double r = ((pt.X - from.X) * (to.X - from.X) + (pt.Y - from.Y) * (to.Y - from.Y)) / segmentLengthSqr;
            if(r<0 || r>1) return false;
            double sl = ((from.Y - pt.Y) * (to.X - from.X) - (from.X - pt.X) * (to.Y - from.Y)) / System.Math.Sqrt(segmentLengthSqr);
            return -epsilon <= sl && sl <= epsilon;
        }
1
edid

Voici ma solution avec C # dans Unity.

private bool _isPointOnLine( Vector2 ptLineStart, Vector2 ptLineEnd, Vector2 ptPoint )
{
    bool bRes = false;
    if((Mathf.Approximately(ptPoint.x, ptLineStart.x) || Mathf.Approximately(ptPoint.x, ptLineEnd.x)))
    {
        if(ptPoint.y > ptLineStart.y && ptPoint.y < ptLineEnd.y)
        {
            bRes = true;
        }
    }
    else if((Mathf.Approximately(ptPoint.y, ptLineStart.y) || Mathf.Approximately(ptPoint.y, ptLineEnd.y)))
    {
        if(ptPoint.x > ptLineStart.x && ptPoint.x < ptLineEnd.x)
        {
            bRes = true;
        }
    }
    return bRes;
}
0
kaleidos

que diriez-vous simplement de vous assurer que la pente est la même et que le point est entre les autres?

points donnés (x1, y1) et (x2, y2) (avec x2> x1) et les points candidats (a, b)

si (b-y1)/(a-x1) = (y2-y2)/(x2-x1) et x1 <a <x2 

Alors (a, b) doit être en ligne entre (x1, y1) et (x2, y2)

0
Charles Bretana

Une réponse en C # en utilisant une classe Vector2D

public static bool IsOnSegment(this Segment2D @this, Point2D c, double tolerance)
{
     var distanceSquared = tolerance*tolerance;
     // Start of segment to test point vector
     var v = new Vector2D( @this.P0, c ).To3D();
     // Segment vector
     var s = new Vector2D( @this.P0, @this.P1 ).To3D();
     // Dot product of s
     var ss = s*s;
     // k is the scalar we multiply s by to get the projection of c onto s
     // where we assume s is an infinte line
     var k = v*s/ss;
     // Convert our tolerance to the units of the scalar quanity k
     var kd = tolerance / Math.Sqrt( ss );
     // Check that the projection is within the bounds
     if (k <= -kd || k >= (1+kd))
     {
        return false;
     }
     // Find the projection point
     var p = k*s;
     // Find the vector between test point and it's projection
     var vp = (v - p);
     // Check the distance is within tolerance.
     return vp * vp < distanceSquared;
}

Notez que

s * s

est le produit scalaire du vecteur de segment via la surcharge de l'opérateur en C #

La clé est de tirer parti de la projection du point sur la ligne infinie et d’observer que la quantité scalaire de la projection nous dit de manière triviale si la projection est sur le segment ou non. Nous pouvons ajuster les limites de la quantité scalaire pour utiliser une tolérance floue.

Si la projection est dans les limites, nous vérifions simplement si la distance entre le point et la projection est dans les limites.

L'avantage par rapport à l'approche multi-produits est que la tolérance a une valeur significative.

0
bradgonesurfing

La version C # de la réponse de Jules:

public static double CalcDistanceBetween2Points(double x1, double y1, double x2, double y2)
{
    return Math.Sqrt(Math.Pow (x1-x2, 2) + Math.Pow (y1-y2, 2));
}

public static bool PointLinesOnLine (double x, double y, double x1, double y1, double x2, double y2, double allowedDistanceDifference)
{
    double dist1 = CalcDistanceBetween2Points(x, y, x1, y1);
    double dist2 = CalcDistanceBetween2Points(x, y, x2, y2);
    double dist3 = CalcDistanceBetween2Points(x1, y1, x2, y2);
    return Math.Abs(dist3 - (dist1 + dist2)) <= allowedDistanceDifference;
}
0
Tone Škoda

Vous pouvez utiliser le produit wedge and dot:

def dot(v,w): return v.x*w.x + v.y*w.y
def wedge(v,w): return v.x*w.y - v.y*w.x

def is_between(a,b,c):
   v = a - b
   w = b - c
   return wedge(v,w) == 0 and dot(v,w) > 0
0
Jules