Как я могу проверить, пересекаются ли 2 сегмента?
Имеются следующие данные:
Segment1 [ {x1,y1}, {x2,y2} ]
Segment2 [ {x1,y1}, {x2,y2} ]
Мне нужно написать небольшой алгоритм на Python, чтобы определить, пересекаются ли 2 линии.
Как я могу проверить, пересекаются ли 2 сегмента?
Имеются следующие данные:
Segment1 [ {x1,y1}, {x2,y2} ]
Segment2 [ {x1,y1}, {x2,y2} ]
Мне нужно написать небольшой алгоритм на Python, чтобы определить, пересекаются ли 2 линии.
Ответы:
Уравнение линии:
f(x) = A*x + b = y
Для сегмента это то же самое, за исключением того, что x входит в интервал I.
Если у вас есть два сегмента, которые определены следующим образом:
Segment1 = {(X1, Y1), (X2, Y2)}
Segment2 = {(X3, Y3), (X4, Y4)}
Абсис Xa потенциальной точки пересечения (Xa, Ya) должен содержаться как в интервале I1, так и в I2, определяемых следующим образом:
I1 = [min(X1,X2), max(X1,X2)]
I2 = [min(X3,X4), max(X3,X4)]
И можно сказать, что Ха входит в:
Ia = [max( min(X1,X2), min(X3,X4) ),
min( max(X1,X2), max(X3,X4) )]
Теперь нам нужно проверить, что этот интервал Ia существует:
if (max(X1,X2) < min(X3,X4)):
return False # There is no mutual abcisses
Итак, у нас есть двухстрочная формула и взаимный интервал. Формулы вашей линии:
f1(x) = A1*x + b1 = y
f2(x) = A2*x + b2 = y
Поскольку мы получили две точки по сегментам, мы можем определить A1, A2, b1 и b2:
A1 = (Y1-Y2)/(X1-X2) # Pay attention to not dividing by zero
A2 = (Y3-Y4)/(X3-X4) # Pay attention to not dividing by zero
b1 = Y1-A1*X1 = Y2-A1*X2
b2 = Y3-A2*X3 = Y4-A2*X4
Если отрезки параллельны, то A1 == A2:
if (A1 == A2):
return False # Parallel segments
Точка (Xa, Ya), стоящая на обеих линиях, должна проверять обе формулы f1 и f2:
Ya = A1 * Xa + b1
Ya = A2 * Xa + b2
A1 * Xa + b1 = A2 * Xa + b2
Xa = (b2 - b1) / (A1 - A2) # Once again, pay attention to not dividing by zero
Последнее, что нужно сделать, это проверить, включен ли Xa в Ia:
if ( (Xa < max( min(X1,X2), min(X3,X4) )) or
(Xa > min( max(X1,X2), max(X3,X4) )) ):
return False # intersection is out of bound
else:
return True
В дополнение к этому вы можете проверить при запуске, что две из четырех предоставленных точек не равны, чтобы избежать всего этого тестирования.
Пользователь @ i_4_got указывает на эту страницу с очень эффективным решением на Python. Я воспроизвожу его здесь для удобства (так как я был бы счастлив иметь его здесь):
def ccw(A,B,C):
return (C.y-A.y) * (B.x-A.x) > (B.y-A.y) * (C.x-A.x)
# Return true if line segments AB and CD intersect
def intersect(A,B,C,D):
return ccw(A,C,D) != ccw(B,C,D) and ccw(A,B,C) != ccw(A,B,D)
Вам не нужно точно вычислять, где пересекаются сегменты, нужно только понимать , пересекаются ли они вообще. Это упростит решение.
Идея состоит в том, чтобы рассматривать один сегмент как «якорь» и разделять второй сегмент на 2 точки.
Теперь вам нужно будет найти относительное положение каждой точки относительно «привязанного» сегмента (OnLeft, OnRight или Collinear).
Сделав это для обеих точек, убедитесь, что одна из точек - OnLeft, а другая - OnRight (или, возможно, укажите коллинеарное положение, если вы также хотите включить неправильные пересечения).
Затем вы должны повторить процесс с ролями якоря и отдельных сегментов.
Пересечение существует тогда и только тогда, когда одна из точек - OnLeft, а другая - OnRight. См. Эту ссылку для более подробного объяснения с примерами изображений для каждого возможного случая.
Реализовать такой метод будет намного проще, чем на самом деле реализовать метод, который находит точку пересечения (учитывая множество угловых случаев, с которыми вам также придется справиться).
Обновить
Следующие функции должны иллюстрировать идею (источник: Computational Geometry in C ).
Примечание. В этом примере предполагается использование целых чисел. Если вместо этого вы используете представление с плавающей запятой (что, очевидно, может усложнить ситуацию), тогда вам следует определить какое-то значение epsilon для обозначения «равенства» (в основном для IsCollinear
оценки).
// points "a" and "b" forms the anchored segment.
// point "c" is the evaluated point
bool IsOnLeft(Point a, Point b, Point c)
{
return Area2(a, b, c) > 0;
}
bool IsOnRight(Point a, Point b, Point c)
{
return Area2(a, b, c) < 0;
}
bool IsCollinear(Point a, Point b, Point c)
{
return Area2(a, b, c) == 0;
}
// calculates the triangle's size (formed by the "anchor" segment and additional point)
int Area2(Point a, Point b, Point c)
{
return (b.X - a.X) * (c.Y - a.Y) -
(c.X - a.X) * (b.Y - a.Y);
}
Конечно, при использовании этих функций необходимо помнить, что каждый сегмент находится «между» другим сегментом (поскольку это конечные сегменты, а не бесконечные линии).
Кроме того, с помощью этих функций вы можете понять, есть ли у вас правильный или неправильный перекресток.
Предположим, у двух сегментов есть конечные точки A, B и C, D. Числовой надежный способ определить пересечение - это проверить знак четырех определителей:
| Ax-Cx Bx-Cx | | Ax-Dx Bx-Dx |
| Ay-Cy By-Cy | | Ay-Dy By-Dy |
| Cx-Ax Dx-Ax | | Cx-Bx Dx-Bx |
| Cy-Ay Dy-Ay | | Cy-By Dy-By |
Для пересечения каждый определитель слева должен иметь знак, противоположный знаку справа, но между двумя линиями не должно быть никакой связи. Вы в основном сравниваете каждую точку сегмента с другим сегментом, чтобы убедиться, что они лежат на противоположных сторонах линии, определяемой другим сегментом.
Смотрите здесь: http://www.cs.cmu.edu/~quake/robust.html
Проверить, пересекаются ли линейные сегменты, очень просто с помощью библиотеки Shapely, используя intersects
метод:
from shapely.geometry import LineString
line = LineString([(0, 0), (1, 1)])
other = LineString([(0, 1), (1, 0)])
print(line.intersects(other))
# True
line = LineString([(0, 0), (1, 1)])
other = LineString([(0, 1), (1, 2)])
print(line.intersects(other))
# False
На основании Liran в и Grumdrig в отличные ответы здесь полный код Python , чтобы проверить , если закрытые сегменты пересекаются. Работает для коллинеарных сегментов, сегментов, параллельных оси Y, вырожденных сегментов (черт в деталях). Предполагает целочисленные координаты. Координаты с плавающей запятой требуют модификации проверки равенства точек.
def side(a,b,c):
""" Returns a position of the point c relative to the line going through a and b
Points a, b are expected to be different
"""
d = (c[1]-a[1])*(b[0]-a[0]) - (b[1]-a[1])*(c[0]-a[0])
return 1 if d > 0 else (-1 if d < 0 else 0)
def is_point_in_closed_segment(a, b, c):
""" Returns True if c is inside closed segment, False otherwise.
a, b, c are expected to be collinear
"""
if a[0] < b[0]:
return a[0] <= c[0] and c[0] <= b[0]
if b[0] < a[0]:
return b[0] <= c[0] and c[0] <= a[0]
if a[1] < b[1]:
return a[1] <= c[1] and c[1] <= b[1]
if b[1] < a[1]:
return b[1] <= c[1] and c[1] <= a[1]
return a[0] == c[0] and a[1] == c[1]
#
def closed_segment_intersect(a,b,c,d):
""" Verifies if closed segments a, b, c, d do intersect.
"""
if a == b:
return a == c or a == d
if c == d:
return c == a or c == b
s1 = side(a,b,c)
s2 = side(a,b,d)
# All points are collinear
if s1 == 0 and s2 == 0:
return \
is_point_in_closed_segment(a, b, c) or is_point_in_closed_segment(a, b, d) or \
is_point_in_closed_segment(c, d, a) or is_point_in_closed_segment(c, d, b)
# No touching and on the same side
if s1 and s1 == s2:
return False
s1 = side(c,d,a)
s2 = side(c,d,b)
# No touching and on the same side
if s1 and s1 == s2:
return False
return True
Вот решение с использованием точечных произведений:
# assumes line segments are stored in the format [(x0,y0),(x1,y1)]
def intersects(s0,s1):
dx0 = s0[1][0]-s0[0][0]
dx1 = s1[1][0]-s1[0][0]
dy0 = s0[1][1]-s0[0][1]
dy1 = s1[1][1]-s1[0][1]
p0 = dy1*(s1[1][0]-s0[0][0]) - dx1*(s1[1][1]-s0[0][1])
p1 = dy1*(s1[1][0]-s0[1][0]) - dx1*(s1[1][1]-s0[1][1])
p2 = dy0*(s0[1][0]-s1[0][0]) - dx0*(s0[1][1]-s1[0][1])
p3 = dy0*(s0[1][0]-s1[1][0]) - dx0*(s0[1][1]-s1[1][1])
return (p0*p1<=0) & (p2*p3<=0)
Вот визуализация в Desmos: Пересечение отрезка линии
У вас есть два отрезка линии. Определите один сегмент конечными точками A и B, а второй сегмент - конечными точками C и D. Есть хороший трюк, чтобы показать, что они должны пересекаться ВНУТРИ границ сегментов. (Обратите внимание, что сами линии могут пересекаться за пределами сегментов, поэтому будьте осторожны. Хороший код также будет следить за параллельными линиями.)
Уловка состоит в том, чтобы проверить, что точки A и B должны располагаться на противоположных сторонах линии CD, И что точки C и D должны лежать на противоположных сторонах линии AB.
Поскольку это домашнее задание, я не буду давать вам точного решения. Но простой тест, чтобы увидеть, на какую сторону линии падает точка, - это использовать скалярное произведение. Таким образом, для данной линии CD вычислите вектор нормали к этой линии (я назову ее N_C.) Теперь просто проверьте признаки этих двух результатов:
dot(A-C,N_C)
а также
dot(B-C,N_C)
Если эти результаты имеют противоположные знаки, то A и B являются противоположными сторонами линии CD. Теперь проделайте тот же тест для другой линии, AB. Имеет нормальный вектор N_A. Сравните признаки
dot(C-A,N_A)
а также
dot(D-A,N_A)
Я предоставлю вам решить, как вычислить вектор нормали. (В 2-d это тривиально, но будет ли ваш код беспокоиться о том, являются ли A и B разными точками? Аналогично, различны ли C и D?)
Вам все еще нужно беспокоиться о линейных сегментах, которые лежат на одной и той же бесконечной линии, или о том, что одна точка фактически попадает на сам другой линейный сегмент. Хороший код решит все возможные проблемы.
Вот код C, чтобы проверить, находятся ли две точки на противоположных сторонах отрезка линии. Используя этот код, вы также можете проверить, пересекаются ли два сегмента.
// true if points p1, p2 lie on the opposite sides of segment s1--s2
bool oppositeSide (Point2f s1, Point2f s2, Point2f p1, Point2f p2) {
//calculate normal to the segment
Point2f vec = s1-s2;
Point2f normal(vec.y, -vec.x); // no need to normalize
// vectors to the points
Point2f v1 = p1-s1;
Point2f v2 = p2-s1;
// compare signs of the projections of v1, v2 onto the normal
float proj1 = v1.dot(normal);
float proj2 = v2.dot(normal);
if (proj1==0 || proj2==0)
cout<<"collinear points"<<endl;
return(SIGN(proj1) != SIGN(proj2));
}
Вот еще один код на Python, чтобы проверить, пересекаются ли закрытые сегменты. Это переписанная версия кода C ++ в http://www.cdn.geeksforgeeks.org/check-if-two-given-line-segments-intersect/ . Эта реализация охватывает все особые случаи (например, все точки коллинеарны).
def on_segment(p, q, r):
'''Given three colinear points p, q, r, the function checks if
point q lies on line segment "pr"
'''
if (q[0] <= max(p[0], r[0]) and q[0] >= min(p[0], r[0]) and
q[1] <= max(p[1], r[1]) and q[1] >= min(p[1], r[1])):
return True
return False
def orientation(p, q, r):
'''Find orientation of ordered triplet (p, q, r).
The function returns following values
0 --> p, q and r are colinear
1 --> Clockwise
2 --> Counterclockwise
'''
val = ((q[1] - p[1]) * (r[0] - q[0]) -
(q[0] - p[0]) * (r[1] - q[1]))
if val == 0:
return 0 # colinear
elif val > 0:
return 1 # clockwise
else:
return 2 # counter-clockwise
def do_intersect(p1, q1, p2, q2):
'''Main function to check whether the closed line segments p1 - q1 and p2
- q2 intersect'''
o1 = orientation(p1, q1, p2)
o2 = orientation(p1, q1, q2)
o3 = orientation(p2, q2, p1)
o4 = orientation(p2, q2, q1)
# General case
if (o1 != o2 and o3 != o4):
return True
# Special Cases
# p1, q1 and p2 are colinear and p2 lies on segment p1q1
if (o1 == 0 and on_segment(p1, p2, q1)):
return True
# p1, q1 and p2 are colinear and q2 lies on segment p1q1
if (o2 == 0 and on_segment(p1, q2, q1)):
return True
# p2, q2 and p1 are colinear and p1 lies on segment p2q2
if (o3 == 0 and on_segment(p2, p1, q2)):
return True
# p2, q2 and q1 are colinear and q1 lies on segment p2q2
if (o4 == 0 and on_segment(p2, q1, q2)):
return True
return False # Doesn't fall in any of the above cases
Ниже приведена функция тестирования, чтобы убедиться, что она работает.
import matplotlib.pyplot as plt
def test_intersect_func():
p1 = (1, 1)
q1 = (10, 1)
p2 = (1, 2)
q2 = (10, 2)
fig, ax = plt.subplots()
ax.plot([p1[0], q1[0]], [p1[1], q1[1]], 'x-')
ax.plot([p2[0], q2[0]], [p2[1], q2[1]], 'x-')
print(do_intersect(p1, q1, p2, q2))
p1 = (10, 0)
q1 = (0, 10)
p2 = (0, 0)
q2 = (10, 10)
fig, ax = plt.subplots()
ax.plot([p1[0], q1[0]], [p1[1], q1[1]], 'x-')
ax.plot([p2[0], q2[0]], [p2[1], q2[1]], 'x-')
print(do_intersect(p1, q1, p2, q2))
p1 = (-5, -5)
q1 = (0, 0)
p2 = (1, 1)
q2 = (10, 10)
fig, ax = plt.subplots()
ax.plot([p1[0], q1[0]], [p1[1], q1[1]], 'x-')
ax.plot([p2[0], q2[0]], [p2[1], q2[1]], 'x-')
print(do_intersect(p1, q1, p2, q2))
p1 = (0, 0)
q1 = (1, 1)
p2 = (1, 1)
q2 = (10, 10)
fig, ax = plt.subplots()
ax.plot([p1[0], q1[0]], [p1[1], q1[1]], 'x-')
ax.plot([p2[0], q2[0]], [p2[1], q2[1]], 'x-')
print(do_intersect(p1, q1, p2, q2))
closed_segment_intersect()
из тестового кода не определяется.
для отрезков AB и CD найти наклон отрезка CD
slope=(Dy-Cy)/(Dx-Cx)
протяните CD над A и B и пройдите расстояние до CD, идя прямо вверх
dist1=slope*(Cx-Ax)+Ay-Cy
dist2=slope*(Dx-Ax)+Ay-Dy
проверьте, находятся ли они на противоположных сторонах
return dist1*dist2<0
Поскольку вы не упоминаете, что хотите найти точку пересечения линии, проблема становится проще. Если вам нужна точка пересечения, то ответ OMG_peanuts - более быстрый подход. Однако, если вы просто хотите определить, пересекаются ли линии или нет, вы можете сделать это с помощью линейного уравнения (ax + by + c = 0). Подход следующий:
Начнем с двух отрезков линии: отрезка 1 и отрезка 2.
segment1 = [[x1,y1], [x2,y2]]
segment2 = [[x3,y3], [x4,y4]]
Убедитесь, что два линейных сегмента являются линией ненулевой длины и отдельными сегментами.
С этого момента я предполагаю, что эти два сегмента имеют отличную от нуля длину и различны. Для каждого сегмента линии вычислите наклон линии, а затем получите уравнение линии в виде ax + by + c = 0. Теперь вычислите значение f = ax + by + c для двух точек отрезка. другой сегмент линии (повторите то же самое для другого сегмента линии).
a2 = (y3-y4)/(x3-x4);
b1 = -1;
b2 = -1;
c1 = y1 - a1*x1;
c2 = y3 - a2*x3;
// using the sign function from numpy
f1_1 = sign(a1*x3 + b1*y3 + c1);
f1_2 = sign(a1*x4 + b1*y4 + c1);
f2_1 = sign(a2*x1 + b2*y1 + c2);
f2_2 = sign(a2*x2 + b2*y2 + c2);
Теперь все, что осталось, - это разные случаи. Если f = 0 для любой точки, то две линии касаются точки. Если f1_1 и f1_2 равны или f2_1 и f2_2 равны, то прямые не пересекаются. Если f1_1 и f1_2 не равны, а f2_1 и f2_2 не равны, то отрезки пересекаются. В зависимости от того, хотите ли вы рассматривать соприкасающиеся линии как «пересекающиеся» или нет, вы можете адаптировать свои условия.
a1
и не работает для ортогональных линий.
Мы также можем решить эту проблему, используя векторы.
Определим сегменты как [start, end]
. Учитывая два таких сегментов [A, B]
и [C, D]
что оба они имеют ненулевую длину, мы можем выбрать один из конечных точек , которые будут использоваться в качестве опорной точки , так что мы получаем три вектора:
x = 0
y = 1
p = A-C = [C[x]-A[x], C[y]-A[y]]
q = B-A = [B[x]-A[x], B[y]-A[y]]
r = D-C = [D[x]-C[x], D[y]-C[y]]
Отсюда мы можем найти пересечение, вычислив t и u in p + t*r = u*q
. Немного поиграв с уравнением, мы получаем:
t = (q[y]*p[x] - q[x]*p[y])/(q[x]*r[y] - q[y]*r[x])
u = (p[x] + t*r[x])/q[x]
Таким образом, функция такая:
def intersects(a, b):
p = [b[0][0]-a[0][0], b[0][1]-a[0][1]]
q = [a[1][0]-a[0][0], a[1][1]-a[0][1]]
r = [b[1][0]-b[0][0], b[1][1]-b[0][1]]
t = (q[1]*p[0] - q[0]*p[1])/(q[0]*r[1] - q[1]*r[0]) \
if (q[0]*r[1] - q[1]*r[0]) != 0 \
else (q[1]*p[0] - q[0]*p[1])
u = (p[0] + t*r[0])/q[0] \
if q[0] != 0 \
else (p[1] + t*r[1])/q[1]
return t >= 0 and t <= 1 and u >= 0 and u <= 1
Это мой способ проверки пересечения линии и места пересечения. Давайте использовать от x1 до x4 и от y1 до y4
Segment1 = {(X1, Y1), (X2, Y2)}
Segment2 = {(X3, Y3), (X4, Y4)}
Затем нам нужны векторы для их представления
dx1 = X2 - X1
dx2 = X4 - X4
dy1 = Y2 - Y1
dy2 = Y4 - Y3
Теперь посмотрим на определитель
det = dx1 * dy2 - dx2 * dy1
Если определитель равен 0,0, то отрезки параллельны. Это может означать, что они перекрываются. Если они перекрываются только в конечных точках, тогда существует одно решение пересечения. В противном случае решений будет бесконечное количество. Что является вашей точкой пересечения с бесконечным множеством решений? Так что это интересный частный случай. Если вы заранее знаете, что линии не могут перекрываться, вы можете просто проверить, det == 0.0
если да, просто скажите, что они не пересекаются, и готово. В противном случае, давайте продолжим
dx3 = X3 - X1
dy3 = Y3 - Y1
det1 = dx1 * dy3 - dx3 * dy1
det2 = dx2 * dy3 - dx3 * dy2
Теперь, если det, det1 и det2 равны нулю, тогда ваши линии коллинеарны и могут перекрываться. Если det равен нулю, но det1 или det2 нет, то они не коллинеарны, а параллельны, поэтому пересечения нет. Итак, что остается, если det равен нулю, это проблема 1D, а не 2D. Нам нужно будет проверить один из двух способов, в зависимости от того, равен ли dx1 нулю (чтобы избежать деления на ноль). Если dx1 равен нулю, просто проделайте ту же логику со значениями y, а не x ниже.
s = X3 / dx1
t = X4 / dx1
Это вычисляет два масштабатора, так что если мы масштабируем вектор (dx1, dy1) по s, мы получаем точку (x3, y3), а по t мы получаем (x4, y4). Итак, если s или t находится между 0,0 и 1,0, то точка 3 или 4 находится на нашей первой строке. Отрицательное значение будет означать, что точка находится за началом нашего вектора, а> 1.0 означает, что она находится впереди конца нашего вектора. 0.0 означает, что он находится в (x1, y1), а 1.0 означает, что он находится в (x2, y2). Если и s, и t <0,0 или оба> 1,0, то они не пересекаются. И это касается особого случая параллельных линий.
Теперь, если det != 0.0
тогда
s = det1 / det
t = det2 / det
if (s < 0.0 || s > 1.0 || t < 0.0 || t > 1.0)
return false // no intersect
На самом деле это похоже на то, что мы делали выше. Теперь, если мы пройдем вышеуказанный тест, наши отрезки линии пересекаются, и мы можем довольно легко вычислить пересечение следующим образом:
Ix = X1 + t * dx1
Iy = Y1 + t * dy1
Если вы хотите глубже понять, что делает математика, изучите правило Крамера.
Ответ Георгия , безусловно, наиболее понятен. Пришлось разобраться с этим, так как пример brycboe, хотя и простой, имел проблемы с колинеарностью.
Код для тестирования:
#!/usr/bin/python
#
# Notes on intersection:
#
# https://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
#
# /programming/3838329/how-can-i-check-if-two-segments-intersect
from shapely.geometry import LineString
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
def ccw(A,B,C):
return (C.y-A.y)*(B.x-A.x) > (B.y-A.y)*(C.x-A.x)
def intersect(A,B,C,D):
return ccw(A,C,D) != ccw(B,C,D) and ccw(A,B,C) != ccw(A,B,D)
def ShapelyIntersect(A,B,C,D):
return LineString([(A.x,A.y),(B.x,B.y)]).intersects(LineString([(C.x,C.y),(D.x,D.y)]))
a = Point(0,0)
b = Point(0,1)
c = Point(1,1)
d = Point(1,0)
'''
Test points:
b(0,1) c(1,1)
a(0,0) d(1,0)
'''
# F
print(intersect(a,b,c,d))
# T
print(intersect(a,c,b,d))
print(intersect(b,d,a,c))
print(intersect(d,b,a,c))
# F
print(intersect(a,d,b,c))
# same end point cases:
print("same end points")
# F - not intersected
print(intersect(a,b,a,d))
# T - This shows as intersected
print(intersect(b,a,a,d))
# F - this does not
print(intersect(b,a,d,a))
# F - this does not
print(intersect(a,b,d,a))
print("same end points, using shapely")
# T
print(ShapelyIntersect(a,b,a,d))
# T
print(ShapelyIntersect(b,a,a,d))
# T
print(ShapelyIntersect(b,a,d,a))
# T
print(ShapelyIntersect(a,b,d,a))
если ваши данные определяют линию, вам просто нужно доказать, что они не параллельны. Для этого вы можете вычислить
alpha = float(y2 - y1) / (x2 - x1).
Если этот коэффициент равен как для Line1, так и для Line2, это означает, что прямые параллельны. Если нет, значит, они пересекутся.
Если они параллельны, вы должны доказать, что они не совпадают. Для этого вы вычисляете
beta = y1 - alpha*x1
Если бета одинакова для Line1 и Line2, это означает, что ваши линии пересекаются, поскольку они равны
Если они сегментные, вам все равно придется вычислять альфа и бета, как описано выше для каждой строки. Затем вы должны проверить, что (beta1 - beta2) / (alpha1 - alpha2) больше Min (x1_line1, x2_line1) и меньше Max (x1_line1, x2_line1)
Это то, что у меня есть для AS3, я мало знаю о python, но концепция есть
public function getIntersectingPointF($A:Point, $B:Point, $C:Point, $D:Point):Number {
var A:Point = $A.clone();
var B:Point = $B.clone();
var C:Point = $C.clone();
var D:Point = $D.clone();
var f_ab:Number = (D.x - C.x) * (A.y - C.y) - (D.y - C.y) * (A.x - C.x);
// are lines parallel
if (f_ab == 0) { return Infinity };
var f_cd:Number = (B.x - A.x) * (A.y - C.y) - (B.y - A.y) * (A.x - C.x);
var f_d:Number = (D.y - C.y) * (B.x - A.x) - (D.x - C.x) * (B.y - A.y);
var f1:Number = f_ab/f_d
var f2:Number = f_cd / f_d
if (f1 == Infinity || f1 <= 0 || f1 >= 1) { return Infinity };
if (f2 == Infinity || f2 <= 0 || f2 >= 1) { return Infinity };
return f1;
}
public function getIntersectingPoint($A:Point, $B:Point, $C:Point, $D:Point):Point
{
var f:Number = getIntersectingPointF($A, $B, $C, $D);
if (f == Infinity || f <= 0 || f >= 1) { return null };
var retPoint:Point = Point.interpolate($A, $B, 1 - f);
return retPoint.clone();
}
Реализовано на JAVA. Однако кажется, что это не работает для коллинеарных линий (то есть сегментов, которые существуют друг в друге L1 (0,0) (10,10) L2 (1,1) (2,2)
public class TestCode
{
public class Point
{
public double x = 0;
public double y = 0;
public Point(){}
}
public class Line
{
public Point p1, p2;
public Line( double x1, double y1, double x2, double y2)
{
p1 = new Point();
p2 = new Point();
p1.x = x1;
p1.y = y1;
p2.x = x2;
p2.y = y2;
}
}
//line segments
private static Line s1;
private static Line s2;
public TestCode()
{
s1 = new Line(0,0,0,10);
s2 = new Line(-1,0,0,10);
}
public TestCode(double x1, double y1,
double x2, double y2,
double x3, double y3,
double x4, double y4)
{
s1 = new Line(x1,y1, x2,y2);
s2 = new Line(x3,y3, x4,y4);
}
public static void main(String args[])
{
TestCode code = null;
////////////////////////////
code = new TestCode(0,0,0,10,
0,1,0,5);
if( intersect(code) )
{ System.out.println( "OK COLINEAR: INTERSECTS" ); }
else
{ System.out.println( "ERROR COLINEAR: DO NOT INTERSECT" ); }
////////////////////////////
code = new TestCode(0,0,0,10,
0,1,0,10);
if( intersect(code) )
{ System.out.println( "OK COLINEAR: INTERSECTS" ); }
else
{ System.out.println( "ERROR COLINEAR: DO NOT INTERSECT" ); }
////////////////////////////
code = new TestCode(0,0,10,0,
5,0,15,0);
if( intersect(code) )
{ System.out.println( "OK COLINEAR: INTERSECTS" ); }
else
{ System.out.println( "ERROR COLINEAR: DO NOT INTERSECT" ); }
////////////////////////////
code = new TestCode(0,0,10,0,
0,0,15,0);
if( intersect(code) )
{ System.out.println( "OK COLINEAR: INTERSECTS" ); }
else
{ System.out.println( "ERROR COLINEAR: DO NOT INTERSECT" ); }
////////////////////////////
code = new TestCode(0,0,10,10,
1,1,5,5);
if( intersect(code) )
{ System.out.println( "OK COLINEAR: INTERSECTS" ); }
else
{ System.out.println( "ERROR COLINEAR: DO NOT INTERSECT" ); }
////////////////////////////
code = new TestCode(0,0,0,10,
-1,-1,0,10);
if( intersect(code) )
{ System.out.println( "OK SLOPE END: INTERSECTS" ); }
else
{ System.out.println( "ERROR SLOPE END: DO NOT INTERSECT" ); }
////////////////////////////
code = new TestCode(-10,-10,10,10,
-10,10,10,-10);
if( intersect(code) )
{ System.out.println( "OK SLOPE Intersect(0,0): INTERSECTS" ); }
else
{ System.out.println( "ERROR SLOPE Intersect(0,0): DO NOT INTERSECT" ); }
////////////////////////////
code = new TestCode(-10,-10,10,10,
-3,-2,50,-2);
if( intersect(code) )
{ System.out.println( "OK SLOPE Line2 VERTIAL: INTERSECTS" ); }
else
{ System.out.println( "ERROR SLOPE Line2 VERTICAL: DO NOT INTERSECT" ); }
////////////////////////////
code = new TestCode(-10,-10,10,10,
50,-2,-3,-2);
if( intersect(code) )
{ System.out.println( "OK SLOPE Line2 (reversed) VERTIAL: INTERSECTS" ); }
else
{ System.out.println( "ERROR SLOPE Line2 (reversed) VERTICAL: DO NOT INTERSECT" ); }
////////////////////////////
code = new TestCode(0,0,0,10,
1,0,1,10);
if( intersect(code) )
{ System.out.println( "ERROR PARALLEL VERTICAL: INTERSECTS" ); }
else
{ System.out.println( "OK PARALLEL VERTICAL: DO NOT INTERSECT" ); }
////////////////////////////
code = new TestCode(0,2,10,2,
0,10,10,10);
if( intersect(code) )
{ System.out.println( "ERROR PARALLEL HORIZONTAL: INTERSECTS" ); }
else
{ System.out.println( "OK PARALLEL HORIZONTAL: DO NOT INTERSECT" ); }
////////////////////////////
code = new TestCode(0,10,5,13.75,
0,18.75,10,15);
if( intersect(code) )
{ System.out.println( "ERROR PARALLEL SLOPE=.75: INTERSECTS" ); }
else
{ System.out.println( "OK PARALLEL SLOPE=.75: DO NOT INTERSECT" ); }
////////////////////////////
code = new TestCode(0,0,1,1,
2,-1,2,10);
if( intersect(code) )
{ System.out.println( "ERROR SEPERATE SEGMENTS: INTERSECTS" ); }
else
{ System.out.println( "OK SEPERATE SEGMENTS: DO NOT INTERSECT" ); }
////////////////////////////
code = new TestCode(0,0,1,1,
-1,-10,-5,10);
if( intersect(code) )
{ System.out.println( "ERROR SEPERATE SEGMENTS 2: INTERSECTS" ); }
else
{ System.out.println( "OK SEPERATE SEGMENTS 2: DO NOT INTERSECT" ); }
}
public static boolean intersect( TestCode code )
{
return intersect( code.s1, code.s2);
}
public static boolean intersect( Line line1, Line line2 )
{
double i1min = Math.min(line1.p1.x, line1.p2.x);
double i1max = Math.max(line1.p1.x, line1.p2.x);
double i2min = Math.min(line2.p1.x, line2.p2.x);
double i2max = Math.max(line2.p1.x, line2.p2.x);
double iamax = Math.max(i1min, i2min);
double iamin = Math.min(i1max, i2max);
if( Math.max(line1.p1.x, line1.p2.x) < Math.min(line2.p1.x, line2.p2.x) )
return false;
double m1 = (line1.p2.y - line1.p1.y) / (line1.p2.x - line1.p1.x );
double m2 = (line2.p2.y - line2.p1.y) / (line2.p2.x - line2.p1.x );
if( m1 == m2 )
return false;
//b1 = line1[0][1] - m1 * line1[0][0]
//b2 = line2[0][1] - m2 * line2[0][0]
double b1 = line1.p1.y - m1 * line1.p1.x;
double b2 = line2.p1.y - m2 * line2.p1.x;
double x1 = (b2 - b1) / (m1 - m2);
if( (x1 < Math.max(i1min, i2min)) || (x1 > Math.min(i1max, i2max)) )
return false;
return true;
}
}
На данный момент результат
ERROR COLINEAR: DO NOT INTERSECT
ERROR COLINEAR: DO NOT INTERSECT
ERROR COLINEAR: DO NOT INTERSECT
ERROR COLINEAR: DO NOT INTERSECT
ERROR COLINEAR: DO NOT INTERSECT
OK SLOPE END: INTERSECTS
OK SLOPE Intersect(0,0): INTERSECTS
OK SLOPE Line2 VERTIAL: INTERSECTS
OK SLOPE Line2 (reversed) VERTIAL: INTERSECTS
OK PARALLEL VERTICAL: DO NOT INTERSECT
OK PARALLEL HORIZONTAL: DO NOT INTERSECT
OK PARALLEL SLOPE=.75: DO NOT INTERSECT
OK SEPERATE SEGMENTS: DO NOT INTERSECT
OK SEPERATE SEGMENTS 2: DO NOT INTERSECT
Я думал, что внесу хорошее решение Swift:
struct Pt {
var x: Double
var y: Double
}
struct LineSegment {
var p1: Pt
var p2: Pt
}
func doLineSegmentsIntersect(ls1: LineSegment, ls2: LineSegment) -> Bool {
if (ls1.p2.x-ls1.p1.x == 0) { //handle vertical segment1
if (ls2.p2.x-ls2.p1.x == 0) {
//both lines are vertical and parallel
return false
}
let x = ls1.p1.x
let slope2 = (ls2.p2.y-ls2.p1.y)/(ls2.p2.x-ls2.p1.x)
let c2 = ls2.p1.y-slope2*ls2.p1.x
let y = x*slope2+c2 // y intersection point
return (y > ls1.p1.y && x < ls1.p2.y) || (y > ls1.p2.y && y < ls1.p1.y) // check if y is between y1,y2 in segment1
}
if (ls2.p2.x-ls2.p1.x == 0) { //handle vertical segment2
let x = ls2.p1.x
let slope1 = (ls1.p2.y-ls1.p1.y)/(ls1.p2.x-ls1.p1.x)
let c1 = ls1.p1.y-slope1*ls1.p1.x
let y = x*slope1+c1 // y intersection point
return (y > ls2.p1.y && x < ls2.p2.y) || (y > ls2.p2.y && y < ls2.p1.y) // validate that y is between y1,y2 in segment2
}
let slope1 = (ls1.p2.y-ls1.p1.y)/(ls1.p2.x-ls1.p1.x)
let slope2 = (ls2.p2.y-ls2.p1.y)/(ls2.p2.x-ls2.p1.x)
if (slope1 == slope2) { //segments are parallel
return false
}
let c1 = ls1.p1.y-slope1*ls1.p1.x
let c2 = ls2.p1.y-slope2*ls2.p1.x
let x = (c2-c1)/(slope1-slope2)
return (((x > ls1.p1.x && x < ls1.p2.x) || (x > ls1.p2.x && x < ls1.p1.x)) &&
((x > ls2.p1.x && x < ls2.p2.x) || (x > ls2.p2.x && x < ls2.p1.x)))
//validate that x is between x1,x2 in both segments
}
Одно из вышеперечисленных решений сработало так хорошо, что я решил написать полную демонстрационную программу с использованием wxPython. Вы должны иметь возможность запускать эту программу так: python " ваше имя файла "
# Click on the window to draw a line.
# The program will tell you if this and the other line intersect.
import wx
class Point:
def __init__(self, newX, newY):
self.x = newX
self.y = newY
app = wx.App()
frame = wx.Frame(None, wx.ID_ANY, "Main")
p1 = Point(90,200)
p2 = Point(150,80)
mp = Point(0,0) # mouse point
highestX = 0
def ccw(A,B,C):
return (C.y-A.y) * (B.x-A.x) > (B.y-A.y) * (C.x-A.x)
# Return true if line segments AB and CD intersect
def intersect(A,B,C,D):
return ccw(A,C,D) != ccw(B,C,D) and ccw(A,B,C) != ccw(A,B,D)
def is_intersection(p1, p2, p3, p4):
return intersect(p1, p2, p3, p4)
def drawIntersection(pc):
mp2 = Point(highestX, mp.y)
if is_intersection(p1, p2, mp, mp2):
pc.DrawText("intersection", 10, 10)
else:
pc.DrawText("no intersection", 10, 10)
def do_paint(evt):
pc = wx.PaintDC(frame)
pc.DrawLine(p1.x, p1.y, p2.x, p2.y)
pc.DrawLine(mp.x, mp.y, highestX, mp.y)
drawIntersection(pc)
def do_left_mouse(evt):
global mp, highestX
point = evt.GetPosition()
mp = Point(point[0], point[1])
highestX = frame.Size[0]
frame.Refresh()
frame.Bind(wx.EVT_PAINT, do_paint)
frame.Bind(wx.EVT_LEFT_DOWN, do_left_mouse)
frame.Show()
app.MainLoop()
Используя решение OMG_Peanuts , я перевел на SQL. (Скалярная функция HANA)
Спасибо OMG_Peanuts, отлично работает. Я использую круглую землю, но расстояния небольшие, поэтому я считаю, что это нормально.
FUNCTION GA_INTERSECT" ( IN LAT_A1 DOUBLE,
IN LONG_A1 DOUBLE,
IN LAT_A2 DOUBLE,
IN LONG_A2 DOUBLE,
IN LAT_B1 DOUBLE,
IN LONG_B1 DOUBLE,
IN LAT_B2 DOUBLE,
IN LONG_B2 DOUBLE)
RETURNS RET_DOESINTERSECT DOUBLE
LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER AS
BEGIN
DECLARE MA DOUBLE;
DECLARE MB DOUBLE;
DECLARE BA DOUBLE;
DECLARE BB DOUBLE;
DECLARE XA DOUBLE;
DECLARE MAX_MIN_X DOUBLE;
DECLARE MIN_MAX_X DOUBLE;
DECLARE DOESINTERSECT INTEGER;
SELECT 1 INTO DOESINTERSECT FROM DUMMY;
IF LAT_A2-LAT_A1 != 0 AND LAT_B2-LAT_B1 != 0 THEN
SELECT (LONG_A2 - LONG_A1)/(LAT_A2 - LAT_A1) INTO MA FROM DUMMY;
SELECT (LONG_B2 - LONG_B1)/(LAT_B2 - LAT_B1) INTO MB FROM DUMMY;
IF MA = MB THEN
SELECT 0 INTO DOESINTERSECT FROM DUMMY;
END IF;
END IF;
SELECT LONG_A1-MA*LAT_A1 INTO BA FROM DUMMY;
SELECT LONG_B1-MB*LAT_B1 INTO BB FROM DUMMY;
SELECT (BB - BA) / (MA - MB) INTO XA FROM DUMMY;
-- Max of Mins
IF LAT_A1 < LAT_A2 THEN -- MIN(LAT_A1, LAT_A2) = LAT_A1
IF LAT_B1 < LAT_B2 THEN -- MIN(LAT_B1, LAT_B2) = LAT_B1
IF LAT_A1 > LAT_B1 THEN -- MAX(LAT_A1, LAT_B1) = LAT_A1
SELECT LAT_A1 INTO MAX_MIN_X FROM DUMMY;
ELSE -- MAX(LAT_A1, LAT_B1) = LAT_B1
SELECT LAT_B1 INTO MAX_MIN_X FROM DUMMY;
END IF;
ELSEIF LAT_B2 < LAT_B1 THEN -- MIN(LAT_B1, LAT_B2) = LAT_B2
IF LAT_A1 > LAT_B2 THEN -- MAX(LAT_A1, LAT_B2) = LAT_A1
SELECT LAT_A1 INTO MAX_MIN_X FROM DUMMY;
ELSE -- MAX(LAT_A1, LAT_B2) = LAT_B2
SELECT LAT_B2 INTO MAX_MIN_X FROM DUMMY;
END IF;
END IF;
ELSEIF LAT_A2 < LAT_A1 THEN -- MIN(LAT_A1, LAT_A2) = LAT_A2
IF LAT_B1 < LAT_B2 THEN -- MIN(LAT_B1, LAT_B2) = LAT_B1
IF LAT_A2 > LAT_B1 THEN -- MAX(LAT_A2, LAT_B1) = LAT_A2
SELECT LAT_A2 INTO MAX_MIN_X FROM DUMMY;
ELSE -- MAX(LAT_A2, LAT_B1) = LAT_B1
SELECT LAT_B1 INTO MAX_MIN_X FROM DUMMY;
END IF;
ELSEIF LAT_B2 < LAT_B1 THEN -- MIN(LAT_B1, LAT_B2) = LAT_B2
IF LAT_A2 > LAT_B2 THEN -- MAX(LAT_A2, LAT_B2) = LAT_A2
SELECT LAT_A2 INTO MAX_MIN_X FROM DUMMY;
ELSE -- MAX(LAT_A2, LAT_B2) = LAT_B2
SELECT LAT_B2 INTO MAX_MIN_X FROM DUMMY;
END IF;
END IF;
END IF;
-- Min of Max
IF LAT_A1 > LAT_A2 THEN -- MAX(LAT_A1, LAT_A2) = LAT_A1
IF LAT_B1 > LAT_B2 THEN -- MAX(LAT_B1, LAT_B2) = LAT_B1
IF LAT_A1 < LAT_B1 THEN -- MIN(LAT_A1, LAT_B1) = LAT_A1
SELECT LAT_A1 INTO MIN_MAX_X FROM DUMMY;
ELSE -- MIN(LAT_A1, LAT_B1) = LAT_B1
SELECT LAT_B1 INTO MIN_MAX_X FROM DUMMY;
END IF;
ELSEIF LAT_B2 > LAT_B1 THEN -- MAX(LAT_B1, LAT_B2) = LAT_B2
IF LAT_A1 < LAT_B2 THEN -- MIN(LAT_A1, LAT_B2) = LAT_A1
SELECT LAT_A1 INTO MIN_MAX_X FROM DUMMY;
ELSE -- MIN(LAT_A1, LAT_B2) = LAT_B2
SELECT LAT_B2 INTO MIN_MAX_X FROM DUMMY;
END IF;
END IF;
ELSEIF LAT_A2 > LAT_A1 THEN -- MAX(LAT_A1, LAT_A2) = LAT_A2
IF LAT_B1 > LAT_B2 THEN -- MAX(LAT_B1, LAT_B2) = LAT_B1
IF LAT_A2 < LAT_B1 THEN -- MIN(LAT_A2, LAT_B1) = LAT_A2
SELECT LAT_A2 INTO MIN_MAX_X FROM DUMMY;
ELSE -- MIN(LAT_A2, LAT_B1) = LAT_B1
SELECT LAT_B1 INTO MIN_MAX_X FROM DUMMY;
END IF;
ELSEIF LAT_B2 > LAT_B1 THEN -- MAX(LAT_B1, LAT_B2) = LAT_B2
IF LAT_A2 < LAT_B2 THEN -- MIN(LAT_A2, LAT_B2) = LAT_A2
SELECT LAT_A2 INTO MIN_MAX_X FROM DUMMY;
ELSE -- MIN(LAT_A2, LAT_B2) = LAT_B2
SELECT LAT_B2 INTO MIN_MAX_X FROM DUMMY;
END IF;
END IF;
END IF;
IF XA < MAX_MIN_X OR
XA > MIN_MAX_X THEN
SELECT 0 INTO DOESINTERSECT FROM DUMMY;
END IF;
RET_DOESINTERSECT := :DOESINTERSECT;
END;
Решено, но почему бы и не использовать python ... :)
def islineintersect(line1, line2):
i1 = [min(line1[0][0], line1[1][0]), max(line1[0][0], line1[1][0])]
i2 = [min(line2[0][0], line2[1][0]), max(line2[0][0], line2[1][0])]
ia = [max(i1[0], i2[0]), min(i1[1], i2[1])]
if max(line1[0][0], line1[1][0]) < min(line2[0][0], line2[1][0]):
return False
m1 = (line1[1][1] - line1[0][1]) * 1. / (line1[1][0] - line1[0][0]) * 1.
m2 = (line2[1][1] - line2[0][1]) * 1. / (line2[1][0] - line2[0][0]) * 1.
if m1 == m2:
return False
b1 = line1[0][1] - m1 * line1[0][0]
b2 = line2[0][1] - m2 * line2[0][0]
x1 = (b2 - b1) / (m1 - m2)
if (x1 < max(i1[0], i2[0])) or (x1 > min(i1[1], i2[1])):
return False
return True
Этот:
print islineintersect([(15, 20), (100, 200)], [(210, 5), (23, 119)])
Выход:
True
И это:
print islineintersect([(15, 20), (100, 200)], [(-1, -5), (-5, -5)])
Выход:
False