Làm thế nào bạn có thể xác định một điểm nằm giữa hai điểm khác trên một đoạn thẳng?


93

Giả sử bạn có một mặt phẳng hai chiều với 2 điểm (được gọi là a và b) trên đó được biểu diễn bằng số nguyên x và số nguyên ay cho mỗi điểm.

Làm thế nào bạn có thể xác định xem một điểm c có nằm trên đoạn thẳng được xác định bởi a và b hay không?

Tôi sử dụng python hầu hết, nhưng các ví dụ bằng bất kỳ ngôn ngữ nào sẽ hữu ích.


4
Tôi thấy rất nhiều thứ về length = sqrt (x) đang diễn ra trong những câu trả lời này; chúng có thể hoạt động, nhưng chúng không nhanh. Cân nhắc sử dụng bình phương độ dài; nếu bạn chỉ so sánh các giá trị độ dài bình phương với nhau, sẽ không làm mất độ chính xác và bạn lưu các lệnh gọi chậm vào sqrt ().
ojrac

1
Điểm c có được biểu diễn bằng 2 số nguyên không? Nếu vậy thì bạn có muốn biết c nằm chính xác trên đường thẳng thực giữa a và b hay nằm trên xấp xỉ raster của đường thẳng giữa a và b? Đây là một sự làm rõ quan trọng.
RobS

Một câu hỏi tương tự đã được hỏi ở đây: stackoverflow.com/q/31346862/1914034 với một giải pháp khi một bộ đệm khoảng cách từ dòng là cần thiết
Dưới Radar


1
Cảnh báo cho độc giả trong tương lai: Một số câu trả lời không chính xác hoặc không đầy đủ. Một số trường hợp cạnh thường không hoạt động là các đường ngang và dọc.
Stefnotch

Câu trả lời:


127

Kiểm tra xem tích chéo của (ba) và (ca) có bằng 0 hay không, như Darius Bacon nói, cho bạn biết liệu các điểm a, b và c có thẳng hàng hay không.

Tuy nhiên, muốn biết c có nằm giữa a và b hay không, bạn cũng phải kiểm tra tích chấm của (ba) và (ca) là dươngnhỏ hơn bình phương khoảng cách giữa a và b.

Trong mã giả không được tối ưu hóa:

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

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)là đủ, phải không?
jfs

9
Giá trị tuyệt đối của sản phẩm chéo gấp đôi diện tích của tam giác được tạo bởi ba điểm (với dấu hiệu cho biết cạnh của điểm thứ ba) vì vậy IMHO bạn nên sử dụng một epsilon tỷ lệ với khoảng cách giữa hai điểm cuối.
bart

2
Bạn có thể cho chúng tôi biết tại sao nó không hoạt động với số nguyên không? Tôi không thấy vấn đề, miễn là kiểm tra epsilon được thay thế bằng "! = 0".
Cyrille Ka

2
Vâng, dấu ngoặc đơn chỉ là lỗi đánh máy. Đã 4 năm trôi qua trước khi có người nói điều gì đó. :)
Cyrille Ka

4
Bạn nên đổi tên a, b, c để rõ ràng hơn đâu là điểm cuối của đoạn và đâu là điểm truy vấn.
Craig Gidney, 19:13

48

Đây là cách tôi sẽ làm điều đó:

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)

7
Đây là một giải pháp thanh lịch.
Paul D. Eden

6
Vấn đề duy nhất của điều này là tính ổn định của số - lấy sự khác biệt của các con số, v.v. có thể làm mất độ chính xác.
Jonathan Leffler

26
-epsilon < (distance(a, c) + distance(c, b) - distance(a, b)) < epsilon
jfs

1
@jfs ý bạn là gì? Kiểm tra với epsilon để làm gì?
Neon Warge

3
@NeonWarge: sqrt () trả về một float. ==là một điều sai đối với phao trong hầu hết các trường hợp . math.isclose()có thể được sử dụng thay thế. Không có math.isclose()trong năm 2008 và do đó tôi đã cung cấp sự bất bình đẳng rõ ràng với epsilon( abs_toltrong math.isclose()nói).
jfs

35

Kiểm tra xem tích chéo của b-ac-a0: nghĩa là tất cả các điểm đều thẳng hàng. Nếu có, hãy kiểm tra xem ctọa độ của có nằm giữa a's và b' không. Sử dụng tọa độ x hoặc y, miễn là abriêng biệt trên trục đó (hoặc chúng giống nhau trên cả hai).

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

Câu trả lời này từng là một mớ hỗn độn của ba bản cập nhật. Thông tin đáng giá từ họ: Chương của Brian Hayes trong Beautiful Code bao gồm không gian thiết kế cho một chức năng kiểm tra độ tương đồng - nền hữu ích. Câu trả lời của Vincent đã giúp cải thiện câu trả lời này. Và chính Hayes là người đề nghị chỉ thử nghiệm một trong các tọa độ x hoặc y; ban đầu mã đã andthay thế if a.x != b.x else.


Vì kiểm tra phạm vi nhanh hơn sẽ tốt hơn nếu kiểm tra phạm vi trước sau đó kiểm tra tính thẳng hàng nếu trong hộp giới hạn.
Grant M

1
Hàm is_on (a, b, c) sai đối với trường hợp a == b! = C. Trong trường hợp này, nó sẽ trả về true, mặc dù c không giao nhau giữa một đoạn thẳng từ a đến b.
Mikko Virkkilä

@SuperFlux, tôi vừa thử chạy nó và nhận được False.
Darius Bacon

2
Tôi nghĩ câu trả lời này rõ ràng là cao hơn câu trả lời được chấp nhận hiện tại.
Rick ủng hộ Monica.

1
collinear (a, b, c) đang kiểm tra số dấu phẩy động bằng đẳng thức. Nó không nên sử dụng epsilon / dung sai?
jwezorek

7

Đây là một cách tiếp cận khác:

  • Giả sử hai điểm là A (x1, y1) và B (x2, y2)
  • Phương trình của đường thẳng đi qua các điểm đó là (x-x1) / (y-y1) = (x2-x1) / (y2-y1) .. (chỉ cần cân bằng hệ số góc)

Điểm C (x3, y3) sẽ nằm giữa A và B nếu:

  • x3, y3 thỏa mãn phương trình trên.
  • x3 nằm giữa x1 & x2 và y3 nằm giữa y1 & y2 (kiểm tra tầm thường)

Điều đó không tính đến lỗi làm tròn (không chính xác của tọa độ).
bart

Tôi nghĩ đây là ý tưởng đúng, nhưng lại thiếu chi tiết (làm thế nào để chúng ta kiểm tra phương trình đó trong thực tế?) Và một chút lỗi: y3 cuối cùng phải là y2.
Darius Bacon,

@Darius: đã sửa lỗi đánh máy đó
Harley Holcombe

7

Độ dài của đoạn không quan trọng, do đó không cần sử dụng căn bậc hai và nên tránh vì chúng ta có thể mất độ chính xác.

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)

Một số ví dụ ngẫu nhiên về cách sử dụng:

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)

1
Nếu cx hay cy là phao sau đó là người đầu tiên ==trong is_between()thể thất bại (btw nó là một tích vectơ trong ngụy trang).
jfs

thêm vào is_between():a, b = self.a, self.b
jfs

Có vẻ như điều đó sẽ trả về true nếu cả ba điểm đều giống nhau (tất cả đều đúng, imho) nhưng false nếu chính xác hai trong số các điểm giống nhau - một cách khá mâu thuẫn để xác định giữa hai điểm. Tôi đã đăng một giải pháp thay thế trong câu trả lời của mình.
Darius Bacon vào

đã khắc phục điều đó bằng một thủ thuật cmp khác, nhưng mã này bắt đầu có mùi ;-)
vincent

5

Đây là một cách khác để thực hiện nó, với mã được cung cấp bằng C ++. Cho hai điểm, l1 và l2, việc thể hiện đoạn thẳng giữa chúng là

l1 + A(l2 - l1)

trong đó 0 <= A <= 1. Đây được gọi là biểu diễn vectơ của một đường nếu bạn quan tâm hơn nữa ngoài việc sử dụng nó cho vấn đề này. Chúng ta có thể tách các thành phần x và y của nó, cho:

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

Lấy một điểm (x, y) và thay các thành phần x và y của nó vào hai biểu thức này để giải được A. Điểm nằm trên đường thẳng nếu nghiệm của A trong cả hai biểu thức bằng nhau và 0 <= A <= 1. Vì giải cho A yêu cầu chia, có những trường hợp đặc biệt cần xử lý để dừng phép chia cho 0 khi đoạn thẳng nằm ngang hoặc dọc. Giải pháp cuối cùng như sau:

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

Sử dụng phương pháp tiếp cận hình học hơn, hãy tính các khoảng cách sau:

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)

và kiểm tra xem ac + bc có bằng ab không :

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

Đó là bởi vì có ba khả năng:

  • 3 điểm tạo thành tam giác => ac + bc> ab
  • Chúng thẳng hàng và c nằm ngoài đoạn ab => ac + bc> ab
  • Chúng thẳng hàng và c nằm trong đoạn ab => ac + bc = ab

Như Jonathan Leffler đã đề cập trong một nhận xét khác, điều này có các vấn đề về số mà các cách tiếp cận khác như sản phẩm chéo đều tránh được. Chương tôi liên kết đến trong câu trả lời của tôi giải thích.
Darius Bacon vào

3

Ok, rất nhiều đề cập đến đại số tuyến tính (tích chéo của vectơ) và điều này hoạt động trong một không gian thực (tức là liên tục hoặc dấu phẩy động) nhưng câu hỏi đã nêu cụ thể rằng hai điểm được biểu thị dưới dạng số nguyên và do đó tích chéo không đúng giải pháp mặc dù nó có thể đưa ra một giải pháp gần đúng.

Giải pháp chính xác là sử dụng Thuật toán đường thẳng của Bresenham giữa hai điểm và xem liệu điểm thứ ba có phải là một trong các điểm trên đường thẳng hay không. Nếu các điểm đủ xa khiến việc tính toán thuật toán không hiệu quả (và nó phải thực sự lớn đối với trường hợp đó), tôi chắc chắn rằng bạn có thể tìm hiểu kỹ và tìm ra cách tối ưu.


Nó giải quyết cách vẽ một đường thẳng qua không gian số nguyên hai chiều giữa hai điểm tùy ý và chính xác về mặt toán học của nó. Nếu điểm thứ ba là một trong những điểm trên đường thẳng đó thì theo định nghĩa, nó nằm giữa hai điểm đó.
cletus

1
Không, Thuật toán Đường thẳng của Bresenham giải quyết cách tạo một đoạn thẳng gần đúng trong không gian số nguyên hai chiều. Tôi không thấy từ thông điệp của người đăng ban đầu rằng đó là một câu hỏi về sự phân loại.
Cyrille Ka

"Giả sử bạn có một mặt phẳng hai chiều với 2 điểm (được gọi là a và b) trên đó được biểu diễn bằng x INTEGER và ay INTEGER cho mỗi điểm." (nhấn mạnh do tôi thêm vào).
cletus

1
Tôi nghĩ Thuật toán Đường thẳng của Bresenham cung cấp các điểm số nguyên trong tủ quần áo cho một đường, sau đó có thể được sử dụng để vẽ đường. Họ có thể không ở trên đường dây. Ví dụ: nếu từ (0,0) đến (11,13), thuật toán sẽ cung cấp một số pixel để vẽ nhưng không có điểm số nguyên nào ngoại trừ điểm cuối, vì 11 và 13 là cùng chuẩn.
Grant M

Làm thế nào một lời giải đúng với không gian thực (ℝ × ℝ) lại không đúng với không gian nguyên (ℕ × ℕ), vì ℕ∈ℝ. Hay ý bạn là: "không phải là tối ưu cho…" thay vì 'là không đúng?
Ideogram

2

Tích vô hướng giữa (ca) và (ba) phải bằng tích độ dài của chúng (điều này có nghĩa là vectơ (ca) và (ba) thẳng hàng và cùng hướng). Hơn nữa, độ dài của (ca) phải nhỏ hơn hoặc bằng độ dài của (ba). Mã giả:

# 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

Điều kiện cuối cùng không nên giống như: ABS (product - lengthca * lengthba) <epsilon?
Jonathan Leffler

Thay vào đó, bạn không nên so sánh độ dài bình phương sao? Căn bậc hai cần phải tránh. Ngoài ra, nếu điều này là không thể tránh khỏi do tràn, bạn có thể sử dụng math.hypot thay vì math.sqrt (với sự thay đổi đối số thích hợp).
Darius Bacon vào

Tôi cũng thắc mắc về epsilon đó. Bạn có thể giải thích nó được không? Tất nhiên, nếu chúng ta phải đối phó với float, chúng ta phải cẩn thận khi so sánh, nhưng tôi không rõ tại sao một epsilon lại làm cho phép so sánh cụ thể này chính xác hơn.
Darius Bacon,

Tôi đồng tình. Có một số câu trả lời tốt cho câu hỏi này, và câu này là tốt. Nhưng mã này cần được sửa đổi để không sử dụng sqrt và so sánh cuối cùng đã được sửa.
Cyrille Ka

@Jonathan: thực sự mã này quen thuộc và thanh lịch hơn khi sử dụng abs. Cảm ơn.
Federico A. Ramponi,

2

Tôi cần nó cho javascript để sử dụng trong canvas html5 để phát hiện xem con trỏ của người dùng có ở trên hoặc gần một dòng nhất định hay không. Vì vậy, tôi đã sửa đổi câu trả lời do Darius Bacon đưa ra thành 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

Bạn có thể sử dụng sản phẩm nêm và chấm:

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

1

Đây là cách tôi đã làm ở trường. Tôi đã quên tại sao nó không phải là một ý kiến ​​hay.

BIÊN TẬP:

@Darius Bacon: trích dẫn một cuốn sách "Beautiful Code" có giải thích tại sao đoạn mã dưới đây không phải là một ý tưởng hay.

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

1

Bất kỳ điểm nào trên đoạn thẳng ( a , b ) (trong đó ab là vectơ) có thể được biểu diễn dưới dạng kết hợp tuyến tính của hai vectơ ab :

Nói cách khác, nếu c nằm trên đoạn thẳng ( a , b ):

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

Giải cho m , ta được:

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

Vì vậy, thử nghiệm của chúng tôi trở thành (bằng 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

c # Từ http://www.faqs.org/faqs/graphics/algorithm-faq/ -> Chủ đề 1.02: Làm cách nào để tìm khoảng cách từ một điểm đến một đoạn thẳng?

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

Cách chính xác để tránh các vấn đề về độ chính xác trong hầu hết các cách tiếp cận khác. Cũng hiệu quả hơn đáng kể mà hầu hết các loại khác chấp nhận được.
Robin Davies

1

Đây là một số mã Java phù hợp với tôi:

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
dotProduct chỉ có thể nói về alignement. Mã của bạn chưa hoàn chỉnh !!! Với a (0,0), b (4,0), c (1,1) bạn có dotproduct = (1-0) * (1-4) + (1-0) * (1-0) = - 3 + 1 = -3
dùng43968

0

làm thế nào về việc chỉ đảm bảo rằng độ dốc là như nhau và điểm nằm giữa những cái khác?

điểm đã cho (x1, y1) và (x2, y2) (với x2> x1) và điểm ứng viên (a, b)

nếu (b-y1) / (a-x1) = (y2-y2) / (x2-x1) Và x1 <a <x2

Khi đó (a, b) phải nằm trên dòng giữa (x1, y1) và (x2, y2)


Làm thế nào về vấn đề độ chính xác dấu phẩy động điên rồ khi một số tọa độ gần hoặc giống hệt nhau?
Robin Davies

Máy tính không làm tốt các dấu chấm động. Trong máy tính không có thứ gọi là các giá trị có thể điều chỉnh liên tục vô hạn. Vì vậy, nếu bạn đang sử dụng Floating points, bạn phải thiết lập định nghĩa và sử dụng một số giá trị epsilon nhỏ làm định thức và hai điểm bất kỳ gần hơn epsilon đó sẽ được coi là cùng một điểm. Xác định điểm IS trên cùng một đường thẳng và cùng khoảng cách với các điểm cuối. Nếu điểm ứng cử viên của bạn nằm trong epsilon của điểm được tính toán đó, thì hãy gọi nó là giống hệt.
Charles Bretana

Quan điểm của tôi là câu trả lời này không sử dụng được vì các vấn đề về độ chính xác khi bạn thực sự triển khai nó trong mã. Vì vậy, không ai nên sử dụng nó. Một câu trả lời đáng yêu trong một bài kiểm tra toán học. Nhưng một thất bại cạnh tranh trong một khóa học khoa học kỹ thuật. Tôi đến đây để tìm kiếm phương pháp dot-product (chính xác); vì vậy tôi nghĩ rằng tôi sẽ dành một chút thời gian để gắn cờ nhiều câu trả lời trong chuỗi này là không chính xác để những người khác quen thuộc với giải pháp đúng sẽ không bị cám dỗ sử dụng chúng.
Robin Davies

Bạn nói đúng về các vấn đề phát sinh do máy tính không thể biểu diễn mọi số thực có thể có trên một dòng. Bạn không chính xác rằng bất kỳ giải pháp nào (bao gồm cả phương pháp sản phẩm chấm) đều có thể miễn nhiễm với những vấn đề này. Bất kỳ giải pháp nào cũng có thể bị những vấn đề này. Trừ khi bạn thực hiện một số cho phép đối với epsilon có thể chấp nhận được, một điểm nằm chính xác trên đường thẳng (nhưng có tọa độ không thể biểu diễn trong biểu diễn nhị phân dấu phẩy động tức là), cũng sẽ thất bại trong bài kiểm tra sản phẩm chấm, vì máy tính sẽ biểu diễn tọa độ của điểm không chính xác bằng một số lượng.
Charles Bretana

0

Một câu trả lời trong C # bằng cách sử dụng lớp 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;
}

Lưu ý rằng

s * s

là tích số chấm của vectơ phân đoạn thông qua nạp chồng toán tử trong C #

Điều quan trọng là tận dụng lợi thế của hình chiếu của điểm lên đường thẳng vô hạn và quan sát rằng đại lượng vô hướng của phép chiếu cho chúng ta biết một cách nhỏ bé liệu phép chiếu có nằm trên đoạn hay không. Chúng ta có thể điều chỉnh các giới hạn của đại lượng vô hướng để sử dụng dung sai mờ.

Nếu hình chiếu nằm trong giới hạn, chúng tôi chỉ kiểm tra xem khoảng cách từ điểm đến hình chiếu có nằm trong giới hạn hay không.

Lợi ích của phương pháp tiếp cận sản phẩm chéo là dung sai có giá trị có ý nghĩa.


0

Đây là giải pháp của tôi với C # trong 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;
}

Có vẻ như mã này sẽ chỉ hoạt động với các phân đoạn đường thẳng đứng và ngang. Điều gì sẽ xảy ra nếu ptLineStart là (0,0), ptLineEnd là (2,2) và ptPoint là (1, 1)?
vac

0

Phiên bản C # của câu trả lời của 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

Bạn có thể làm điều đó bằng cách giải phương trình đường thẳng cho đoạn thẳng đó với tọa độ điểm, bạn sẽ biết liệu điểm đó có nằm trên đoạn thẳng hay không và sau đó kiểm tra các giới hạn của đoạn để biết nó nằm trong hay ngoài của nó. Bạn có thể áp dụng một số ngưỡng vì nó nằm ở đâu đó trong không gian rất có thể được xác định bởi giá trị dấu phẩy động và bạn không được chạm chính xác. Ví dụ trong 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;
    
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.