如何判断一个点是在一条线的右侧还是左侧

Posted

技术标签:

【中文标题】如何判断一个点是在一条线的右侧还是左侧【英文标题】:How to tell whether a point is to the right or left side of a line 【发布时间】:2010-12-06 08:08:37 【问题描述】:

我有一组点。我想将它们分成 2 个不同的集合。为此,我选择了两个点(ab)并在它们之间画一条假想线。现在我想把这条线左边的所有点放在一组中,把这条线右边的点放在另一组中。

我如何判断任何给定点 z 是在左边还是在右边?我试图计算 azb 之间的角度——小于 180 的角度在右侧,大于 180 的角度在左侧——但是由于 ArcCos 的定义,计算的角度总是更小大于 180°。是否有计算大于 180° 的角度的公式(或任何其他选择右侧或左侧的公式)?

【问题讨论】:

如何定义右或左? A) 从 P1 到 P2 看或 B) 平面中线的左侧或右侧。 为了澄清问题的第二部分,您可以使用 atan2() 而不是 acos() 来计算正确的角度。然而,正如 Eric Bainville 指出的那样,使用叉积是解决此问题的最佳方法。 下面的许多解决方案都不起作用,因为如果您交换点 a 和 b(我们用来定义线的点),它们会给出相反的答案。我在 Clojure 中给出了一个解决方案,它首先按字典顺序对两个点进行排序,然后再将它们与第三个点进行比较。 【参考方案1】:

使用向量行列式的符号(AB,AM),其中M(X,Y)是查询点:

position = sign((Bx - Ax) * (Y - Ay) - (By - Ay) * (X - Ax))

在线是0,一侧是+1,另一侧是-1

【讨论】:

+1 很好,有一点需要注意:当点非常接近线上时,舍入误差可能是一个问题。对于大多数用途来说不是问题,但它确实会时不时地咬人。 如果您发现自己处于此测试的舍入错误导致您出现问题的情况下,您将需要查找 Jon Shewchuk 的“计算几何的快速鲁棒谓词”。 为了澄清,这与直线 (b-a) 和从 a (m-a) 到点的向量之间的叉积的 Z 分量相同。在你最喜欢的向量类中: position = sign((b-a).cross(m-a)[2]) 不会交换 A 和 B 保持同一行,而是更改 positions 的符号? 是的。 A,B 定义方向,如“站在 A 看 B 时向左”。【参考方案2】:

使用equation of the lineab,得到与要排序的点在同一y坐标的线上的x坐标。

如果点的 x > 线的 x,则该点位于线的右侧。 如果点的 x 如果点的 x == 线的 x,则该点在线上。

【讨论】:

这是错误的,因为从 Aaginor 对第一个答案的评论中可以看出,我们不想弄清楚该点是在 DIRECTED 线 AB 的左侧还是右侧,即如果你站在 A 上,看向 B 是在你的左边还是在你的右边? @dionyziz - 嗯?我的回答没有为通过 AB 的线分配“方向”。我的回答假设“左”是坐标系的 -x 方向。接受的答案选择定义 vector AB,并使用叉积定义 left。原始问题没有具体说明“左”是什么意思。 注意:如果您使用这种方法(而不是被批准作为答案的跨产品方法),请注意当线接近水平时存在陷阱。数学错误会增加,如果完全水平,则会达到无穷大。解决方案是使用两点之间的增量较大的轴。 (或者也许更小的三角洲..这不是我的想法。) 这完全是我想要的。我不想知道 A 是高于还是低于 B。我只想知道它是否在该线的左侧(负 x 方向)! 也正是我想要的。非常简单,切中要害。谢谢!【参考方案3】:

你看看行列式的符号

| x2-x1  x3-x1 |
| y2-y1  y3-y1 |

一侧的点为正,另一侧为负(线本身的点为零)。

【讨论】:

扩展这个答案,以防人们不知道交叉产品的样子。下一个可视化步骤是 ( (x2-x1) * (y3-y1) ) - ( (y2 - y1) * (x3-x1) )【参考方案4】:

矢量(y1 - y2, x2 - x1) 垂直于直线,并且始终指向右侧(或者始终指向左侧,如果您的平面方向与我的不同)。

然后您可以计算该向量与(x3 - x1, y3 - y1) 的点积,以确定该点是否与垂直向量位于直线的同一侧(点积> 0)。

【讨论】:

【参考方案5】:

试试这个使用cross product的代码:

public bool isLeft(Point a, Point b, Point c)
     return ((b.X - a.X)*(c.Y - a.Y) - (b.Y - a.Y)*(c.X - a.X)) > 0;

其中 a = 线点 1; b = 线点 2; c = 要检查的点。

如果公式等于0,则点共线。

如果直线是水平的,那么如果点在直线上方,则返回 true。

【讨论】:

@lzprgmr:不,这是一个叉积,相当于二维矩阵的行列式。考虑由行 (a,b) 和 (c,d) 定义的二维矩阵。行列式是 ad - bc。上面的形式是将2点表示的直线转化为一个向量(a,b),然后使用PointA和PointC定义another向量得到(c,d):(a,b ) = (PointB.x - PointA.x, PointB.y - PointA.y) (c,d) = (PointC.x - PointA.x, PointC.y - PointA.y) 行列式因此如其所述在帖子中。 我认为这是交叉积还是点积的混淆是因为它是二维的。它在两个维度上的叉积:mathworld.wolfram.com/CrossProduct.html 对于它的价值,这可以稍微简化为return (b.x - a.x)*(c.y - a.y) > (b.y - a.y)*(c.x - a.x);,但编译器可能会优化它。 此解决方案取决于点 A、B 的顺序。此答案 math.stackexchange.com/questions/757591/... 中提供的解决方案(这意味着计算线 AB 方程)与点 A、B 顺序无关。 情况 a = (1; 0), b = (-1; 0), c = (0; 2) 怎么样?它将返回 false,尽管点 c 位于线的左侧(上方应视为线的左侧,bcz 它形成左转)。证明: ((bX - aX)*(cY - aY) - (bY - aY)*(cX - aX)) = ((-1 - 1) * (2 - 0) - (0 - 0) * (0 - 1)) = -2 * 2 = -4 【参考方案6】:

首先检查是否有垂直线:

if (x2-x1) == 0
  if x3 < x2
     it's on the left
  if x3 > x2
     it's on the right
  else
     it's on the line

然后,计算斜率:m = (y2-y1)/(x2-x1)

然后,使用点斜率形式创建直线方程:y - y1 = m*(x-x1) + y1。为了我的解释,将其简化为斜率截距形式(在您的算法中不是必需的):y = mx+b

现在为xy 插入(x3, y3)。这是一些伪代码,详细说明了应该发生的情况:

if m > 0
  if y3 > m*x3 + b
    it's on the left
  else if y3 < m*x3 + b
    it's on the right
  else
    it's on the line
else if m < 0
  if y3 < m*x3 + b
    it's on the left
  if y3 > m*x3+b
    it's on the right
  else
    it's on the line
else
  horizontal line; up to you what you do

【讨论】:

失败:垂直线的斜率计算无效。没完没了的 if/else 东西。不确定这是否是 OP 所说的左/右的意思 - 如果是这样看它旋转 90 度会将此代码减半,因为“上方”是右或左。 这个答案有几个问题。垂直线导致除以零。更糟糕的是,它失败了,因为它不关心线的斜率是正数还是负数。 @phkahler,修复了垂直线问题。忘记一个测试用例绝对不是失败,但感谢您的客气话。 “无尽的if/else”是对数学理论的解释; OP的问题中没有提到编程。 @woodchips,修复了垂直线问题。斜率是变量 m;我会检查它是正面还是负面。【参考方案7】:

基本上,我认为有一个更简单直接的解决方案,对于任何给定的多边形,假设由四个顶点(p1,p2,p3,p4)组成,找到多边形中两个极端相反的顶点,换句话说,找到例如最左上角的顶点(比如说 p1)和位于右下角的相反顶点(比如说 )。因此,给定您的测试点 C(x,y),现在您必须在 C 和 p1 以及 C 和 p4 之间进行双重检查:

如果 cx > p1x AND cy > p1y ==> 表示 C 在 p1 的下方和右侧 下一个 if cx 表示C在p4的左上角

结论,C在矩形内。

谢谢:)

【讨论】:

(1) 回答的问题与被问到的不同?当矩形与两个轴对齐时,听起来像“边界框”测试。 (2) 更详细:假设 4 个点之间可能存在的关系。例如,取一个矩形,将其旋转 45 度,得到一个菱形。那颗钻石中没有“左上角”之类的东西。最左边的点既不是最顶部也不是最底部。当然,4个点可以形成更奇怪的形状。例如,3 个点可能在一个方向上很远,而第 4 个点在另一个方向上。继续努力!【参考方案8】:

我在 java 中实现了这个并运行了一个单元测试(来源如下)。以上解决方案均无效。此代码通过了单元测试。如果有人发现单元测试没有通过,请告诉我。

代码:注意:如果两个数字非常接近,nearlyEqual(double,double) 返回 true。

/*
 * @return integer code for which side of the line ab c is on.  1 means
 * left turn, -1 means right turn.  Returns
 * 0 if all three are on a line
 */
public static int findSide(
        double ax, double ay, 
        double bx, double by,
        double cx, double cy) 
    if (nearlyEqual(bx-ax,0))  // vertical line
        if (cx < bx) 
            return by > ay ? 1 : -1;
        
        if (cx > bx) 
            return by > ay ? -1 : 1;
         
        return 0;
    
    if (nearlyEqual(by-ay,0))  // horizontal line
        if (cy < by) 
            return bx > ax ? -1 : 1;
        
        if (cy > by) 
            return bx > ax ? 1 : -1;
         
        return 0;
    
    double slope = (by - ay) / (bx - ax);
    double yIntercept = ay - ax * slope;
    double cSolution = (slope*cx) + yIntercept;
    if (slope != 0) 
        if (cy > cSolution) 
            return bx > ax ? 1 : -1;
        
        if (cy < cSolution) 
            return bx > ax ? -1 : 1;
        
        return 0;
    
    return 0;

这是单元测试:

@Test public void testFindSide() 
    assertTrue("1", 1 == Utility.findSide(1, 0, 0, 0, -1, -1));
    assertTrue("1.1", 1 == Utility.findSide(25, 0, 0, 0, -1, -14));
    assertTrue("1.2", 1 == Utility.findSide(25, 20, 0, 20, -1, 6));
    assertTrue("1.3", 1 == Utility.findSide(24, 20, -1, 20, -2, 6));

    assertTrue("-1", -1 == Utility.findSide(1, 0, 0, 0, 1, 1));
    assertTrue("-1.1", -1 == Utility.findSide(12, 0, 0, 0, 2, 1));
    assertTrue("-1.2", -1 == Utility.findSide(-25, 0, 0, 0, -1, -14));
    assertTrue("-1.3", -1 == Utility.findSide(1, 0.5, 0, 0, 1, 1));

    assertTrue("2.1", -1 == Utility.findSide(0,5, 1,10, 10,20));
    assertTrue("2.2", 1 == Utility.findSide(0,9.1, 1,10, 10,20));
    assertTrue("2.3", -1 == Utility.findSide(0,5, 1,10, 20,10));
    assertTrue("2.4", -1 == Utility.findSide(0,9.1, 1,10, 20,10));

    assertTrue("vertical 1", 1 == Utility.findSide(1,1, 1,10, 0,0));
    assertTrue("vertical 2", -1 == Utility.findSide(1,10, 1,1, 0,0));
    assertTrue("vertical 3", -1 == Utility.findSide(1,1, 1,10, 5,0));
    assertTrue("vertical 3", 1 == Utility.findSide(1,10, 1,1, 5,0));

    assertTrue("horizontal 1", 1 == Utility.findSide(1,-1, 10,-1, 0,0));
    assertTrue("horizontal 2", -1 == Utility.findSide(10,-1, 1,-1, 0,0));
    assertTrue("horizontal 3", -1 == Utility.findSide(1,-1, 10,-1, 0,-9));
    assertTrue("horizontal 4", 1 == Utility.findSide(10,-1, 1,-1, 0,-9));

    assertTrue("positive slope 1", 1 == Utility.findSide(0,0, 10,10, 1,2));
    assertTrue("positive slope 2", -1 == Utility.findSide(10,10, 0,0, 1,2));
    assertTrue("positive slope 3", -1 == Utility.findSide(0,0, 10,10, 1,0));
    assertTrue("positive slope 4", 1 == Utility.findSide(10,10, 0,0, 1,0));

    assertTrue("negative slope 1", -1 == Utility.findSide(0,0, -10,10, 1,2));
    assertTrue("negative slope 2", -1 == Utility.findSide(0,0, -10,10, 1,2));
    assertTrue("negative slope 3", 1 == Utility.findSide(0,0, -10,10, -1,-2));
    assertTrue("negative slope 4", -1 == Utility.findSide(-10,10, 0,0, -1,-2));

    assertTrue("0", 0 == Utility.findSide(1, 0, 0, 0, -1, 0));
    assertTrue("1", 0 == Utility.findSide(0,0, 0, 0, 0, 0));
    assertTrue("2", 0 == Utility.findSide(0,0, 0,1, 0,2));
    assertTrue("3", 0 == Utility.findSide(0,0, 2,0, 1,0));
    assertTrue("4", 0 == Utility.findSide(1, -2, 0, 0, -1, 2));

【讨论】:

【参考方案9】:

假设点是 (Ax,Ay) (Bx,By) 和 (Cx,Cy),则需要计算:

(Bx - Ax) * (Cy - Ay) - (By - Ay) * (Cx - Ax)

如果点 C 在由点 A 和 B 形成的线上,则这将等于零,并且根据边的不同,符号会有所不同。这是哪一边取决于您的 (x,y) 坐标的方向,但您可以将 A、B 和 C 的测试值插入此公式以确定负值是在左侧还是右侧。

【讨论】:

【参考方案10】:

@AVB 在 ruby​​ 中的回答

det = Matrix[
  [(x2 - x1), (x3 - x1)],
  [(y2 - y1), (y3 - y1)]
].determinant

如果det 为正则其上方,如果为负则其下方。如果为0,则上线。

【讨论】:

【参考方案11】:

这是一个版本,同样使用叉积逻辑,用 Clojure 编写。

(defn is-left? [line point]
  (let [[[x1 y1] [x2 y2]] (sort line)
        [x-pt y-pt] point]
    (> (* (- x2 x1) (- y-pt y1)) (* (- y2 y1) (- x-pt x1)))))

示例用法:

(is-left? [[-3 -1] [3 1]] [0 10])
true

也就是说点 (0, 10) 位于由 (-3, -1) 和 (3, 1) 确定的线的左侧。

注意:这个实现解决了一个其他(到目前为止)都没有解决的问题! 顺序很重要,在给出决定线的点时。即,在某种意义上,它是一条“有向线”。所以用上面的代码,这个调用也会产生true的结果:

(is-left? [[3 1] [-3 -1]] [0 10])
true

那是因为这段代码的 sn-p:

(sort line)

最后,与其他基于叉积的解决方案一样,此解决方案返回一个布尔值,并且不给出共线性的第三个结果。但它会给出一个有意义的结果,例如:

(is-left? [[1 1] [3 1]] [10 1])
false

【讨论】:

【参考方案12】:

了解网络专家提供的解决方案的另一种方法是了解一点几何含义。

pqr=[P,Q,R] 是形成一个平面的点,该平面被线 [P,R] 分成 2 条边。我们要找出 pqr 平面上的两个点 A,B 是否在同一侧。

pqr 平面上的任意点 T 可以用 2 个向量表示:v = PQ 和 u = RQ,如:

T' = T-Q = i * v + j * u

现在几何含义:

    i+j =1: 公关线上的T i+j i+j >1: Snq 上的 T i+j =0: T = Q i+j

i+j: <0 0 <1 =1 >1 ---------Q------[PR]--------- <== this is PQR plane ^ pr line

一般来说,

i+j 是 T 与 Q 或线 [P,R] 的距离的度量,而 i+j-1的符号暗示了T的偏向。

ij 的其他几何意义(与本解法无关)为:

i,j 是 T 在新坐标系中的标量,其中 v,u 是新坐标轴,Q > 是新的起源; i, j 可以分别看作是P,R拉力i 越大,T 离 R 越远(与 P 的拉力越大)。

i,j的值可以通过解方程得到:

i*vx + j*ux = T'x
i*vy + j*uy = T'y
i*vz + j*uz = T'z

所以我们在平面上得到了 2 个点 A,B:

A = a1 * v + a2 * u B = b1 * v + b2 * u

如果 A、B 在同一侧,则为真:

sign(a1+a2-1) = sign(b1+b2-1)

请注意,这也适用于以下问题:A,B 是否在平面 [P,Q,R] 的同一侧,其中:

T = i * P + j * Q + k * R

and i+j+k=1 意味着 T 在平面 [P,Q,R] 和 i+j+k-1 的符号暗示它的侧面。由此我们有:

A = a1 * P + a2 * Q + a3 * R B = b1 * P + b2 * Q + b3 * R

如果

和A,B在平面[P,Q,R]的同一侧

sign(a1+a2+a3-1) = sign(b1+b2+b3-1)

【讨论】:

【参考方案13】:

我想提供一个受物理学启发的解决方案。

想象一个沿线施加的力,并且您正在测量该力关于该点的扭矩。如果扭矩为正(逆时针),则该点位于直线的“左侧”,但如果扭矩为负,则该点位于直线的“右侧”。

所以如果力向量等于定义线的两点的跨度

fx = x_2 - x_1
fy = y_2 - y_1

您根据以下测试的符号测试点(px,py) 的边

var torque = fx*(py-y_1)-fy*(px-x_1)
if  torque>0  then
     "point on left side"
else if torque <0 then
     "point on right side"  
else
     "point on line"
end if

【讨论】:

【参考方案14】:

直线方程为 y-y1 = m(x-x1)

这里 m 是 y2-y1 / x2-x1

现在将 m 放入方程中,并将条件放在 y

例如。

for i in rows:

  for j in cols:

    if j>m(i-a)+b:

      image[i][j]=0

【讨论】:

【参考方案15】:

A(x1,y1) B(x2,y2) 一条长度为 L=sqrt( (y2-y1)^2 + (x2-x1)^2 ) 的线段

和一个点 M(x,y)

进行坐标变换,使 A 点成为新起点,B 点成为新 X 轴的点

我们有了点 M 的新坐标

哪些是 newX = ((x-x1)(x2-x1)+(y-y1)(y2-y1)) / L 从 (x-x1)*cos(t)+(y-y1)*sin(t) 其中 cos(t)=(x2-x1)/L, sin(t)=(y2-y1)/L

newY = ((y-y1)(x2-x1)-(x-x1)(y2-y1)) / L 从 (y-y1)*cos(t)-(x-x1)*sin(t)

因为“左”是 X 轴的一侧,Y 为正,如果 newY(即 M 到 AB 的距离)为正,那么它在 AB 的左侧(新的 X 轴) 如果您只想要符号,则可以省略除以 L(始终为正)

【讨论】:

以上是关于如何判断一个点是在一条线的右侧还是左侧的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Python 和 DLib 找到线的左侧或右侧的点?

matlab中如何计算一条线的长度

使用R,如何计算从一点到线的距离?

使用qwt画图时如何获得一条线的最右边的宽度

对于canvas画图时,改变其中一条线的颜色,该怎么解决

如何在谷歌地图中找到一条线的中点