คุณจะระบุได้อย่างไรว่าจุดหนึ่งอยู่ระหว่างจุดอื่นอีกสองจุดในส่วนของเส้นตรง


97

สมมติว่าคุณมีระนาบสองมิติที่มีจุด 2 จุด (เรียกว่า a และ b) ซึ่งแสดงด้วยจำนวนเต็ม x และจำนวนเต็ม ay สำหรับแต่ละจุด

คุณจะทราบได้อย่างไรว่าจุดอื่น c อยู่บนส่วนของเส้นตรงที่กำหนดโดย a และ b?

ฉันใช้ python มากที่สุด แต่ตัวอย่างในภาษาใด ๆ ก็จะเป็นประโยชน์


4
ฉันเห็นความยาว = sqrt (x) จำนวนมากเกิดขึ้นในคำตอบเหล่านี้ มันอาจจะใช้ได้ แต่ก็ไม่เร็ว พิจารณาใช้ความยาวกำลังสอง หากคุณแค่เปรียบเทียบค่าความยาวกำลังสองซึ่งกันและกันจะไม่มีการสูญเสียความแม่นยำและคุณบันทึกการโทรช้าลงใน sqrt ()
ojrac

1
จุด c แสดงด้วยจำนวนเต็ม 2 จำนวนด้วยหรือไม่? ถ้าเป็นเช่นนั้นคุณต้องการทราบว่า c ตรงตามเส้นตรงจริงระหว่าง a และ b หรืออยู่บนการประมาณแบบแรสเตอร์ของเส้นตรงระหว่าง a และ b หรือไม่? นี่คือคำชี้แจงที่สำคัญ
RobS

มีการถามคำถามที่คล้ายกันที่นี่: stackoverflow.com/q/31346862/1914034พร้อมวิธีแก้ปัญหาเมื่อต้องการระยะทางบัฟเฟอร์จากเส้น
ด้านล่างเรดาร์


1
คำเตือนสำหรับผู้อ่านในอนาคต: คำตอบจำนวนพอสมควรไม่ถูกต้องหรือไม่สมบูรณ์ ขอบบางส่วนที่มักใช้ไม่ได้คือเส้นแนวนอนและแนวตั้ง
Stefnotch

คำตอบ:


131

ตรวจสอบว่าผลคูณไขว้ของ (ba) และ (ca) เป็น 0 ตามที่ Darius Bacon บอกหรือไม่บอกคุณว่าจุด a, b และ c อยู่ในแนวเดียวกันหรือไม่

แต่อย่างที่คุณต้องการทราบว่า c อยู่ระหว่าง a และ b หรือไม่คุณต้องตรวจสอบด้วยว่าผลคูณของ (ba) และ (ca) เป็นบวกและน้อยกว่ากำลังสองของระยะห่างระหว่าง a และ b

ในรหัสเทียมที่ไม่ได้รับการปรับให้เหมาะสม:

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)เพียงพอแล้วไม่ใช่หรือ?
jfs

9
ค่าสัมบูรณ์ของผลิตภัณฑ์ไขว้เป็นสองเท่าของพื้นที่สามเหลี่ยมที่เกิดจากจุดสามจุด (โดยมีเครื่องหมายแสดงด้านข้างเป็นจุดที่สาม) ดังนั้น IMHO คุณควรใช้ epsilon ที่เป็นสัดส่วนกับระยะห่างระหว่างจุดสิ้นสุดทั้งสอง
บาร์ต

2
คุณช่วยบอกเราได้ไหมว่าทำไมมันไม่ทำงานกับจำนวนเต็ม? ฉันไม่เห็นปัญหาหากการตรวจสอบ epsilon ถูกแทนที่ด้วย "! = 0"
Cyrille Ka

2
ใช่วงเล็บเสริมเป็นเพียงการพิมพ์ผิด 4 ปีผ่านไปก่อนที่ใครบางคนจะพูดอะไรบางอย่าง :)
Cyrille Ka

4
คุณควรเปลี่ยนชื่อ a, b, c เพื่อให้ชัดเจนขึ้นว่าจุดสิ้นสุดของเซ็กเมนต์ใดและจุดใดคือจุดค้นหา
Craig Gidney

53

นี่คือวิธีที่ฉันจะทำ:

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)

8
ปัญหาเดียวในเรื่องนี้คือความเสถียรของตัวเลข - การรับความแตกต่างของตัวเลขไปเรื่อย ๆ มีแนวโน้มที่จะสูญเสียความแม่นยำ
Jonathan Leffler

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

1
@jfs คุณหมายถึงอะไร? เช็คกับ epsilon เพื่ออะไร?
Neon Warge

3
@NeonWarge: sqrt () คืนค่าลอย เป็นสิ่งที่ไม่ถูกต้องสำหรับลอยในกรณีส่วนใหญ่== math.isclose()สามารถใช้แทนได้ ไม่มีmath.isclose()ในปี 2008 ดังนั้นฉันจึงให้ความไม่เท่าเทียมกันอย่างชัดเจนกับepsilon( abs_tolในการmath.isclose()พูด)
jfs

ถูกต้องจริงๆอย่างไรก็ตามวิธีนี้ไม่ค่อยดีนักแม้จะใช้ math.isclose () เมื่อพิกัดเป็นจำนวนเต็มคุณต้องการทดสอบที่แน่นอน คำตอบอื่นของฉันให้สูตร: stackoverflow.com/a/29301940/40078
Jules

36

ตรวจสอบว่าผลคูณไขว้ของb-aและc-aเป็น0: นั่นหมายความว่าจุดทั้งหมดเป็นแบบ collinear หากเป็นเช่นนั้นให้ตรวจสอบว่าcพิกัดอยู่ระหว่างa"กับb" หรือไม่ ใช้พิกัด x หรือ y ตราบใดที่aและbแยกกันบนแกนนั้น (หรือเหมือนกันทั้งสองอย่าง)

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

คำตอบนี้เคยเป็นปัญหาของการอัปเดตสามรายการ ข้อมูลที่คุ้มค่าจากพวกเขา: บทของ Brian Hayes ในBeautiful Codeครอบคลุมพื้นที่การออกแบบสำหรับฟังก์ชันการทดสอบ collinearity ซึ่งเป็นพื้นหลังที่มีประโยชน์ คำตอบของ Vincentช่วยปรับปรุงข้อนี้ และเฮย์สเป็นคนแนะนำให้ทดสอบพิกัด x หรือ y เพียงตัวเดียว เดิมรหัสมีandแทนที่if a.x != b.x else.


เนื่องจากการตรวจสอบช่วงเร็วขึ้นจะเป็นการดีกว่าที่จะตรวจสอบช่วงก่อนจากนั้นตรวจสอบ collinear หากอยู่ในกรอบ
Grant M

1
ฟังก์ชัน is_on (a, b, c) ผิดสำหรับกรณีที่ a == b! = c ในกรณีนี้มันจะกลับมาเป็นจริงแม้ว่า c จะไม่ตัดส่วนของเส้นตรงจาก a ถึง b
Mikko Virkkilä

@SuperFlux ฉันเพิ่งลองใช้งานและได้รับ False
Darius Bacon

2
ฉันคิดว่าคำตอบนี้ค่อนข้างชัดเจนกว่าคำตอบที่ยอมรับในปัจจุบัน
Rick สนับสนุน Monica

1
collinear (a, b, c) กำลังทดสอบตัวเลขทศนิยมโดยความเท่าเทียมกัน ไม่ควรใช้ epsilon / tolerance?
jwezorek

7

นี่คือแนวทางอื่น:

  • สมมติว่าจุดสองจุดคือ A (x1, y1) และ B (x2, y2)
  • สมการของเส้นที่ผ่านจุดเหล่านั้นคือ (x-x1) / (y-y1) = (x2-x1) / (y2-y1) .. (เพียงแค่สร้างความลาดชัน)

จุด C (x3, y3) จะอยู่ระหว่าง A & B ถ้า:

  • x3, y3 เป็นไปตามสมการข้างต้น
  • x3 อยู่ระหว่าง x1 & x2 และ y3 อยู่ระหว่าง y1 และ y2 (ตรวจสอบเล็กน้อย)

นั่นไม่ได้คำนึงถึงข้อผิดพลาดในการปัดเศษ (ความไม่แน่นอนของพิกัด)
บาร์ต

ฉันคิดว่านี่เป็นความคิดที่ถูกต้อง แต่มีรายละเอียดสั้น ๆ (เราจะตรวจสอบสมการนั้นในทางปฏิบัติได้อย่างไร) และข้อผิดพลาดเล็กน้อย: y3 สุดท้ายควรเป็น y2
Darius Bacon

@Darius: แก้ไขที่พิมพ์ผิด
Harley Holcombe

7

ความยาวของส่วนนั้นไม่สำคัญจึงไม่จำเป็นต้องใช้รากที่สองและควรหลีกเลี่ยงเนื่องจากเราอาจสูญเสียความแม่นยำไปได้

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)

ตัวอย่างการใช้งานแบบสุ่ม:

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
หาก cx หรือ cy ลอยแล้วครั้งแรก==ในis_between()อาจล้มเหลว (BTW มันเป็นผลคูณไขว้ในการปลอมตัว)
jfs

เพิ่มไปที่is_between():a, b = self.a, self.b
jfs

ดูเหมือนว่าจะกลับมาเป็นจริงหากทั้งสามจุดเหมือนกัน (ซึ่งก็ถูกต้องทั้งหมด imho) แต่เป็นเท็จถ้าจุดสองจุดเหมือนกัน - วิธีที่ไม่สอดคล้องกันในการกำหนดระหว่างความเป็นจริง ฉันโพสต์ทางเลือกในคำตอบของฉัน
Darius Bacon

แก้ไขโดยเคล็ดลับ cmp อื่น แต่รหัสนี้เริ่มมีกลิ่น ;-)
vincent

5

นี่เป็นวิธีที่แตกต่างออกไปโดยใช้รหัสที่กำหนดใน C ++ ให้สองจุด l1 และ l2 เป็นเรื่องเล็กน้อยที่จะแสดงส่วนของเส้นตรงระหว่างทั้งสองเป็น

l1 + A(l2 - l1)

โดยที่ 0 <= A <= 1 สิ่งนี้เรียกว่าการแทนค่าเวกเตอร์ของเส้นหากคุณสนใจอะไรที่นอกเหนือไปจากการใช้มันสำหรับปัญหานี้ เราสามารถแยกส่วนประกอบ x และ y ของสิ่งนี้ได้โดยให้:

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

หาจุด (x, y) และแทนที่ส่วนประกอบ x และ y ในสองนิพจน์นี้เพื่อแก้ปัญหาสำหรับ A จุดอยู่บนบรรทัดถ้าคำตอบสำหรับ A ในนิพจน์ทั้งสองเท่ากันและ 0 <= A <= 1 เนื่องจาก การแก้สำหรับ A ต้องการการหารมีกรณีพิเศษที่ต้องจัดการเพื่อหยุดการหารด้วยศูนย์เมื่อส่วนของเส้นตรงเป็นแนวนอนหรือแนวตั้ง ทางออกสุดท้ายมีดังนี้:

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

ใช้วิธีทางเรขาคณิตมากขึ้นคำนวณระยะทางต่อไปนี้:

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)

และทดสอบว่าac + bcเท่ากับab หรือไม่ :

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

นั่นเป็นเพราะมีความเป็นไปได้สามประการ:

  • จุด 3 จุดเป็นรูปสามเหลี่ยม => ac + bc> ab
  • พวกมันเป็น collinear และcอยู่นอกส่วนab => ac + bc> ab
  • พวกมันเป็น collinear และcอยู่ในส่วนab => ac + bc = ab

ดังที่ Jonathan Leffler กล่าวไว้ในความคิดเห็นอื่นสิ่งนี้มีปัญหาเชิงตัวเลขที่แนวทางอื่น ๆ เช่นการข้ามผลิตภัณฑ์หลีกเลี่ยง บทที่ฉันเชื่อมโยงในคำตอบของฉันอธิบาย
Darius Bacon

3

ตกลงมีการกล่าวถึงพีชคณิตเชิงเส้นจำนวนมาก (ผลคูณไขว้ของเวกเตอร์) และใช้งานได้ในช่องว่างจริง (เช่นจุดต่อเนื่องหรือจุดลอยตัว) แต่คำถามระบุโดยเฉพาะว่าจุดทั้งสองแสดงเป็นจำนวนเต็มดังนั้นผลคูณข้ามไม่ถูกต้อง วิธีแก้ปัญหาแม้ว่าจะสามารถให้วิธีแก้ปัญหาโดยประมาณได้

วิธีแก้ไขที่ถูกต้องคือการใช้Line Algorithm ของ Bresenhamระหว่างจุดทั้งสองและเพื่อดูว่าจุดที่สามเป็นหนึ่งในจุดบนเส้นหรือไม่ หากคะแนนอยู่ห่างกันมากพอที่จะทำให้การคำนวณอัลกอริทึมไม่มีประสิทธิภาพ (และจะต้องมีขนาดใหญ่มากสำหรับกรณีนี้) ฉันแน่ใจว่าคุณสามารถเจาะลึกและค้นหาการเพิ่มประสิทธิภาพได้


มันแก้วิธีการลากเส้นผ่านช่องว่างจำนวนเต็มสองมิติระหว่างจุดสองจุดโดยพลการและมันถูกต้องทางคณิตศาสตร์ ถ้าจุดที่สามเป็นหนึ่งในจุดบนเส้นนั้นตามนิยามระหว่างสองจุดนั้น
cletus

1
ไม่อัลกอริทึมเส้นของ Bresenham แก้วิธีสร้างการประมาณส่วนของเส้นตรงในช่องว่างจำนวนเต็มสองมิติ ฉันไม่เห็นข้อความของผู้โพสต์ต้นฉบับว่าเป็นคำถามเกี่ยวกับการแรสเตอร์
Cyrille Ka

"สมมติว่าคุณมีระนาบสองมิติที่มีจุด 2 จุด (เรียกว่า a และ b) ซึ่งแทนด้วย x INTEGER และ ay INTEGER สำหรับแต่ละจุด" (เน้นย้ำโดยฉัน)
cletus

1
ฉันคิดว่า Line Algorithm ของ Bresenham ให้คะแนนจำนวนเต็มตู้เป็นเส้นซึ่งสามารถใช้วาดเส้นได้ พวกเขาอาจไม่อยู่ในบรรทัด ตัวอย่างเช่นถ้าสำหรับ (0,0) ถึง (11,13) อัลกอริทึมจะให้พิกเซลจำนวนหนึ่งเพื่อวาด แต่ไม่มีจุดจำนวนเต็มยกเว้นจุดสิ้นสุดเนื่องจาก 11 และ 13 เป็น coprime
มอบให้เมื่อ

วิธีแก้ปัญหาที่ถูกต้องสำหรับพื้นที่จริง (ℝ×ℝ) จะไม่ถูกต้องสำหรับช่องว่างจำนวนเต็ม (ℕ×ℕ) ตามที่ℕ∈ℝได้อย่างไร หรือคุณหมายถึง: "ไม่เหมาะสมสำหรับ ... " แทนที่จะเป็น "ไม่ถูกต้อง?
Ideogram

2

ผลคูณสเกลาร์ระหว่าง (ca) และ (ba) ต้องเท่ากับผลคูณของความยาว (ซึ่งหมายความว่าเวกเตอร์ (ca) และ (ba) อยู่ในแนวเดียวกันและมีทิศทางเดียวกัน) ยิ่งไปกว่านั้นความยาวของ (ca) จะต้องน้อยกว่าหรือเท่ากับ (ba) รหัสเทียม:

# 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

เงื่อนไขสุดท้ายไม่ควรเป็นเช่น: ABS (product - lengthca * lengthba) <epsilon?
Jonathan Leffler

คุณควรเปรียบเทียบความยาวกำลังสองแทนไม่ใช่หรือ? ควรหลีกเลี่ยงรากที่สอง นอกจากนี้หากหลีกเลี่ยงไม่ได้เนื่องจากมีปัญหามากเกินไปคุณสามารถใช้ math.hypot แทน math.sqrt ได้ (ด้วยการเปลี่ยนอาร์กิวเมนต์ที่เหมาะสม)
Darius Bacon

ฉันสงสัยเกี่ยวกับเอปไซลอนนั้นด้วย คุณสามารถอธิบายได้หรือไม่? แน่นอนว่าถ้าเราต้องจัดการกับการลอยเราต้องระมัดระวังเกี่ยวกับการเปรียบเทียบ แต่ก็ไม่ชัดเจนสำหรับฉันว่าทำไม epsilon จึงทำให้การเปรียบเทียบนี้แม่นยำยิ่งขึ้น
Darius Bacon

ฉันเห็นด้วย มีคำตอบที่ดีหลายประการสำหรับคำถามนี้และคำถามนี้ใช้ได้ แต่รหัสนี้จำเป็นต้องได้รับการแก้ไขเพื่อไม่ให้ใช้ sqrt และการเปรียบเทียบล่าสุดคงที่
Cyrille Ka

@ โจนาธาน: รหัสนี้คุ้นเคยและสง่างามกว่าโดยใช้ abs ขอบคุณ.
Federico A.Ramponi

2

ฉันต้องการสิ่งนี้สำหรับ javascript เพื่อใช้ในผืนผ้าใบ html5 เพื่อตรวจสอบว่าเคอร์เซอร์ของผู้ใช้อยู่เหนือหรือใกล้บรรทัดใดบรรทัดหนึ่ง ดังนั้นฉันจึงแก้ไขคำตอบที่ได้รับจาก Darius Bacon เป็น 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

คุณสามารถใช้ผลิตภัณฑ์ลิ่มและดอท:

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

นี่คือวิธีที่ฉันทำที่โรงเรียน ฉันลืมไปแล้วว่าทำไมมันถึงไม่ใช่ความคิดที่ดี

แก้ไข:

@Darius Bacon: อ้างอิงหนังสือ "Beautiful Code"ซึ่งมีคำอธิบายว่าเหตุใดรหัสด้านล่างจึงไม่ใช่ความคิดที่ดี

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

จุดใด ๆ บนส่วนของเส้นตรง ( a , b ) (โดยที่aและbเป็นเวกเตอร์) สามารถแสดงเป็นผลรวมเชิงเส้นของเวกเตอร์สองตัวaและb :

กล่าวอีกนัยหนึ่งถ้าcอยู่บนส่วนของเส้นตรง ( a , b ):

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

แก้ม.เราจะได้รับ:

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

ดังนั้นการทดสอบของเราจึงกลายเป็น (ใน 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 # จากhttp://www.faqs.org/faqs/graphics/algorithms-faq/ -> เรื่อง 1.02: ฉันจะหาระยะทางจากจุดถึงเส้นได้อย่างไร

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

วิธีที่ถูกต้องเพื่อหลีกเลี่ยงปัญหาความแม่นยำในวิธีอื่น ๆ ส่วนใหญ่ นอกจากนี้ยังมีประสิทธิภาพมากกว่าการอนุมัติอื่น ๆ อย่างมีนัยสำคัญ
Robin Davies

1

นี่คือโค้ด Java ที่ใช้ได้กับฉัน:

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 สามารถบอกได้เฉพาะเกี่ยวกับการจัดตำแหน่งเท่านั้น รหัสของคุณไม่สมบูรณ์ !!! ด้วย a (0,0), b (4,0), c (1,1) คุณมี dotproduct = (1-0) * (1-4) + (1-0) * (1-0) = - 3 + 1 = -3
user43968

0

แล้วจะมั่นใจได้อย่างไรว่าความชันเท่ากันและจุดอยู่ระหว่างจุดอื่น ๆ ?

คะแนนที่กำหนด (x1, y1) และ (x2, y2) (พร้อม x2> x1) และจุดผู้สมัคร (a, b)

ถ้า (b-y1) / (a-x1) = (y2-y2) / (x2-x1) และ x1 <a <x2

จากนั้น (a, b) ต้องอยู่ในบรรทัดระหว่าง (x1, y1) และ (x2, y2)


ปัญหาความแม่นยำของจุดลอยตัวที่บ้าคลั่งเมื่อพิกัดบางส่วนอยู่ใกล้หรือเหมือนกัน?
Robin Davies

คอมพิวเตอร์ทำคะแนนลอยได้ไม่ดี ในคอมพิวเตอร์ไม่มีสิ่งที่เรียกว่าค่าที่ปรับได้อย่างต่อเนื่องไม่สิ้นสุด ดังนั้นหากคุณกำลังใช้ Floating point คุณต้องกำหนดและใช้ค่า epsilon เล็ก ๆ เป็นตัวกำหนดและจุดสองจุดใดที่อยู่ใกล้กว่า epsilon นั้นควรถือว่าเป็นจุดเดียวกัน กำหนดจุดที่ IS บนบรรทัดเดียวกันและระยะทางเดียวกันจากจุดสิ้นสุด หากคะแนนผู้สมัครของคุณอยู่ภายใน epsilon ของจุดที่คำนวณนั้นให้เรียกว่าจุดเดียวกัน
Charles Bretana

ประเด็นของฉันคือคำตอบนี้ใช้ไม่ได้เนื่องจากปัญหาด้านความแม่นยำเมื่อคุณใช้งานจริงในโค้ด จึงไม่มีใครควรใช้ คำตอบที่น่ารักสำหรับการทดสอบคณิตศาสตร์ แต่ความล้มเหลวในการแข่งขันในหลักสูตร comp-sci ฉันมาที่นี่เพื่อมองหาวิธีดอทโปรดักส์ (ซึ่งถูกต้อง); ดังนั้นฉันคิดว่าฉันจะใช้เวลาสักครู่ในการตั้งค่าสถานะคำตอบจำนวนมากในชุดข้อความนี้ว่าไม่ถูกต้องดังนั้นคนอื่น ๆ ที่คุ้นเคยกับวิธีแก้ไขปัญหาที่ถูกต้องจะไม่ถูกล่อลวงให้ใช้
Robin Davies

คุณถูกต้องเกี่ยวกับปัญหาที่เกิดขึ้นเนื่องจากคอมพิวเตอร์ไม่สามารถแสดงจำนวนจริงที่เป็นไปได้ทั้งหมดบนบรรทัด คุณไม่ถูกต้องที่โซลูชันใด ๆ (รวมถึงวิธีการของผลิตภัณฑ์ดอท) สามารถป้องกันปัญหาเหล่านี้ได้ วิธีแก้ปัญหาใด ๆ ที่สามารถประสบปัญหาเหล่านี้ได้ เว้นแต่คุณจะให้ค่าเผื่อสำหรับ epsilon ที่ยอมรับได้จุดที่อยู่บนเส้นตรง (แต่พิกัดที่ไม่สามารถแสดงได้ในการแสดงเลขฐานสองทศนิยมของ ieee) จะล้มเหลวในการทดสอบผลิตภัณฑ์ดอทเช่นกันเนื่องจากคอมพิวเตอร์จะแสดงพิกัดของจุดอย่างไม่ถูกต้อง ตามจำนวน
Charles Bretana

0

คำตอบใน C # โดยใช้คลาส 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;
}

โปรดทราบว่า

s * s

เป็นผลิตภัณฑ์ดอทของเวกเตอร์เซกเมนต์ผ่านตัวดำเนินการมากเกินไปใน C #

กุญแจสำคัญคือการใช้ประโยชน์จากการฉายภาพของจุดไปยังเส้นไม่มีที่สิ้นสุดและสังเกตว่าปริมาณสเกลาร์ของเส้นโครงจะบอกเราได้เล็กน้อยว่าการฉายภาพอยู่บนส่วนหรือไม่ เราสามารถปรับขอบเขตของปริมาณสเกลาร์เพื่อใช้ค่าเผื่อฟัซซี่

หากการฉายภาพอยู่ในขอบเขตเราเพียงแค่ทดสอบว่าระยะห่างจากจุดถึงจุดฉายอยู่ในขอบเขตหรือไม่

ประโยชน์ที่ได้รับจากแนวทางผลิตภัณฑ์ข้ามคือความอดทนมีคุณค่าที่มีความหมาย


0

นี่คือวิธีแก้ปัญหาของฉันด้วย C # ใน 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;
}

ดูเหมือนว่าโค้ดนี้จะใช้ได้กับส่วนของเส้นแนวตั้งและแนวนอน จะเกิดอะไรขึ้นถ้า ptLineStart คือ (0,0), ptLineEnd คือ (2,2) และ ptPoint คือ (1, 1)?
vac

0

คำตอบของ Jules รุ่น C #:

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

คุณสามารถทำได้โดยการแก้สมการเส้นตรงสำหรับส่วนของเส้นตรงนั้นด้วยพิกัดจุดคุณจะรู้ว่าจุดนั้นอยู่บนเส้นตรงหรือไม่จากนั้นตรวจสอบขอบเขตของส่วนเพื่อให้ทราบว่าอยู่ภายในหรือภายนอก คุณสามารถใช้เกณฑ์บางอย่างได้เพราะมันอยู่ที่ไหนสักแห่งในอวกาศซึ่งส่วนใหญ่มักจะกำหนดโดยค่าทศนิยมและคุณต้องไม่ถึงจุดที่แน่นอน ตัวอย่างใน 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;
    
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.