Come stabilire se un punto si trova sul lato destro o sinistro di una linea


130

Ho una serie di punti. Voglio separarli in 2 set distinti. Per fare questo, ho scelto due punti ( a e b ) e disegnare una linea immaginaria tra di loro. Ora voglio avere tutti i punti rimasti da questa linea in un set e quelli che sono proprio da questa linea nell'altro set.

Come posso sapere per ogni dato punto z se si trova nel set di sinistra o nel set di destra? Ho provato a calcolare l'angolo tra azb - gli angoli più piccoli di 180 sono sul lato destro, maggiori di 180 sul lato sinistro - ma a causa della definizione di ArcCos, gli angoli calcolati sono sempre più piccoli di 180 °. Esiste una formula per calcolare angoli maggiori di 180 ° (o qualsiasi altra formula per scegliere il lato destro o sinistro)?


Come viene definito destra o sinistra? A) in termini di guardare da P1 a P2 o B) a sinistra oa destra della linea nel piano.
Phkahler,

2
Per chiarire, nella seconda parte della tua domanda, puoi usare atan2 () invece di acos () per calcolare l'angolo corretto. Tuttavia, l'utilizzo di un prodotto incrociato è la soluzione migliore a questo, come ha sottolineato Eric Bainville.
Dionyziz,

Molte delle soluzioni seguenti non funzionano perché forniscono risposte opposte se si scambiano i punti aeb (i punti che stiamo usando per definire la nostra linea). Fornisco una soluzione in Clojure che ordina i due punti lessicograficamente prima di confrontarli con il terzo punto.
Purplejacket,

Risposte:


202

Utilizzare il segno del determinante dei vettori (AB,AM), dove si M(X,Y)trova il punto di query:

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

È 0sulla linea, e +1da un lato, -1dall'altro lato.


10
+1 bello, con una cosa da tenere presente: l'errore di arrotondamento può essere un problema quando il punto è quasi in linea. Non è un problema per la maggior parte degli usi, ma di tanto in tanto morde le persone.
Stephen Canon,

16
Se ti trovi in ​​una situazione in cui un errore di arrotondamento in questo test ti sta causando problemi, ti consigliamo di consultare i "Predicati veloci per la geometria computazionale" di Jon Shewchuk.
Stephen Canon,

14
Per chiarimento, questo è lo stesso del componente Z del prodotto incrociato tra la linea (ba) e il vettore al punto da un (ma). Nella tua classe vettoriale preferita: position = sign ((ba) .cross (ma) [2])
larsmoa

3
lo scambio di A e B manterrebbe la stessa linea, ma cambierebbe il segno di positions?
Jayen,

6
Sì. A, B definisce l'orientamento, come in "alla tua sinistra quando sei in piedi su A e guardi B".
Eric Bainville,

224

Prova questo codice che utilizza un prodotto incrociato :

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

Dove a = punto linea 1; b = punto linea 2; c = punto da verificare.

Se la formula è uguale a 0, i punti sono lineari.

Se la linea è orizzontale, questo ritorna vero se il punto è sopra la linea.


6
Se la linea è verticale allora?
Tofeeq Ahmad,

9
vuoi dire punto prodotto?
Baiyan Huang,

13
@lzprgmr: No, questo è un prodotto incrociato, equivalentemente il determinante di una matrice 2D. Considera la matrice 2D definita dalle righe (a, b) e (c, d). Il determinante è ad - bc. La forma sopra sta trasformando una linea rappresentata da 2 punti in un vettore, (a, b), e quindi definendo un altro vettore usando PointA e PointC per ottenere (c, d): (a, b) = (PointB.x - PointA.x, PointB.y - PointA.y) (c, d) = (PointC.x - PointA.x, PointC.y - PointA.y) Il determinante è quindi proprio come indicato nel post.
AndyG,

6
Penso che la confusione sul fatto che si tratti di un prodotto incrociato o di un punto è perché è in due dimensioni. Si tratta il prodotto incrociato, in due dimensioni: mathworld.wolfram.com/CrossProduct.html
brianmearns

4
Per quello che vale, questo può essere leggermente semplificato return (b.x - a.x)*(c.y - a.y) > (b.y - a.y)*(c.x - a.x);, ma il compilatore probabilmente lo ottimizza comunque.
Nicu Stiurca,

44

Guardi il segno del determinante di

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

Sarà positivo per i punti da un lato e negativo dall'altro (e zero per i punti sulla linea stessa).


1
Espandendo su questa risposta, nel caso in cui le persone non sappiano come si presenta il prodotto incrociato. Il prossimo passo visivo è ((x2-x1) * (y3-y1)) - ((y2 - y1) * (x3-x1))
Franky Rivera,

10

Il vettore (y1 - y2, x2 - x1)è perpendicolare alla linea e punta sempre a destra (o punta sempre a sinistra, se l'orientamento del piano è diverso dal mio).

È quindi possibile calcolare il prodotto punto di quel vettore e (x3 - x1, y3 - y1)determinare se il punto si trova sullo stesso lato della linea del vettore perpendicolare (prodotto punto> 0) oppure no.


5

Utilizzando l' equazione della linea ab , ottenere la coordinata x sulla linea con la stessa coordinata y del punto da ordinare.

  • Se il punto x> linea è x, il punto si trova a destra della linea.
  • Se il punto è x <linea è x, il punto è a sinistra della linea.
  • Se il punto è x == la linea è x, il punto è sulla linea.

Questo è sbagliato, perché come puoi vedere dal commento di Aaginor sulla prima risposta, non vogliamo capire se il punto si trova a sinistra o a destra della linea DIRECTED AB, cioè se stai su A e stai guardando verso B è alla tua sinistra o alla tua destra?
Dionyziz,

1
@dionyziz - Huh? La mia risposta non assegna una "direzione" alla linea attraverso AB. La mia risposta presuppone che "sinistra" sia la direzione -x del sistema corrdinato. La risposta accettata ha scelto di definire un vettore AB e di definire a sinistra utilizzando il prodotto incrociato. La domanda originale non specifica cosa si intende per "sinistra".
mbeckish,

3
NOTA: se si utilizza questo approccio (anziché quello tra prodotti approvati come risposta), prestare attenzione a un errore mentre la linea si avvicina all'orizzontale. Gli errori matematici aumentano e colpiscono l'infinito se esattamente orizzontali. La soluzione consiste nell'utilizzare qualunque asse abbia il delta maggiore tra i due punti. (O forse un delta più piccolo .. questo è dalla cima della mia testa.)
ToolmakerSteve

questo è totalmente quello che stavo cercando. non voglio sapere se A è sopra o sotto B. voglio solo sapere se è rimasto (direzione x negativa) della linea!
Jayen,

5

Prima controlla se hai una linea verticale:

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

Quindi, calcola la pendenza: m = (y2-y1)/(x2-x1)

Quindi creare un'equazione della linea tramite il modulo pendenza punto: y - y1 = m*(x-x1) + y1. Per il bene della mia spiegazione, semplificare al modulo di pendenza-intercetta (non è necessario nel vostro algoritmo): y = mx+b.

Ora collega (x3, y3)per xe y. Ecco alcuni pseudocodici che dettagliano cosa dovrebbe accadere:

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
Fallito: calcolo pendenza non valido per le linee verticali. Roba infinita se / altro. Non sono sicuro se questo è ciò che l'OP intendeva per sinistra / destra - se così fosse guardandolo ruotato di 90 gradi taglierebbe questo codice a metà poiché "sopra" sarebbe giusto o sinistro.
Phkahler,

1
Questa risposta ha diversi problemi. Le linee verticali causano una divisione per zero. Peggio ancora, fallisce perché non si preoccupa se la pendenza della linea sia positiva o negativa.

2
@phkahler, risolto il problema della linea verticale. Sicuramente non è un fallimento per aver dimenticato un caso di prova ma grazie per le belle parole. "Endless if / else" è quello di spiegare la teoria matematica; nulla nella domanda dell'OP menziona la programmazione. @woodchips, risolto il problema della linea verticale. La pendenza è la variabile m; Controllo quando è positivo o negativo.
Maksim,

5

Ho implementato questo in Java e ho eseguito un test unitario (fonte sotto). Nessuna delle soluzioni precedenti funziona. Questo codice supera il test unitario. Se qualcuno trova un test unitario che non supera, per favore fatemelo sapere.

Codice: NOTA: nearlyEqual(double,double)restituisce vero se i due numeri sono molto vicini.

/*
 * @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;
}

Ecco il test unitario:

@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

Supponendo che i punti siano (Ax, Ay) (Bx, By) e (Cx, Cy), è necessario calcolare:

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

Questo sarà uguale a zero se il punto C si trova sulla linea formata dai punti A e B e avrà un segno diverso a seconda del lato. Quale lato dipende dall'orientamento delle coordinate (x, y), ma puoi inserire i valori di test per A, B e C in questa formula per determinare se i valori negativi sono a sinistra o a destra.


2

Volevo fornire una soluzione ispirata alla fisica.

Immagina una forza applicata lungo la linea e stai misurando la coppia della forza attorno al punto. Se la coppia è positiva (in senso antiorario), allora il punto è alla "sinistra" della linea, ma se la coppia è negativa il punto è la "destra" della linea.

Quindi, se il vettore della forza è uguale all'intervallo dei due punti che definiscono la linea

fx = x_2 - x_1
fy = y_2 - y_1

si esegue il test per il lato di un punto in (px,py)base al segno del test seguente

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

fondamentalmente, penso che ci sia una soluzione che è molto più semplice e diretta, per ogni dato poligono, diciamo che consiste di quattro vertici (p1, p2, p3, p4), trova i due vertici estremi opposti nel poligono, in un altro parole, trova ad esempio il vertice più in alto a sinistra (diciamo p1) e il vertice opposto che si trova nella parte più in basso a destra (diciamo). Quindi, dato il tuo punto di prova C (x, y), ora devi fare un doppio controllo tra C e p1 e C e p4:

se cx> p1x AND cy> p1y ==> indica che C è inferiore e a destra di p1 successivo se cx <p2x AND cy <p2y ==> indica che C è superiore e a sinistra di p4

conclusione, C è all'interno del rettangolo.

Grazie :)


1
(1) Risponde a una domanda diversa da quella posta? Sembra un test "bounding box", quando un rettangolo è allineato con entrambi gli assi. (2) Più in dettaglio: ipotizza le possibili relazioni tra 4 punti. Ad esempio, prendi un rettangolo e ruotalo di 45 gradi, in modo da avere un diamante. Non esiste un "punto in alto a sinistra" in quel diamante. Il punto più a sinistra non è né il più in alto né il più in basso. E, naturalmente, 4 punti possono formare forme ancora più strane. Ad esempio, 3 punti potrebbero essere lontani in una direzione e il 4 ° punto in un'altra direzione. Continua a provare!
ToolmakerSteve

1

@ Risposta di AVB in rubino

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

Se detè positivo è sopra, se negativo è sotto. Se 0, è sulla linea.


1

Ecco una versione, sempre usando la logica del prodotto incrociato, scritta in 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)))))

Esempio di utilizzo:

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

Vale a dire che il punto (0, 10) è a sinistra della linea determinata da (-3, -1) e (3, 1).

NOTA: questa implementazione risolve un problema che nessuno degli altri (finora) fa! L'ordine è importante quando si danno i punti che determinano la linea. Vale a dire, è una "linea diretta", in un certo senso. Quindi, con il codice sopra, questa invocazione produce anche il risultato di true:

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

Questo a causa di questo frammento di codice:

(sort line)

Infine, come con le altre soluzioni basate su prodotti incrociati, questa soluzione restituisce un valore booleano e non fornisce un terzo risultato per la collinearità. Ma darà un risultato sensato, ad esempio:

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

0

Un modo alternativo di farsi un'idea delle soluzioni fornite dalle reti è quello di comprendere le implicazioni della geometria.

Sia pqr = [P, Q, R] sono punti che formano un piano che è diviso in 2 lati per linea [P, R] . Dobbiamo scoprire se due punti sul piano pqr , A, B, sono sullo stesso lato.

Qualsiasi punto T sul piano pqr può essere rappresentato con 2 vettori: v = PQ e u = RQ, come:

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

Ora le implicazioni della geometria:

  1. i + j = 1: T sulla riga pr
  2. i + j <1: T su Sq
  3. i + j> 1: T su Snq
  4. i + j = 0: T = Q
  5. i + j <0: T su Sq e oltre Q.

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

In generale,

  • i + j è una misura di quanto T è lontano da Q o linea [P, R] e
  • il segno di i + j-1 implica la disparità di T.

Gli altri significati geometrici di i e j (non correlati a questa soluzione) sono:

  • i , j sono gli scalari di T in un nuovo sistema di coordinate in cui v, u sono i nuovi assi e Q è la nuova origine;
  • i , j può essere visto come forza di tiro per P, R , rispettivamente. La grande i , T è più lontano da R (maggiore trazione da P ).

Il valore di i, j può essere ottenuto risolvendo le equazioni:

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

Quindi ci vengono dati 2 punti, A, B sull'aereo:

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

Se A, B sono dalla stessa parte, questo sarà vero:

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

Si noti che ciò vale anche per la domanda: sono A, B sullo stesso lato del piano [P, Q, R] , in cui:

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

e i + j + k = 1 implica che T è sul piano [P, Q, R] e il segno di i + j + k-1 implica la sua laterale. Da questo abbiamo:

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

e A, B sono sullo stesso lato del piano [P, Q, R] se

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.