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


130

我有几点要点。我想将它们分为2个不同的集合。为此,我选择两个点(ab)并在它们之间画一条假想线。现在,我要使一组中的该行剩余的点和另一组中的该行右边的点。

如何确定给定点z是在左侧还是右侧?我试图计算azb之间的角度-右侧小于180度,左侧大于180度-但是由于ArcCos的定义,计算出的角度始终小于180°。是否存在用于计算大于180°的角度的公式(或用于选择右侧或左侧的任何其他公式)?


左右是如何定义的?A)从P1到P2观看,或B)在平面中线的左侧或右侧。
phkahler 2010年

2
为了澄清,在问题的第二部分,您可以使用atan2()而不是acos()来计算正确的角度。但是,正如Eric Bainville指出的那样,使用叉形产品是最好的解决方案。
dionyziz 2011年

下面的许多解决方案不起作用,因为如果您交换点a和b(我们用来定义线的点),它们会给出相反的答案。我在Clojure中提供了一个解决方案,该解决方案先按字典顺序对两个点进行排序,然后再将它们与第三点进行比较。
Purplejacket

Answers:


202

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

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

0在线上,+1在一侧,-1在另一侧。


10
+1不错,有一点要注意:当点非常接近直线时,舍入误差可能是一个问题。对于大多数用途而言,这不是问题,但确实会不时地咬人。
斯蒂芬·佳能

16
如果您发现这种测试中的舍入错误导致您遇到问题,您将需要查找Jon Shewchuk的“计算几何学的快速健壮谓词”。
斯蒂芬·佳能

14
为了澄清起见,这与直线(ba)和矢量到(ma)的点之间的叉积的Z分量相同。在您最喜欢的向量类中:position = sign((ba).cross(ma)[2])
larsmoa 2010年

3
不会交换A和B保持同一行,但会更改符号positions
2014年

6
是。A,B定义方向,如“站在A并看着B时向左”。
埃里克·班维尔

224

尝试使用交叉产品的以下代码:

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。


6
如果线是垂直的呢?
Tofeeq Ahmad 2012年

9
你是说点积吗?
Baiyan Huang

13
@lzprgmr:不,这是一个叉积,等效于2D矩阵的行列式。考虑由(a,b)和(c,d)行定义的2D矩阵。行列式为ad-bc。上面的形式是将由2个点表示的线转换为一个向量(a,b),然后使用PointA和PointC 定义另一个向量以获得(c,d):(a,b)=(PointB.x- PointA.x,PointB.y-PointA.y)(c,d)=(PointC.x-PointA.x,PointC.y-PointA.y)因此行列式如帖子中所述。
AndyG 2013年

6
我认为对于这是叉积还是点积感到困惑是因为它是二维的。它互积,在两个维度上:mathworld.wolfram.com/CrossProduct.html
brianmearns 2013年

4
对于它的价值,可以将其稍微简化为return (b.x - a.x)*(c.y - a.y) > (b.y - a.y)*(c.x - a.x);,但是编译器可能仍会对其进行优化。
Nicu Stiurca,

44

你看一下行列式的符号

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

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


1
扩展此答案,以防人们不知道叉积是什么样子。下一步是((x2-x1)*(y3-y1))-((y2-y1)*(x3-x1))
Franky Rivera

10

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

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


5

使用直线 ab方程,获得直线上的x坐标,该y坐标与要排序的点相同。

  • 如果点的x>线的x,则该点在线的右侧。
  • 如果点的x <线的x,则该点在线的左侧。
  • 如果点的x ==线的x,则该点在该线上。

这是错误的,因为从Aaginor对第一个答案的评论中可以看出,我们不想弄清楚该点是在直接线AB的左侧还是右侧,也就是说,如果您站在A上并看朝向B是在您的左侧还是右侧?
dionyziz 2011年

1
@dionyziz-嗯?我的答案没有为通过AB的线路分配“方向”。我的答案假设“左”是cordtinate系统的-x方向。接受的答案选择定义向量 AB,并使用叉积定义左边。原始问题未指定“左”的含义。
mbeckish 2011年

3
注意:如果使用此方法(而不是被批准为答案的交叉乘积),请注意当直线接近水平时的陷阱。数学错误会增加,并且如果精确地达到水平,则会达到无穷大。解决方案是使用两点之间具有较大增量的轴。(或者更小的三角洲..这不在我的脑海中。)
ToolmakerSteve13年

这完全是我想要的。我不想知道A是否在B之上或之下。我只想知道它是否在直线的左边(负x方向)!
2014年

5

首先检查您是否有垂直线:

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

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

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

3
失败:坡度计算对垂直线无效。无尽的东西。不知道这是OP左/右的意思吗?如果这样看,它旋转了90度,则该代码将减少一半,因为“上”是右还是左。
phkahler 2010年

1
这个答案有几个问题。垂直线导致被零除。更糟糕的是,它失败了,因为它不担心直线的斜率是正还是负。

2
@phkahler,修复了垂直线问题。绝对不是忘记一个测试用例的失败,而是感谢您的客气话。“无穷无尽”是对数学理论的解释;OP的问题中都没有提到编程。@woodchips,修复了垂直线问题。斜率是变量m;我会检查它是正数还是负数。
maksim

5

我在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));
}

2

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

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

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


2

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

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

因此,如果力矢量等于定义直线的两点的跨度

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

1

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

如果cx> p1x AND cy> p1y ==>表示C较低且在p1的右边,如果cx <p2x AND cy <p2y ==>表示C较高且在p4的左边

结论,C在矩形内。

谢谢 :)


1
(1)回答的问题与提出的问题不同?当矩形与两个轴对齐时,听起来像“边界框”测试。(2)更详细:假设4点之间的可能关系。例如,取一个矩形,然后将其旋转45度,以便获得菱形。该菱形中没有“左上角”之类的东西。最左边的点既不是最高点也不是最低点。当然,四个点甚至可以形成更奇怪的形状。例如,在一个方向上可能有3个点相距很远,而在另一个方向上可能有4个点相距很远。继续尝试!
ToolmakerSteve

1

@AVB在红宝石中的答案

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

如果det为正,则为上,如果为负,则为下。如果为0,则就行了。


1

这是一个版本,再次使用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

这是因为以下代码段:

(sort line)

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

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

0

体验netter提供的解决方案的另一种方法是了解一些几何含义。

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

pqr平面上的任何点T都可以用2个矢量表示:v = PQ和u = RQ,如下所示:

T'= TQ = i * v + j * u

现在的几何含义:

  1. i + j = 1:公关线上的T
  2. i + j <1:S上的T
  3. i + j> 1:Snq上的T
  4. i + j = 0:T = Q
  5. i + j <0:Sq上的T并超出Q。

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

一般来说,

  • I + j是吨如何远距离Q或线[P,R]的量度,和
  • i + j-1的符号表示T的侧面。

ij的其他几何意义(与该解决方案无关)是:

  • ij是新坐标系中T的标量,其中v,u是新轴,Q是新原点;
  • Ĵ可以被看作是拉力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在同一侧,则为true:

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

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

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

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)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.