你怎么能确定一个点是在一个线段上的另外两个点之间?
假设你有一个二维平面,其上有2个点(称为a和b),每个点用一个x整数和一个整数表示。
如何确定另一个点c是否位于由a和b定义的线段上?
我使用python最多,但任何语言的例子将是有益的。
检查(ba)和(ca)的叉积是否为0,如告诉Darius培根,告诉你a,b和c点是否alignment。
但是,如果要知道c是否在a和b之间,则还必须检查(ba)和(ca)的点积是正数 ,并且小于 a和b之间的距离的平方。
在非优化的伪代码中:
def isBetween(a, b, c): crossproduct = (cy - ay) * (bx - ax) - (cx - ax) * (by - ay) if abs(crossproduct) > epsilon : return False # (or != 0 if using integers) dotproduct = (cx - ax) * (bx - ax) + (cy - ay)*(by - ay) if dotproduct < 0 : return False squaredlengthba = (bx - ax)*(bx - ax) + (by - ay)*(by - ay) if dotproduct > squaredlengthba: return False return True
以下是我该怎么做:
def distance(a,b): return sqrt((ax - bx)**2 + (ay - by)**2) def is_between(a,c,b): return distance(a,c) + distance(c,b) == distance(a,b)
检查ba
和ca
的叉积是否为0
:表示所有点是共线的。 如果是,请检查c
的坐标是否在a
和b
之间。 只要a
和b
在该轴上是分开的(或者两者都是相同的),就使用x或y坐标。
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(ax, cx, bx) if ax != bx else within(ay, cy, by))) def collinear(a, b, c): "Return true iff a, b, and c all lie on the same line." return (bx - ax) * (cy - ay) == (cx - ax) * (by - ay) 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中的章节涵盖了共线性testingfunction的devise空间 – 有用的背景。 文森特的回答有助于改善这一点。 海耶斯build议只testingx或y坐标中的一个; 原来的代码已经and
代替if ax != bx else
。
这是另一种方法:
- 假设两点是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之间(平凡检查)
段的长度并不重要,因此使用平方根不是必需的,应该避免,因为我们可能会失去一些精度。
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 ax to cx, cy must be proportionally # increased than it takes to get from ax to bx . # Then, cx must be between ax and bx, and cy must be between ay and by # => 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 ((bx - ax) * (cy - ay) == (cx - ax) * (by - ay) and abs(cmp(ax, cx) + cmp(bx, cx)) <= 1 and abs(cmp(ay, cy) + cmp(by, cy)) <= 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)
使用更多的几何方法,计算以下距离:
ab = sqrt((ax-bx)**2 + (ay-by)**2) ac = sqrt((ax-cx)**2 + (ay-cy)**2) bc = sqrt((bx-cx)**2 + (by-cy)**2)
并testingac + bc是否等于ab :
is_on_segment = abs(ac + bc - ab) < EPSILON
那是因为有三种可能性:
- 三点形成一个三angular形=> ac + bc> ab
- 它们是共线的, c在ab片段之外=> ac + bc> ab
- 它们是共线的, c在ab片段内=> ac + bc = ab
好,很多提到的线性代数(vector的交叉乘积),这在真实(即连续或浮点)空间中工作,但问题明确指出,两点表示为整数 ,因此交叉乘积不是正确的虽然它可以给出一个近似的解决scheme。
正确的解决方法是在两点之间使用布雷森汉姆线algorithm ,并查看第三点是否是线上的点之一。 如果点数足够远以至于计算algorithm是非高性能的,那么我相信你可以挖掘并find优化。
这里有一个不同的方法去处理,用C ++给出的代码。 给出两点,l1和l2将它们之间的线段表示为是微不足道的
l1 + A(l2 - l1)
其中0 <= A <= 1。如果您感兴趣的不仅仅是用于这个问题,这就是所谓的线条的vector表示。 我们可以分解出这个的x和y分量,给出:
x = l1.x + A(l2.x - l1.x) y = l1.y + A(l2.y - l1.y)
取一个点(x,y),用它的x和y分量代入这两个expression式来求解A.如果两个expression式中A的解相等且0 <= A <= 1,则点在线上。解决A需要划分,当线段水平或垂直时,需要处理的特殊情况要停止除零。 最终的解决scheme如下:
// 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, py); // vertical line if(l2.y - l1.y == 0) return isBetween(l1.x, l2.x, px); // horizontal line double Ax = (px - l1.x) / (l2.x - l1.x); double Ay = (py - 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; }
(ca)和(ba)之间的标量积必须等于它们长度的乘积(这意味着vector(ca)和(ba)alignment并且具有相同的方向)。 而且,(ca)的长度必须小于或等于(ba)的长度。 伪代码:
# epsilon = small constant def isBetween(a, b, c): lengthca2 = (cx - ax)*(cx - ax) + (cy - ay)*(cy - ay) lengthba2 = (bx - ax)*(bx - ax) + (by - ay)*(by - ay) if lengthca2 > lengthba2: return False dotproduct = (cx - ax)*(bx - ax) + (cy - ay)*(by - ay) if dotproduct < 0.0: return False if abs(dotproduct*dotproduct - lengthca2*lengthba2) > epsilon: return False return True
我需要这个JavaScript的HTML5canvas用于检测用户光标是否在某一行或附近。 所以我把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
这是我在学校做的。 我忘了为什么这不是一个好主意。
编辑:
@Darius培根: 引用了一个“美丽的代码”的书 ,其中包含解释为什么下面的代码是不是一个好主意。
#!/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 ax > bx: a, b = b, a (self.x0, self.y0, self.x1, self.y1) = (ax, ay, bx, by) self.slope = (self.y1 - self.y0) / (self.x1 - self.x0) if self.x1 != self.x0 else None def __contains__(self, c): return (self.x0 <= cx <= self.x1 and min(self.y0, self.y1) <= cy <= max(self.y0, self.y1) and (not self.slope or -epsilon < (cy - self.y(cx)) < epsilon)) def y(self, x): return self.slope * (x - self.x0) + self.y0 if __name__ == '__main__': import doctest doctest.testmod()
c#从http://www.faqs.org/faqs/graphics/algorithms-faq/ – >主题1.02:如何find点到线的距离?
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; }
以下是一些适用于我的Java代码:
boolean liesOnSegment(Coordinate a, Coordinate b, Coordinate c) { double dotProduct = (cx - ax) * (cx - bx) + (cy - ay) * (cy - by); if (dotProduct < 0) return true; return false; }
如何确保斜率是相同的,重点在于其他?
给定点(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)
线段( a , b )(其中a和b是vector)上的任意点可以表示为两个vectora和b的线性组合 :
换句话说,如果c位于线段( a , b )上:
c = ma + (1 - m)b, where 0 <= m <= 1
解决m ,我们得到:
m = (cx - bx)/(ax - bx) = (cy - by)/(ay - by)
所以,我们的testing成为(在Python中):
def is_on(a, b, c): """Is c on the line segment ab?""" def _is_zero( val ): return -epsilon < val < epsilon x1 = ax - bx x2 = cx - bx y1 = ay - by y2 = cy - by 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)
在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#中通过操作符重载的段向量的点积
关键是利用点投影到无限长线上,并观察投影的标量是否能够平均地告诉我们投影是否在线段上。 我们可以调整标量的边界以使用模糊容差。
如果投影在范围内,我们只testing从点到投影的距离是否在范围内。
跨产品方法的好处是容差有一个有意义的价值。
您可以使用楔形和点形产品:
def dot(v,w): return vx*wx + vy*wy def wedge(v,w): return vx*wy - vy*wx def is_between(a,b,c): v = a - b w = b - c return wedge(v,w) == 0 and dot(v,w) > 0
这是我在Unity中使用C#的解决scheme。
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; }