web-dev-qa-db-de.com

Wie können Sie feststellen, dass ein Punkt zwischen zwei anderen Punkten in einem Liniensegment liegt?

Angenommen, Sie haben eine zweidimensionale Ebene mit zwei Punkten (a und b genannt), die durch eine ganze Zahl x und eine ganze Zahl y für jeden Punkt dargestellt wird.

Wie können Sie feststellen, ob sich ein weiterer Punkt c auf dem durch a und b definierten Liniensegment befindet?

Ich benutze meistens Python, aber Beispiele in jeder Sprache wären hilfreich.

81
Paul D. Eden

Prüfen Sie, ob das Kreuzprodukt von (b-a) und (c-a) 0 ist, wie Darius Bacon mitteilt, wenn die Punkte a, b und c miteinander übereinstimmen.

Da Sie jedoch wissen möchten, ob c zwischen a und b liegt, müssen Sie auch prüfen, ob das dot-Produkt von (ba) und (ca) positiv und weniger als ist das Quadrat der Entfernung zwischen a und b.

Im nicht optimierten Pseudocode:

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

So würde ich es machen:

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

Prüfen Sie, ob das Kreuzprodukt von b-a und c-a0 ist: Dies bedeutet, dass alle Punkte kollinear sind. Wenn dies der Fall ist, prüfen Sie, ob die Koordinaten von c zwischen a und b liegen. Verwenden Sie entweder die x- oder die y-Koordinate, solange sich a und b auf dieser Achse befinden (oder auf beiden Achsen gleich sind).

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

Diese Antwort war früher ein Durcheinander von drei Updates. Die lohnenswerten Informationen von ihnen: Brian Hayes Kapitel in Beautiful Code deckt den Gestaltungsraum für eine Kollinearitätstestfunktion ab - nützlicher Hintergrund. Vincents Antwort half, diese zu verbessern. Und Hayes schlug vor, nur eine der x- oder y-Koordinaten zu testen; ursprünglich hatte der Code and anstelle von if a.x != b.x else.

30
Darius Bacon

Hier ist ein anderer Ansatz: 

  • Nehmen wir an, die zwei Punkte seien A (x1, y1) und B (x2, y2)
  • Die Gleichung der Linie, die durch diese Punkte verläuft, ist (x-x1)/(y-y1) = (x2-x1)/(y2-y1) .. (nur die Steigungen gleichsetzen)

Punkt C (x3, y3) liegt zwischen A und B, wenn

  • x3, y3 erfüllt die obige Gleichung.
  • x3 liegt zwischen x1 & x2 und y3 liegt zwischen y1 & y2 (Trivialcheck)
7
Sridhar Iyer

Die Länge des Segments ist nicht wichtig, daher ist die Verwendung einer Quadratwurzel nicht erforderlich und sollte vermieden werden, da wir etwas an Genauigkeit verlieren könnten.

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)

Einige zufällige Verwendungsbeispiele:

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

Hier ist eine andere Vorgehensweise, mit Code, der in C++ angegeben wird. Bei zwei Punkten, l1 und l2, ist es trivial, das Liniensegment zwischen ihnen als auszudrücken

l1 + A(l2 - l1)

dabei gilt 0 <= A <= 1. Dies wird als Vektordarstellung einer Linie bezeichnet, wenn Sie darüber hinaus daran interessiert sind, sie nur für dieses Problem zu verwenden. Wir können die x- und y-Komponenten davon aufspalten und geben:

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

Nehmen Sie einen Punkt (x, y) und setzen Sie seine x- und y-Komponente in diese beiden Ausdrücke ein, um nach A zu lösen. Der Punkt liegt auf der Linie, wenn die Lösungen für A in beiden Ausdrücken gleich sind und 0 <= A <= 1. Weil Wenn für A eine Division erforderlich ist, gibt es spezielle Fälle, in denen die Division durch Null gehandhabt werden muss, wenn das Liniensegment horizontal oder vertikal ist. Die endgültige Lösung lautet wie folgt:

// 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, viele Erwähnungen der linearen Algebra (Kreuzprodukt von Vektoren) und dies funktioniert in einem realen (dh kontinuierlichen oder Fließkomma) Raum, aber die Frage stellte ausdrücklich fest, dass die beiden Punkte als ganze Zahlen und somit ein Kreuzprodukt ausgedrückt wurden ist nicht die richtige Lösung, obwohl es eine ungefähre Lösung geben kann.

Die richtige Lösung ist die Verwendung des Bresenham-Linienalgorithmus zwischen den beiden Punkten und die Überprüfung, ob der dritte Punkt einer der Punkte auf der Linie ist. Wenn die Punkte so weit voneinander entfernt sind, dass die Berechnung des Algorithmus nicht performant ist (und dies für den Fall sehr groß sein muss), bin ich sicher, Sie könnten herumgraben und Optimierungen finden.

3
cletus

Berechnen Sie mit einem geometrischeren Ansatz die folgenden Abstände:

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)

und prüfen, ob ac + bc gleich ab ist:

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

Das liegt daran, dass es drei Möglichkeiten gibt:

  • Die 3 Punkte bilden ein Dreieck => ac + bc> ab
  • Sie sind kollinear und c liegt außerhalb des ab segment => ac + bc> ab
  • Sie sind kollinear und c befindet sich im ab segment => ac + bc = ab
3
efotinis

Das Skalarprodukt zwischen (c-a) und (b-a) muss dem Produkt ihrer Länge entsprechen (dh die Vektoren (c-a) und (b-a) sind in derselben Richtung ausgerichtet). Darüber hinaus muss die Länge von (c-a) kleiner oder gleich der Länge von (b-a) sein. 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

Ich brauchte dies für Javascript zur Verwendung in einer HTML5-Leinwand, um festzustellen, ob der Cursor des Benutzers über oder in der Nähe einer bestimmten Linie war. Also habe ich die von Darius Bacon gegebene Antwort in ein Kaffeescript geändert:

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

Jeder Punkt auf dem Liniensegment (a, b) (wobei a und b Vektoren sind) kann als ausgedrückt werden. Linearkombination der beiden Vektoren a und b:

Anders ausgedrückt, wenn c auf dem Liniensegment liegt (a, b):

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

Wenn wir nach m suchen, erhalten wir:

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

Unser Test wird also (in 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

So habe ich es in der Schule gemacht. Ich habe vergessen, warum es keine gute Idee ist.

BEARBEITEN: 

@Darius Bacon: zitiert ein "Beautiful Code" Buch das eine Erklärung enthält, warum der unten stehende Code keine gute Idee ist.

#!/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

Hier ist etwas Java-Code, der für mich funktioniert hat:

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 # Von http://www.faqs.org/faqs/graphics/algorithms-faq/ -> Subject 1.02: Wie finde ich die Entfernung von einem Punkt zu einer Linie?

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

Hier ist meine Lösung mit C # in 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

wie wäre es, sicherzustellen, dass die Steigung gleich ist und der Punkt zwischen den anderen liegt?

gegebene Punkte (x1, y1) und (x2, y2) (mit x2> x1) und Kandidatenpunkt (a, b)

wenn (b-y1)/(a-x1) = (y2-y2)/(x2-x1) und x1 <a <x2 

Dann muss (a, b) zwischen (x1, y1) und (x2, y2) liegen.

0
Charles Bretana

Eine Antwort in C # unter Verwendung einer Vector2D-Klasse

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

Beachten Sie, dass

s * s

ist das Punktprodukt des Segmentvektors durch Operatorüberladung in C #

Der Schlüssel besteht darin, die Projektion des Punktes auf die unendliche Linie zu nutzen und zu beobachten, dass die Skalargröße der Projektion uns trivial sagt, ob die Projektion auf dem Segment ist oder nicht. Wir können die Grenzen der Skalarmenge anpassen, um eine Fuzzy-Toleranz zu verwenden.

Wenn die Projektion innerhalb der Grenzen liegt, testen wir nur, ob der Abstand vom Punkt zur Projektion innerhalb der Grenzen liegt.

Der Vorteil gegenüber dem produktübergreifenden Ansatz besteht darin, dass die Toleranz einen sinnvollen Wert hat.

0
bradgonesurfing

C # -Version von Jules 'Antwort:

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

Sie können das Keil- und Punktprodukt verwenden:

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