Jak określić, czy punkt znajduje się między dwoma innymi punktami na odcinku linii?

96

Powiedzmy, że masz dwuwymiarową płaszczyznę z 2 punktami (zwanymi aib) reprezentowanymi przez liczbę całkowitą x i ay dla każdego punktu.

Jak określić, czy inny punkt c znajduje się na odcinku linii zdefiniowanym przez a i b?

Najczęściej używam Pythona, ale przykłady w dowolnym języku byłyby pomocne.

Paul D. Eden
źródło
4
Widzę DUŻO długości = sqrt (x) w tych odpowiedziach; mogą działać, ale nie są szybkie. Rozważ użycie kwadratu długości; jeśli porównujesz tylko wartości kwadratów długości ze sobą, nie ma utraty dokładności i oszczędzasz powolne wywołania funkcji sqrt ().
ojrac
1
Czy punkt c jest również reprezentowany przez 2 liczby całkowite? Jeśli tak, to czy chcesz wiedzieć, czy c leży dokładnie wzdłuż rzeczywistej linii prostej między a i b, czy też leży na przybliżeniu rastrowym linii prostej między a i b? To jest ważne wyjaśnienie.
RobS
Podobne pytanie padło tutaj: stackoverflow.com/q/31346862/1914034 z rozwiązaniem, gdy potrzebna jest odległość bufora od linii
Poniżej radaru
1
Ostrzeżenie dla przyszłych czytelników: Spora liczba odpowiedzi jest niepoprawnych lub niepełnych. Kilka przypadków brzegowych, które często nie działają, to linie poziome i pionowe.
Stefnotch

Odpowiedzi:

129

Sprawdź, czy iloczyn (ba) i (ca) wynosi 0, jak mówi Darius Bacon, mówi ci, czy punkty a, b i c są wyrównane.

Ale ponieważ chcesz wiedzieć, czy c jest między a i b, musisz również sprawdzić, czy iloczyn skalarny (ba) i (ca) jest dodatni i jest mniejszy niż kwadrat odległości między a i b.

W niezoptymalizowanym pseudokodzie:

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
Cyrille Ka
źródło
5
-epsilon < crossproduct < epsilon and min(a.x, b.x) <= c.x <= max(a.x, b.x) and min(a.y, b.y) <= c.y <= max(a.y, b.y)jest wystarczające, prawda?
jfs
9
Bezwzględna wartość iloczynu krzyżowego jest dwukrotnością powierzchni trójkąta utworzonego przez trzy punkty (ze znakiem wskazującym bok trzeciego punktu), więc IMHO powinieneś użyć epsilon, który jest proporcjonalny do odległości między dwoma punktami końcowymi.
bart
2
Czy możesz nam powiedzieć, dlaczego nie działałoby to z liczbami całkowitymi? Nie widzę problemu, pod warunkiem, że sprawdzenie epsilon jest zastąpione znakiem „! = 0”.
Cyrille Ka
2
Tak, dodatkowy nawias to tylko literówka. Minęły 4 lata, zanim ktoś coś powiedział. :)
Cyrille Ka
4
Należy zmienić nazwę a, b, c, aby było jaśniejsze, które są punktami końcowymi segmentu, a które są punktem zapytania.
Craig Gidney,
50

Oto jak bym to zrobił:

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)
Jules
źródło
7
Jedynym problemem jest stabilność numeryczna - przyjmowanie różnic w liczbach i tak dalej może stracić precyzję.
Jonathan Leffler
29
-epsilon < (distance(a, c) + distance(c, b) - distance(a, b)) < epsilon
jfs
1
@jfs co przez to rozumiesz? Po co czek z epsilonem?
Neon Warge,
3
@NeonWarge: sqrt () zwraca liczbę zmiennoprzecinkową. ==w większości przypadków jest niewłaściwa dla pływaków . math.isclose()można użyć zamiast tego. W math.isclose()2008 r. Nie było, dlatego przedstawiłem wyraźną nierówność epsilon( abs_tolw math.isclose()mowie).
jfs
Jest to absolutnie poprawne, jednak ta metoda nie jest zbyt dobra nawet z math.isclose (). Gdy współrzędne są liczbami całkowitymi, chciałbyś mieć dokładny test. Moja druga odpowiedź daje na to wzór: stackoverflow.com/a/29301940/40078
Jules
36

Sprawdź, czy iloczyn iloczynu b-ai c-ajest 0: oznacza to, że wszystkie punkty są współliniowe. Jeśli tak, sprawdź, czy cwspółrzędne są między a'a b'. Użyj współrzędnych x lub y, o ile ai bsą oddzielne na tej osi (lub są takie same na obu).

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

Ta odpowiedź była niegdyś bałaganem trzech aktualizacji. Cenne informacje od nich: rozdział Briana Hayesa w Beautiful Code obejmuje przestrzeń projektową dla funkcji testu kolinearności - przydatne tło. Odpowiedź Vincenta pomogła ulepszyć ten. I to Hayes zasugerował przetestowanie tylko jednej ze współrzędnych x lub y; pierwotnie kod miał andmiejsce if a.x != b.x else.

Darius Bacon
źródło
Ponieważ sprawdzanie zakresu jest szybsze, lepiej byłoby najpierw sprawdzić zakres, a następnie sprawdzić współliniowość, jeśli w obwiedni.
Grant M
1
Funkcja is_on (a, b, c) jest nieprawidłowa w przypadku, gdy a == b! = C. W takim przypadku zwróci prawdę, nawet jeśli c nie przecina odcinka linii od a do b.
Mikko Virkkilä
@SuperFlux, właśnie próbowałem to uruchomić i otrzymałem False.
Darius Bacon
2
Myślę, że ta odpowiedź jest wyraźnie lepsza od obecnie akceptowanej odpowiedzi.
Rick wspiera Monikę
1
współliniowe (a, b, c) testuje liczby zmiennoprzecinkowe na zasadzie równości. Czy nie powinien używać epsilon / tolerancji?
jwezorek
7

Oto inne podejście:

  • Załóżmy, że te dwa punkty to A (x1, y1) i B (x2, y2)
  • Równanie prostej przechodzącej przez te punkty to (x-x1) / (y-y1) = (x2-x1) / (y2-y1) .. (po prostu zrównując nachylenia)

Punkt C (x3, y3) będzie leżeć między A i B, jeśli:

  • x3, y3 spełnia powyższe równanie.
  • x3 leży między x1 a x2 i y3 leży między y1 i y2 (trywialne sprawdzenie)
Sridhar Iyer
źródło
Nie uwzględnia to błędów zaokrągleń (niedokładności współrzędnych).
bart
Myślę, że to dobry pomysł, ale mało szczegółów (jak sprawdzić to równanie w praktyce?) I trochę błędny: ostatnim y3 powinno być y2.
Darius Bacon
@Darius: naprawiono tę literówkę
Harley Holcombe
7

Długość segmentu nie jest ważna, dlatego użycie pierwiastka kwadratowego nie jest wymagane i należy go unikać, ponieważ możemy stracić pewną precyzję.

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)

Losowy przykład użycia:

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)
vincent
źródło
1
Jeśli cx lub cy są pływające, to pierwszy ==in is_between()może zawieść (przy okazji jest to produkt krzyżowy w przebraniu).
jfs
dodaj do is_between():a, b = self.a, self.b
jfs
Wygląda na to, że zwróci prawdę, jeśli wszystkie trzy punkty są takie same (co jest w porządku, imho), ale fałszywe, jeśli dokładnie dwa punkty są takie same - dość niespójny sposób definiowania między. W mojej odpowiedzi zamieściłem alternatywę.
Darius Bacon
naprawiłem to inną sztuczką cmp, ale ten kod zaczyna śmierdzieć ;-)
vincent
5

Oto inny sposób, z kodem podanym w C ++. Biorąc pod uwagę dwa punkty, l1 i l2, trywialne jest wyrażenie odcinka linii między nimi jako

l1 + A(l2 - l1)

gdzie 0 <= A <= 1. Jest to znane jako reprezentacja wektorowa linii, jeśli jesteś zainteresowany czymś więcej niż tylko używaniem go do tego problemu. Możemy podzielić składowe xiy tego, dając:

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

Weź punkt (x, y) i podstaw jego składowe x i y do tych dwóch wyrażeń, aby znaleźć A. Punkt znajduje się na prostej, jeśli rozwiązania dla A w obu wyrażeniach są równe i 0 <= A <= 1. Ponieważ rozwiązanie dla A wymaga dzielenia, istnieją specjalne przypadki, które wymagają obsługi, aby zatrzymać dzielenie przez zero, gdy segment linii jest poziomy lub pionowy. Ostateczne rozwiązanie jest następujące:

// 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;
}
Matthew Henry
źródło
4

Korzystając z bardziej geometrycznego podejścia, oblicz następujące odległości:

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)

i sprawdź, czy ac + bc równa się ab :

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

Dzieje się tak, ponieważ istnieją trzy możliwości:

  • Te 3 punkty tworzą trójkąt => ac + bc> ab
  • Są współliniowe i C znajduje się poza AB segmentu => ac + bc> ab
  • Są współliniowe, a C jest w środku ab segmentu => ac = + bc ab
efotinis
źródło
Jak Jonathan Leffler wspomina w innym komentarzu, wiąże się to z problemami liczbowymi, których inne podejścia, takie jak produkty krzyżowe, unikają. Rozdział, do którego odsyłam w mojej odpowiedzi, wyjaśnia.
Darius Bacon
3

Ok, wiele wzmianek o algebrze liniowej (iloczyn wektorowy wektorów) i to działa w przestrzeni rzeczywistej (tj. Ciągłej lub zmiennoprzecinkowej), ale pytanie konkretnie mówiło, że dwa punkty zostały wyrażone jako liczby całkowite, a zatem iloczyn krzyżowy nie jest poprawny rozwiązanie, chociaż może dać przybliżone rozwiązanie.

Prawidłowym rozwiązaniem jest użycie algorytmu liniowego Bresenham między dwoma punktami i sprawdzenie, czy trzeci punkt jest jednym z punktów na prostej. Jeśli punkty są na tyle odległe, że obliczanie algorytmu jest niewydajne (i musiałby być naprawdę duży, żeby tak było), jestem pewien, że możesz poszperać i znaleźć optymalizacje.

cletus
źródło
Rozwiązuje, jak narysować linię przez dwuwymiarową przestrzeń całkowitą między dwoma dowolnymi punktami i jest matematycznie poprawna. Jeśli trzeci punkt jest jednym z punktów na tej linii, to z definicji znajduje się między tymi dwoma punktami.
cletus
1
Nie, algorytm liniowy Bresenham rozwiązuje sposób tworzenia przybliżenia odcinka linii w dwuwymiarowej przestrzeni liczb całkowitych. Z wiadomości pierwotnego nadawcy nie wynika, że ​​chodziło o rasteryzację.
Cyrille Ka
„Powiedzmy, że masz dwuwymiarową płaszczyznę z 2 punktami (zwanymi a i b) reprezentowanymi przez x INTEGER i ay INTEGER dla każdego punktu”. (podkreślenie dodane przeze mnie).
cletus
1
Myślę, że algorytm liniowy Bresenham daje najbliższe punkty całkowite do linii, których można następnie użyć do narysowania linii. Mogą nie być na linii. Na przykład, jeśli dla (0,0) do (11,13) algorytm poda liczbę pikseli do narysowania, ale nie ma punktów całkowitych poza punktami końcowymi, ponieważ 11 i 13 są względnie pierwsze.
Grant M
Jak rozwiązanie, które jest poprawne dla przestrzeni rzeczywistej (ℝ × ℝ), może nie być poprawne dla przestrzeni całkowitej (ℕ × ℕ), jak,. Czy masz na myśli: „nie jest optymalne dla…” zamiast „nie jest poprawne?
Ideogram
2

Iloczyn skalarny między (ca) i (ba) musi być równy iloczynowi ich długości (oznacza to, że wektory (ca) i (ba) są wyrównane i mają ten sam kierunek). Ponadto długość (ca) musi być mniejsza lub równa długości (ba). Pseudo kod:

# 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
Federico A. Ramponi
źródło
Czy ostatni warunek nie powinien bardziej przypominać: ABS (produkt - lengthca * lengthba) <epsilon?
Jonathan Leffler
Nie powinieneś zamiast tego porównywać kwadratowych długości? Należy unikać pierwiastków kwadratowych. Ponadto, jeśli jest to nieuniknione z powodu przepełnienia, możesz użyć math.hypot zamiast math.sqrt (z odpowiednią zmianą argumentów).
Darius Bacon
Zastanawiam się też nad tym epsilonem. Możesz to wyjaśnić? Oczywiście, jeśli mamy do czynienia z liczbami zmiennoprzecinkowymi, musimy uważać na porównania, ale nie jest dla mnie jasne, dlaczego epsilon sprawia, że ​​to konkretne porównanie jest dokładniejsze.
Darius Bacon
Zgadzam się. Jest kilka dobrych odpowiedzi na to pytanie, a ta jest w porządku. Ale ten kod musi zostać zmieniony, aby nie używał sqrt, a ostatnie porównanie zostało naprawione.
Cyrille Ka
@Jonathan: rzeczywiście, kod jest bardziej znany i elegancki dzięki abs. Dzięki.
Federico A. Ramponi
2

Potrzebowałem tego dla javascript do użycia w kanwie HTML5 do wykrywania, czy kursor użytkownika znajduje się nad lub w pobliżu określonej linii. Więc zmodyfikowałem odpowiedź udzieloną przez Dariusa Bacona na 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
bfcoder
źródło
2

Możesz użyć iloczynu klinowego i punktowego:

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
Jules
źródło
1

Oto, jak zrobiłem to w szkole. Zapomniałem, dlaczego to nie jest dobry pomysł.

EDYTOWAĆ:

@Darius Bacon: cytuje książkę „Piękny kod”, która zawiera wyjaśnienie, dlaczego poniższy kod nie jest dobrym pomysłem.

#!/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 __name__ == '__main__':
    import  doctest
    doctest.testmod()
jfs
źródło
1

Dowolny punkt na odcinku linii ( a , b ) (gdzie a i b są wektorami) można wyrazić jako liniową kombinację dwóch wektorów a i b :

Innymi słowy, jeśli c leży na odcinku linii ( a , b ):

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

Rozwiązując m , otrzymujemy:

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

Tak więc nasz test wygląda następująco (w Pythonie):

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)
Shankster
źródło
1

c # Z http://www.faqs.org/faqs/graphics/algorithms-faq/ -> Temat 1.02: Jak znaleźć odległość od punktu do linii?

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;
        }
edid
źródło
Właściwy sposób na uniknięcie problemów z precyzją w większości innych podejść. Również znacznie bardziej wydajny niż większość innych osób.
Robin Davies,
1

Oto kod Java, który działał dla mnie:

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;
}
Golwig
źródło
1
dotProduct może tylko powiedzieć o wyrównaniu. Twój kod jest niekompletny !!! Z a (0,0), b (4,0), c (1,1) masz produkt dot = (1-0) * (1-4) + (1-0) * (1-0) = - 3 + 1 = -3
user43968
0

co powiesz na upewnienie się, że nachylenie jest takie samo, a punkt znajduje się między innymi?

dane punkty (x1, y1) i (x2, y2) (przy x2> x1) i punkt kandydujący (a, b)

if (b-y1) / (a-x1) = (y2-y2) / (x2-x1) I x1 <a <x2

Wtedy (a, b) musi znajdować się w linii między (x1, y1) a (x2, y2)

Charles Bretana
źródło
A co z szalonymi problemami z precyzją zmiennoprzecinkową, gdy niektóre współrzędne są bliskie lub identyczne?
Robin Davies,
Komputery nie radzą sobie dobrze z liczbami zmiennoprzecinkowymi. W komputerze nie ma czegoś takiego jak nieskończenie płynnie regulowane wartości. Więc jeśli używasz punktów zmiennoprzecinkowych, musisz zdefiniować i użyć małej wartości epsilon jako wyznacznika, a dowolne dwa punkty bliższe niż ten epsilon powinny być uważane za ten sam punkt. Określ punkt znajdujący się na tej samej linii i w tej samej odległości od punktów końcowych. Jeśli punkt kandydujący znajduje się w Twoim epsilonie tego obliczonego punktu, nazwij go identycznym.
Charles Bretana
Chodziło mi o to, że ta odpowiedź jest bezużyteczna z powodu problemów z precyzją, gdy faktycznie implementujesz ją w kodzie. Więc nikt nie powinien go używać. Wspaniała odpowiedź na teście z matematyki. Ale zawodowa porażka na kursie informatycznym. Przyszedłem tutaj, szukając metody iloczynu skalarnego (co jest poprawne); więc pomyślałem, że poświęcę kilka chwil na oznaczenie wielu niepoprawnych odpowiedzi w tym wątku, aby inne osoby, które znają prawidłowe rozwiązanie, nie uległy pokusie ich użycia.
Robin Davies
Masz rację co do problemów, które pojawiają się z powodu niemożności reprezentacji przez komputery wszystkich możliwych liczb rzeczywistych w wierszu. Mylisz się, że każde rozwiązanie (w tym metoda iloczynu punktowego) może być odporne na te problemy. Każde rozwiązanie może cierpieć z powodu tych problemów. O ile nie uwzględnisz dopuszczalnego epsilon, punkt, który znajduje się dokładnie na linii (ale którego współrzędne nie są reprezentowane w binarnej reprezentacji zmiennoprzecinkowej ieee), również nie przejdzie testu iloczynu skalarnego, ponieważ komputer będzie niedokładnie przedstawiał współrzędne punktu o jakąś kwotę.
Charles Bretana
0

Odpowiedź w C # przy użyciu klasy 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;
}

Zwróć na to uwagę

s * s

jest iloczynem skalarnym wektora segmentu przez przeciążenie operatora w C #

Kluczem jest wykorzystanie rzutu punktu na nieskończoną linię i zaobserwowanie, że skalarna wielkość rzutu mówi nam w trywialny sposób, czy rzut znajduje się na odcinku, czy nie. Możemy dostosować granice wielkości skalarnej, aby użyć rozmytej tolerancji.

Jeśli rzut jest w granicach, po prostu sprawdzamy, czy odległość od punktu do rzutu mieści się w granicach.

Zaletą podejścia obejmującego wiele produktów jest to, że tolerancja ma znaczącą wartość.

bradgonesurfing
źródło
0

Oto moje rozwiązanie z C # w 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;
}
kaleidos
źródło
Wygląda na to, że ten kod działałby tylko z pionowymi i poziomymi segmentami linii. Co jeśli ptLineStart to (0,0), ptLineEnd to (2,2), a ptPoint to (1, 1)?
vac,
0

Wersja C # odpowiedzi Julesa:

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;
}
Tone Škoda
źródło
0

Możesz to zrobić, rozwiązując równanie linii dla tego odcinka linii ze współrzędnymi punktu, dzięki którym będziesz wiedzieć, czy ten punkt znajduje się na prostej, a następnie sprawdzając granice segmentu, aby wiedzieć, czy znajduje się on wewnątrz, czy na zewnątrz. Możesz zastosować jakiś próg, ponieważ cóż, jest on gdzieś w przestrzeni, najprawdopodobniej określony przez wartość zmiennoprzecinkową i nie możesz trafić dokładnie w ten. Przykład w php

function getLineDefinition($p1=array(0,0), $p2=array(0,0)){
    
    $k = ($p1[1]-$p2[1])/($p1[0]-$p2[0]);
    $q = $p1[1]-$k*$p1[0];
    
    return array($k, $q);
    
}

function isPointOnLineSegment($line=array(array(0,0),array(0,0)), $pt=array(0,0)){
    
    // GET THE LINE DEFINITION y = k.x + q AS array(k, q) 
    $def = getLineDefinition($line[0], $line[1]);
    
    // use the line definition to find y for the x of your point
    $y = $def[0]*$pt[0]+$def[1];

    $yMin = min($line[0][1], $line[1][1]);
    $yMax = max($line[0][1], $line[1][1]);

    // exclude y values that are outside this segments bounds
    if($y>$yMax || $y<$yMin) return false;
    
    // calculate the difference of your points y value from the reference value calculated from lines definition 
    // in ideal cases this would equal 0 but we are dealing with floating point values so we need some threshold value not to lose results
    // this is up to you to fine tune
    $diff = abs($pt[1]-$y);
    
    $thr = 0.000001;
    
    return $diff<=$thr;
    
}
Sagan
źródło