Как определить, находится ли точка справа или слева от линии
У меня есть набор точек. Я хочу разделить их на 2 отдельных набора. Для этого я выбираю два пункта (a и b) и провести воображаемую линию между ними. Теперь я хочу, чтобы все точки, которые остались от этой линии в одном наборе, и те, которые находятся справа от этой линии в другом наборе.
Как я могу сказать в любой момент z находится ли он в левом или в правом наборе? Я попытался вычислить угол между a-z-b – углы меньше 180 находятся на правой стороне, больше 180 – на левой стороне, но из-за определения ArcCos вычисленные углы всегда меньше 180°. Есть ли формула для расчета углов больше 180° (или любая другая формула для выбора правой или левой стороны)?
11 ответов
используйте знак определителя векторов (AB,AM)
, где M(X,Y)
смысл запроса:
position = sign((Bx - Ax) * (Y - Ay) - (By - Ay) * (X - Ax))
это 0
, и +1
С одной стороны, -1
на другой стороне.
попробуйте этот код, который использует произведение:
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, если точка находится выше линии.
вы смотрите на знак определителя
| x2-x1 x3-x1 |
| y2-y1 y3-y1 |
он будет положительным для точек с одной стороны и отрицательным с другой (и нулевым для точек на самой линии).
вектор (y1 - y2, x2 - x1)
перпендикулярно линии и всегда указывает вправо (или всегда указывает влево, если вы плоскость ориентации отличается от моей).
затем вы можете вычислить скалярное произведение этого вектора и (x3 - x1, y3 - y1)
чтобы определить, находится ли точка на той же стороне линии, что и перпендикулярный вектор (точечное произведение > 0
) или нет.
я реализовал это на 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));
}
с помощью уравнение линии ab, получите координату x на линии в той же координате y, что и сортируемая точка.
- если точка x > линия x, точка находится справа от линии.
- Если точки x
- если точка x = = линия x, точка находится на линии.
сначала проверьте, есть ли у вас вертикальная линия:
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
в принципе, я думаю, что есть решение, которое намного проще и прямолинейно,для любого заданного многоугольника,скажем,состоит из четырех вершин(p1, p2, p3, p4), найти две крайние противоположные вершины в многоугольнике, другими словами, найти, например, самую верхнюю левую вершину (скажем, p1) и противоположную вершину, которая находится в самом нижнем правом углу (скажем ). Следовательно, учитывая вашу тестовую точку C( x, y), Теперь вам нужно сделать двойную проверку между C и p1 и C и p4:
Если cx > p1x и cy > p1y ==> означает, что C ниже и справа от p1 следующий если cx означает, что C находится вверху и слева от p4
заключение, C находится внутри прямоугольника.
спасибо :)
предполагая, что точки (Ax,Ay) (Bx,By) и (Cx, Cy), вам нужно вычислить:
(Bx - Ax) * (Cy - Ay) - (By - Ay) * (Cx-Ax)
Это будет равно нулю, если точка C находится на линии, образованной точками A и B, и будет иметь другой знак в зависимости от стороны. Какая сторона это зависит от ориентации ваших координат (x, y), но вы можете подключить тестовые значения для A, B и C в эту формулу, чтобы определить, являются ли отрицательные значения слева или право.
ответ@AVB в ruby
det = Matrix[
[(x2 - x1), (x3 - x1)],
[(y2 - y1), (y3 - y1)]
].determinant
Если det
положительно ее выше, если отрицательно ее ниже. Если 0, он на линии.
вот версия, снова используя логику кросс-продукта, написанную на 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