Cómo saber si un punto está al lado derecho o izquierdo de una línea

130

Tengo un conjunto de puntos. Quiero separarlos en 2 conjuntos distintos. Para hacer esto, elijo dos puntos ( a y b ) y dibujar una línea imaginaria entre ellos. Ahora quiero tener todos los puntos que quedan de esta línea en un conjunto y los que están directamente desde esta línea en el otro conjunto.

¿Cómo puedo saber para un punto dado z si está en el conjunto izquierdo o derecho? Traté de calcular el ángulo entre azb (los ángulos menores que 180 están en el lado derecho, mayores que 180 en el lado izquierdo), pero debido a la definición de ArcCos, los ángulos calculados siempre son menores que 180 °. ¿Existe una fórmula para calcular ángulos mayores de 180 ° (o alguna otra fórmula para elegir el lado derecho o izquierdo)?

Aaginor
fuente
¿Cómo se define derecha o izquierda? A) en términos de mirar de P1 a P2 o B) a la izquierda o derecha de la línea en el plano.
phkahler
2
Para aclarar, a la segunda parte de su pregunta, puede usar atan2 () en lugar de acos () para calcular el ángulo correcto. Sin embargo, el uso de un producto cruzado es la mejor solución para esto, como señaló Eric Bainville.
dionyziz
Muchas de las soluciones a continuación no funcionan porque dan respuestas opuestas si intercambia los puntos ayb (los puntos que estamos utilizando para definir nuestra línea). Doy una solución en Clojure que clasifica los dos puntos lexicográficamente primero antes de compararlos con el tercer punto.
Purplejacket

Respuestas:

202

Use el signo del determinante de vectores (AB,AM), donde M(X,Y)está el punto de consulta:

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

Está 0en la línea, y +1en un lado, -1en el otro lado.

Eric Bainville
fuente
10
+1 agradable, con una cosa a tener en cuenta: el error de redondeo puede ser una preocupación cuando el punto está casi en la línea. No es un problema para la mayoría de los usos, pero muerde a las personas de vez en cuando.
Stephen Canon
16
Si se encuentra en una situación en la que el error de redondeo en esta prueba le está causando problemas, deberá buscar los "Predicados rápidos y robustos para geometría computacional" de Jon Shewchuk.
Stephen Canon
14
Para aclarar, esto es lo mismo que el componente Z del producto cruzado entre la línea (ba) y el vector hasta el punto desde a (ma). En su clase de vector favorita: posición = signo ((ba) .cross (ma) [2])
larsmoa
3
¿no cambiar A y B mantendría la misma línea, pero cambiaría el signo de positions?
Jayen
66
Si. A, B define la orientación, como en "a su izquierda al pararse en A y mirar a B".
Eric Bainville
224

Pruebe este código que hace uso de un producto cruzado :

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

Donde a = línea punto 1; b = punto de línea 2; c = punto para verificar.

Si la fórmula es igual a 0, los puntos son colineales.

Si la línea es horizontal, esto devuelve verdadero si el punto está por encima de la línea.

jethro
fuente
66
Si la línea es vertical, entonces?
Tofeeq Ahmad
9
¿te refieres a producto de punto?
Baiyan Huang
13
@lzprgmr: No, este es un producto cruzado, equivalente al determinante de una matriz 2D. Considere la matriz 2D definida por las filas (a, b) y (c, d). El determinante es ad - bc. La forma anterior está transformando una línea representada por 2 puntos en un vector, (a, b), y luego definiendo otro vector usando PointA y PointC para obtener (c, d): (a, b) = (PointB.x - PointA.x, PointB.y - PointA.y) (c, d) = (PointC.x - PointA.x, PointC.y - PointA.y) El determinante es, por lo tanto, tal como se indica en la publicación.
AndyG
66
Creo que la confusión sobre si se trata de un producto cruzado o un producto de puntos se debe a que tiene dos dimensiones. Es es el producto cruzado, en dos dimensiones: mathworld.wolfram.com/CrossProduct.html
brianmearns
44
Para lo que vale, esto puede simplificarse un poco return (b.x - a.x)*(c.y - a.y) > (b.y - a.y)*(c.x - a.x);, pero el compilador probablemente lo optimice de todos modos.
Nicu Stiurca
44

Miras el signo del determinante de

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

Será positivo para los puntos en un lado y negativo en el otro (y cero para los puntos en la línea misma).

AVB
fuente
1
Ampliando esta respuesta, en caso de que la gente no sepa cómo se ve el producto cruzado. El siguiente paso visual es ((x2-x1) * (y3-y1)) - ((y2 - y1) * (x3-x1))
Franky Rivera
10

El vector (y1 - y2, x2 - x1) es perpendicular a la línea y siempre apunta a la derecha (o siempre apunta a la izquierda, si la orientación de su plano es diferente a la mía).

Luego puede calcular el producto de punto de ese vector y (x3 - x1, y3 - y1)determinar si el punto se encuentra en el mismo lado de la línea que el vector perpendicular (producto de punto> 0) o no.

Victor Nicollet
fuente
5

Usando la ecuación de la línea ab , obtenga la coordenada x en la línea en la misma coordenada y que el punto a ordenar.

  • Si el punto es x> la línea x, el punto está a la derecha de la línea.
  • Si el punto es x <línea de x, el punto está a la izquierda de la línea.
  • Si el punto es x == línea x, el punto está en la línea.
mbeckish
fuente
Esto está mal, porque como puede ver en el comentario de Aaginor sobre la primera respuesta, no queremos saber si el punto está a la izquierda o a la derecha de la línea DIRECTA AB, es decir, si está parado en A y mirando hacia B ¿está a su izquierda o a su derecha?
dionyziz
1
@dionyziz - ¿Eh? Mi respuesta no asigna una "dirección" a la línea a través de AB. Mi respuesta asume que "izquierda" es la dirección -x del sistema de coordenadas. La respuesta aceptada eligió definir un vector AB y definir a la izquierda usando un producto cruzado. La pregunta original no especifica qué se entiende por "izquierda".
mbeckish
3
NOTA: Si usa este enfoque (en lugar del producto cruzado que fue aprobado como respuesta), tenga en cuenta un obstáculo a medida que la línea se acerca horizontalmente. Los errores matemáticos aumentan y llegan al infinito si son exactamente horizontales. La solución es usar el eje que tenga el delta mayor entre los dos puntos. (O tal vez un delta más pequeño ... esto está fuera de mi cabeza).
ToolmakerSteve
Esto es totalmente lo que estaba buscando. ¡no quiero saber si A está arriba o abajo de B. solo quiero saber si queda (dirección x negativa) de la línea!
Jayen
5

Primero verifique si tiene una línea vertical:

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

Luego, calcule la pendiente: m = (y2-y1)/(x2-x1)

A continuación, crear una ecuación de la línea utilizando la forma pendiente punto: y - y1 = m*(x-x1) + y1. Por el bien de mi explicación, simplifíquelo a la forma de pendiente-intercepción (no es necesario en su algoritmo):y = mx+b .

Ahora conecte (x3, y3)para xy y. Aquí hay un pseudocódigo que detalla lo que debería suceder:

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
maksim
fuente
3
Error: cálculo de pendiente no válido para líneas verticales. Sin fin si / otras cosas. No estoy seguro de si eso es lo que el OP quiso decir con izquierda / derecha; de ser así, mirarlo rotado 90 grados reduciría este código a la mitad ya que "arriba" sería derecha o izquierda.
phkahler
1
Esta respuesta tiene varios problemas. Las líneas verticales causan una división entre cero. Peor aún, falla porque no le preocupa si la pendiente de la línea es positiva o negativa.
2
@phkahler, solucionó el problema de la línea vertical. Definitivamente no es un fracaso por olvidar un caso de prueba, pero gracias por las amables palabras. "Endless if / else" es explicar la teoría matemática; nada en la pregunta de OP menciona programación. @woodchips, solucionó el problema de la línea vertical. La pendiente es la variable m; Verifico cuando es positivo o negativo.
maksim
5

Implementé esto en Java y ejecuté una prueba unitaria (fuente a continuación). Ninguna de las soluciones anteriores funciona. Este código pasa la prueba de la unidad. Si alguien encuentra una prueba unitaria que no pasa, hágamelo saber.

Código: NOTA: nearlyEqual(double,double)devuelve verdadero si los dos números están muy cerca.

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

Aquí está la prueba de la unidad:

@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));
}
Al Globus
fuente
2

Suponiendo que los puntos son (Ax, Ay) (Bx, By) y (Cx, Cy), debe calcular:

(Bx - Hacha) * (Cy - Ay) - (Por - Ay) * (Cx - Hacha)

Esto será igual a cero si el punto C está en la línea formada por los puntos A y B, y tendrá un signo diferente dependiendo del lado. De qué lado es esto depende de la orientación de sus coordenadas (x, y), pero puede insertar valores de prueba para A, B y C en esta fórmula para determinar si los valores negativos están a la izquierda o a la derecha.

usuario2154342
fuente
2

Quería proporcionar una solución inspirada en la física.

Imagina una fuerza aplicada a lo largo de la línea y estás midiendo el torque de la fuerza sobre el punto. Si el par es positivo (en sentido antihorario), entonces el punto está a la "izquierda" de la línea, pero si el par es negativo, el punto está a la "derecha" de la línea.

Entonces, si el vector de fuerza es igual al lapso de los dos puntos que definen la línea

fx = x_2 - x_1
fy = y_2 - y_1

prueba el lado de un punto en (px,py)función del signo de la siguiente prueba

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
John Alexiou
fuente
1

Básicamente, creo que hay una solución que es mucho más fácil y directa, para cualquier polígono dado, digamos que consisten en cuatro vértices (p1, p2, p3, p4), encontrar los dos vértices opuestos extremos en el polígono, en otro palabras, encuentre, por ejemplo, el vértice superior izquierdo (digamos p1) y el vértice opuesto que se encuentra en la parte inferior derecha (digamos). Por lo tanto, dado su punto de prueba C (x, y), ahora debe hacer una doble verificación entre C y p1 y C y p4:

if cx> p1x AND cy> p1y ==> significa que C es inferior y a la derecha de p1 siguiente si cx <p2x AND cy <p2y ==> significa que C es superior y a la izquierda de p4

conclusión, C está dentro del rectángulo.

Gracias :)

Mohamed
fuente
1
(1) Responde una pregunta diferente de la que se hizo? Suena como una prueba de "cuadro delimitador", cuando un rectángulo está alineado con ambos ejes. (2) En más detalle: hace suposición sobre las posibles relaciones entre 4 puntos. Por ejemplo, tome un rectángulo y gírelo 45 grados, de modo que tenga un diamante. No hay tal cosa como un "punto superior izquierdo" en ese diamante. El punto más a la izquierda no es el más alto ni el más bajo. Y, por supuesto, 4 puntos pueden formar formas aún más extrañas. Por ejemplo, 3 puntos podrían estar lejos en una dirección, y el 4to punto en otra dirección. ¡Sigue intentándolo!
ToolmakerSteve
1

La respuesta de @ AVB en rubí

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

Si detes positivo es arriba, si es negativo es abajo. Si 0, está en la línea.

boulder_ruby
fuente
1

Aquí hay una versión, nuevamente usando la lógica de productos cruzados, escrita en 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)))))

Ejemplo de uso:

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

Es decir que el punto (0, 10) está a la izquierda de la línea determinada por (-3, -1) y (3, 1).

NOTA: ¡Esta implementación resuelve un problema que ninguno de los otros (hasta ahora) hace! El orden importa al dar los puntos que determinan la línea. Es decir, es una "línea dirigida", en cierto sentido. Entonces, con el código anterior, esta invocación también produce el resultado de true:

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

Eso se debe a este fragmento de código:

(sort line)

Finalmente, como con las otras soluciones basadas en productos cruzados, esta solución devuelve un valor booleano y no da un tercer resultado para la colinealidad. Pero dará un resultado que tiene sentido, por ejemplo:

(is-left? [[1 1] [3 1]] [10 1])
false
Chaqueta morada
fuente
0

Una forma alternativa de tener una idea de las soluciones proporcionadas por los netters es comprender algunas implicaciones de la geometría.

Supongamos que pqr = [P, Q, R] son ​​puntos que forman un plano dividido en 2 lados por la línea [P, R] . Debemos averiguar si dos puntos en pqr plano , A, B, están en el mismo lado.

Cualquier punto T en el plano pqr puede representarse con 2 vectores: v = PQ yu = RQ, como:

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

Ahora las implicaciones de la geometría:

  1. i + j = 1: T en la línea pr
  2. i + j <1: T en Sq
  3. i + j> 1: T en Snq
  4. i + j = 0: T = Q
  5. i + j <0: T en Sq y más allá de Q.

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

En general,

  • i + j es una medida de qué tan lejos está T de Q o de la línea [P, R] , y
  • El signo de i + j-1 implica la lateralidad de T.

Los otros significados geométricos de i y j (no relacionados con esta solución) son:

  • i , j son los escalares para T en un nuevo sistema de coordenadas donde v, u son los nuevos ejes y Q es el nuevo origen;
  • i , j puede verse como fuerza de tracción para P, R , respectivamente. Cuanto más grande es i , más lejos está T de R (mayor atracción de P ).

El valor de i, j se puede obtener resolviendo las ecuaciones:

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

Entonces se nos dan 2 puntos, A, B en el avión:

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

Si A, B están del mismo lado, esto será cierto:

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

Tenga en cuenta que esto también se aplica a la pregunta: ¿Están A, B en el mismo lado del plano [P, Q, R] , en el que:

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

e i + j + k = 1 implica que T está en el plano [P, Q, R] y el signo de i + j + k-1 implica su lateralidad. De esto tenemos:

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

y A, B están en el mismo lado del plano [P, Q, R] si

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

Runsun
fuente