Как определить, находится ли точка справа или слева от линии

130

У меня есть набор очков. Я хочу разделить их на 2 отдельных набора. Для этого я выбираю две точки ( a и b ) и провожу между ними воображаемую линию. Теперь я хочу, чтобы все точки, которые остались от этой линии, были в одном наборе, а те, которые находятся справа от этой линии, в другом наборе.

Как я могу определить для любой данной точки z , находится ли она в левом или правом наборе? Я попытался вычислить угол между азб - углы меньше 180 - с правой стороны, больше 180 - с левой стороны - но из-за определения ArcCos вычисленные углы всегда меньше 180 °. Есть ли формула для вычисления углов больше 180 ° (или любая другая формула для выбора правой или левой стороны)?

Aaginor
источник
Как определяется право или лево? А) с точки зрения взгляда с точки P1 на точку P2 или B) слева или справа от линии на плоскости.
phkahler
2
Чтобы уточнить, что касается второй части вашего вопроса, вы можете использовать atan2 () вместо acos () для вычисления правильного угла. Однако, как указал Эрик Бейнвиль, лучшим решением этой проблемы является использование перекрестного произведения.
dionyziz
Многие из приведенных ниже решений не работают, потому что они дают противоположные ответы, если вы поменяете местами точки a и b (точки, которые мы используем для определения нашей линии). Я даю решение на Clojure, которое сначала лексикографически сортирует две точки, прежде чем сравнивать их с третьей точкой.
Purplejacket

Ответы:

202

Используйте знак определителя векторов (AB,AM), где M(X,Y)- точка запроса:

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

Он находится 0на линии, и +1с одной стороны, и -1с другой стороны.

Эрик Бейнвиль
источник
10
+1 приятно, с одной вещью, о которой нужно знать: ошибка округления может быть проблемой, когда точка почти на линии. Не проблема для большинства пользователей, но время от времени это действительно кусает людей.
Стивен Кэнон,
16
Если вы окажетесь в ситуации, когда ошибка округления в этом тесте вызывает у вас проблемы, вам следует поискать «Быстрые надежные предикаты для вычислительной геометрии» Джона Шевчука.
Стивен Кэнон,
14
Для пояснения, это то же самое, что и Z-компонента перекрестного произведения между линией (ba) и вектором в точку от a (ma). В вашем любимом векторном классе: position = sign ((ba) .cross (ma) [2])
larsmoa
3
не поменять местами A и B, чтобы сохранить ту же строку, но поменять знак positions?
Jayen
6
Да. A, B определяют ориентацию, например, «слева от вас, когда вы стоите на A и смотрите на B».
Eric Bainville
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;
}

Где а = точка линии 1; b = точка 2 на линии; c = точка для проверки.

Если формула равна 0, точки коллинеарны.

Если линия горизонтальна, то возвращается истина, если точка находится над линией.

Jethro
источник
6
Если линия вертикальная, тогда?
Тофик Ахмад
9
вы имеете в виду точечный продукт?
Байянь Хуанг
13
@lzprgmr: Нет, это перекрестное произведение, то есть определитель 2D-матрицы. Рассмотрим 2D-матрицу, заданную строками (a, b) и (c, d). Определитель ad - bc. Приведенная выше форма преобразует линию, представленную двумя точками, в один вектор (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 05
6
Я думаю, что путаница в отношении того, является ли это кросс-продукт или скалярный продукт, связана с тем, что он имеет два измерения. Это является кросс продукт, в двух измерениях: mathworld.wolfram.com/CrossProduct.html
brianmearns
4
Как бы то ни было, это можно немного упростить return (b.x - a.x)*(c.y - a.y) > (b.y - a.y)*(c.x - a.x);, но компилятор, вероятно, все равно оптимизирует это.
Нику Стюрка
44

Вы смотрите на знак определителя

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

Он будет положительным для точек с одной стороны и отрицательным с другой (и нулевым для точек на самой линии).

AVB
источник
1
Расширяя этот ответ, на случай, если люди не знают, как выглядит кросс-продукт. Следующий визуальный шаг - ((x2-x1) * (y3-y1)) - ((y2 - y1) * (x3-x1))
Фрэнки Ривера
10

Вектор (y1 - y2, x2 - x1)перпендикулярен линии и всегда указывает вправо (или всегда указывает влево, если ориентация вашей плоскости отличается от моей).

Затем вы можете вычислить скалярное произведение этого вектора и (x3 - x1, y3 - y1)определить, находится ли точка на той же стороне линии, что и перпендикулярный вектор (скалярный продукт> 0) или нет.

Виктор Николе
источник
5

Используя уравнение для линии ab , получите координату x на линии с той же координатой y, что и точка, которую нужно отсортировать.

  • Если точка x> x линии, точка находится справа от линии.
  • Если точка x <x линии, точка находится слева от линии.
  • Если точка x == x линии, точка находится на линии.
mbeckish
источник
Это неправильно, потому что, как вы можете видеть из комментария Аагинора к первому ответу, мы не хотим выяснять, находится ли точка слева или справа от НАПРАВЛЕННОЙ линии AB, т.е. если вы стоите на A и смотрите в сторону B, слева или справа?
dionyziz
1
@dionyziz - А? Мой ответ не определяет «направление» линии, проходящей через AB. Мой ответ предполагает, что "left" - это направление -x системы corrdinate. В принятом ответе было выбрано определение вектора AB и определение левого с помощью векторного произведения. В исходном вопросе не уточняется, что подразумевается под «левым».
mbeckish
3
ПРИМЕЧАНИЕ. Если вы используете этот подход (а не метод перекрестного произведения, который был одобрен в качестве ответа), помните о ловушке, когда линия приближается к горизонтали. Математические ошибки увеличиваются и достигают бесконечности, если точно горизонтально. Решение состоит в том, чтобы использовать ту ось, у которой больше дельта между двумя точками. (Или, может быть, меньшая дельта ... это не в моей голове.)
ToolmakerSteve
это именно то, что я искал. Я не хочу знать, находится ли A выше или ниже B. Я просто хочу знать, осталось ли оно (отрицательное направление x) от линии!
Jayen
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)к xи y. Вот псевдокод с подробным описанием того, что должно произойти:

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
1
У этого ответа несколько проблем. Вертикальные линии вызывают деление на ноль. Хуже того, он терпит неудачу, потому что не беспокоится о том, положительный или отрицательный наклон линии.
2
@phkahler, исправлена ​​проблема с вертикальной линией. Определенно не провал из-за того, что забыл один тестовый пример, но спасибо за добрые слова. «Бесконечное if / else» - это объяснение математической теории; ничего в вопросе OP не упоминает программирование. @woodchips, исправлена ​​проблема с вертикальной линией. Наклон - переменная m; Я проверяю, положительный он или отрицательный.
Максим
5

Я реализовал это на java и запустил модульный тест (источник ниже). Ни одно из вышеперечисленных решений не работает. Этот код проходит модульный тест. Если кто-то обнаружит, что модульный тест не прошел, дайте мне знать.

Код: ПРИМЕЧАНИЕ: nearlyEqual(double,double)возвращает истину, если два числа очень близки.

/*
 * @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 в эту формулу, чтобы определить, находятся ли отрицательные значения слева или справа.

user2154342
источник
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 находится внутри прямоугольника.

Спасибо :)

Mohamed
источник
1
(1) Отвечает на другой вопрос, чем был задан? Похоже на тест «ограничивающего прямоугольника», когда прямоугольник выровнен по обеим осям. (2) Более подробно: делает предположение о возможных отношениях между 4 точками. Например, возьмите прямоугольник и поверните его на 45 градусов, чтобы получился ромб. В этом ромбе нет такой вещи, как «левая верхняя точка». Крайняя левая точка не является ни самой верхней, ни самой нижней. И, конечно же, 4 точки могут образовывать еще более странные формы. Например, 3 точки могут находиться далеко в одном направлении, а 4 точка - в другом. Продолжайте пытаться!
Инструментальщик Стив
1

@ Ответ AVB на рубине

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

Если detположительно - вверху, если отрицательно - внизу. Если 0, он на линии.

boulder_ruby
источник
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
Purplejacket
источник
0

Альтернативный способ почувствовать решения, предлагаемые сетями, - это немного понять геометрические последствия.

Пусть pqr = [P, Q, R] - точки, образующие плоскость, разделенную на 2 стороны прямой [P, R] . Мы должны выяснить, есть ли две точки на pqr плоскости , A, B, на одной стороне.

Любую точку T на плоскости pqr можно представить двумя векторами: v = PQ и u = RQ, как:

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

Теперь о геометрии:

  1. i + j = 1: T на линии пр.
  2. i + j <1: T на Sq
  3. i + j> 1: T на Snq
  4. я + j = 0: Т = Q
  5. i + j <0: T на Sq и за его пределами Q.

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.

Другие геометрические значения i и j (не связанные с этим решением):

  • 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]. , в которой:

Т = я * 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)

runsun
источник