ポイントが線の右側または左側のどちらにあるかを確認する方法


130

ポイントのセットがあります。それらを2つの異なるセットに分けたいと思います。これを行うには、2つの点(ab)を選択し、それらの間に想像上の線を描きます。ここで、この線から離れたすべてのポイントを1つのセットに入れ、この線から右にあるすべてのポイントをもう1つのセットに入れます。

与えられた点zについて、それが左セットにあるのか右セットにあるのかをどのようにして知ることができますか?私はazb間の角度を計算しようとしました– 180未満の角度は右側にあり、左側は180を超えています–しかし、ArcCosの定義により、計算された角度は常に180°未満です。180°より大きい角度を計算する式(または右側または左側を選択する他の式)はありますか?


右または左はどのように定義されますか?A)P1からP2への視線、またはB)平面内の線の左側または右側。
phkahler 2010

2
明確にするために、質問の2番目の部分では、acos()の代わりにatan2()を使用して正しい角度を計算できます。ただし、Eric Bainvilleが指摘したように、クロス積を使用することがこれに対する最良のソリューションです。
dionyziz 2011

以下のソリューションの多くは、ポイントaとb(ラインの定義に使用しているポイント)を交換すると反対の答えを出すため、機能しません。Clojureで2つのポイントを辞書順にソートしてから3番目のポイントと比較するソリューションを提供します。
Purplejacket 2015

回答:


202

ベクトルの行列式の符号を使用します。(AB,AM)ここM(X,Y)で、はクエリポイントです。

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

それは0ライン上にあり+1、一方の側-1にあり、もう一方の側にあります。


10
+1が良く、1つの注意点があります。丸め誤差は、点が線上に非常に近い場合に問題になる可能性があります。ほとんどの用途では問題ありませんが、時々人を噛みます。
スティーブンキャノン

16
このテストで丸め誤差が問題を引き起こしている状況に気づいた場合は、Jon Shewchukの「計算幾何学の高速ロバスト述語」を調べてください。
スティーブンキャノン

14
明確にするために、これはライン(ba)とa(ma)からポイントへのベクトルの間の外積のZ成分と同じです。あなたの好きなベクタークラス:position = sign((ba).cross(ma)[2])
larsmoa

3
AとBを入れ替えても同じ行は保持されませんが、符号が変更されpositionsますか?
Jayen、2014年

6
はい。A、Bは、「Aに立ってBを見るときの左側」のように方向を定義します。
Eric Bainville、2014年

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つのポイントで表される線を1つのベクトル(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
これが外積か内積かを混同しているのは、2次元だからです。これ、2つの次元での外積
brianmearns

4
それだけの価値がある場合、これはに少し簡略化できますreturn (b.x - a.x)*(c.y - a.y) > (b.y - a.y)*(c.x - a.x);が、コンパイラはおそらくそれを最適化します。
Nicu Stiurca 2013年

44

あなたはの行列式の兆候を見て

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

片側の点は正、もう片側は負(線自体の点はゼロ)です。


1
この答えをさらに拡張して、人々がクロス積がどのように見えるかを知らない場合。次の視覚的ステップは((x2-x1)*(y3-y1))-((y2-y1)*(x3-x1))です
フランキーリベラ

10

ベクトル(y1 - y2, x2 - x1)は線に垂直であり、常に右向きです(または、平面の向きが私のものと異なる場合は、常に左向きです)。

次に、そのベクトルの内積を計算し、点(x3 - x1, y3 - y1)が垂直ベクトルと同じ線の側にあるかどうか(内積> 0)かどうかを判断できます。


5

線分 abの方程式を使用して、ソートする点と同じy座標で線上のx座標を取得します。

  • ポイントのx>ラインのxの場合、ポイントはラインの右側にあります。
  • ポイントのx <ラインのxの場合、ポイントはラインの左側にあります。
  • ポイントのx ==ラインのxの場合、ポイントはライン上にあります。

これは間違っています。最初の回答に関するAaginorのコメントからわかるように、点がDIRECTED線ABの左側にあるのか右側にあるのか、つまりAの上に立って見ているのかどうかはわかりません。 Bの方は左側ですか、右側ですか。
dionyziz 2011

1
@dionyziz-えっ?私の答えは、ABを通る線に「方向」を割り当てません。私の回答では、「左」が座標系の-x方向であると想定しています。受け入れられた答えは、ベクトル AB を定義し、クロス積を使用して左を定義することを選択しました。元の質問は、「左」が何を意味するかを指定していません。
mbeckish

3
注:この方法を使用する場合(回答として承認されたクロス積ではなく)、線が水平に近づくときの落とし穴に注意してください。数学エラーが増加し、正確に水平の場合は無限大になります。解決策は、2つのポイント間のデルタが大きい方の軸を使用することです。(または、より小さなデルタ..これは私の頭の上にあります。)
ToolmakerSteve

これは完全に私が探していたものです。AがBの上か下かを知りたくありません。Aがラインの左(負のx方向)かどうかを知りたいだけです!
Jayen、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)のために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
失敗:垂直線の勾配計算が無効です。無限のif / elseのもの。OPが左/右で何を意味するのかはわかりません。90度回転して見た場合、「上」が右または左になるため、このコードは半分にカットされます。
phkahler 2010

1
この回答にはいくつかの問題があります。垂直線はゼロ除算を引き起こします。さらに悪いことに、線の傾きが正か負かを気にしないので失敗します。

2
@phkahler、縦線の問題を修正しました。間違いなく、1つのテストケースを忘れたことによる失敗ではありませんが、親切な言葉に感謝します。「Endless if / else」は、数学的理論を説明することです。OPの質問にはプログラミングについての言及はありません。@woodchips、垂直線の問題を修正しました。勾配は変数mです。それが正か負かをチェックします。
maksim、2010

5

これをJavaで実装し、単体テストを実行しました(以下のソース)。上記の解決策はどれも機能しません。このコードは単体テストに合格しています。合格しないユニットテストを見つけた場合はお知らせください。

コード:注:nearlyEqual(double,double)2つの数値が非常に近い場合、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

物理学から着想を得た解決策を提供したかったのです。

線に沿って加えられた力を想像してください。あなたはその点の周りの力のトルクを測定しています。トルクが正の場合(反時計回り)、ポイントはラインの「左」にありますが、トルクが負の場合、ポイントはラインの「右」です。

したがって、力のベクトルが線を定義する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

基本的に、私は、任意のポリゴンについて、4つの頂点(p1、p2、p3、p4)で構成される、たとえば、ポリゴン内の2つの極端に反対側の頂点を別の単語、例えば、最も左上の頂点(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度回転させると、ひし形になります。そのダイヤモンドには「左上のポイント」などはありません。一番左の点は一番上でも一番下でもありません。そしてもちろん、4ポイントはさらに奇妙な形を形成することができます。たとえば、3つのポイントが1つの方向に離れており、4番目のポイントが別の方向にある場合があります。挑戦し続ける!
ToolmakerSteve 2013

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)

最後に、他の外積ベースのソリューションと同様に、このソリューションはブール値を返し、共線性の3番目の結果を与えません。しかし、それは意味のある結果を与えます、例えば:

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

0

netterによって提供されるソリューションの感触を得る別の方法は、ジオメトリの影響を少し理解することです。

ましょうPQR = [P、Q、R]はラインによって2辺に分割されている平面構成点である[P、Rに]pqr平面上の2つの点A、Bが同じ側にあるかどうかを確認します。

pqr平面上の任意の点Tは、2つのベクトル(v = PQおよびu)で表すことができます。として、RQを=。

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

次に、ジオメトリの影響:

  1. i + j = 1:PR行のT
  2. i + j <1:SqのT
  3. i + j> 1:SnqのT
  4. i + j = 0:T = Q
  5. i + j <0:SqおよびTを超えるT。

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の他のジオメトリの重要性(このソリューションには関係ありません)は次のとおりです。

  • ijは新しい座標系でのTのスカラーで、v、uは新しい軸、Qは新しい原点です。
  • IJは、として見ることができる力を引っ張るために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

また、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)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.