Найдите, находится ли сегмент на расстоянии другого

У меня есть куча сегментов (данные, которые у меня есть, - это две точки, которые составляют сегмент [x1, y1] и [x2, y2]), и я хотел бы классифицировать их в соответствии с их положением. Если сегмент достаточно близок к другому, я хочу соединить их вместе. Если бы мне пришлось описать это в предложении: я хотел бы найти все соседние сегменты на расстоянии 5 пикселей от любой точки сегмента.

С правилами, похожими на нарисованную картинку: picture

Я смотрел на разные алгоритмы, но большинство из них имеют дело с пересечениями и предсказывают, будут ли пересекаться линии. Это на самом деле не работает для меня, так как я не хочу продолжать линии до бесконечности, я просто хочу знать, находятся ли они в пределах 5 пикселей друг от друга.

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

Спасибо!


person Alexandre Guichet    schedule 17.03.2021    source источник


Ответы (1)


(Я пересмотрел свой предыдущий ответ. В этом ответе есть некоторые недостатки. Я думаю, что мой новый ответ показывает более простое и надежное решение.)

У вас есть два сегмента: S с точками S0 и S1 и T с точками T0 и T1. . Коллизия обнаруживается, когда эти сегменты находятся на расстоянии менее r друг от друга в одной точке.

Для сегмента S вы получаете вектор направления Δs, длину сегмента s и нормализованный вектор направления u.

Δs = S1 − S0
    s = |Δs|
    u = Δs / s

Единичный вектор u и точка S0 могут описывать преобразование любой точки P в точку P′:

P′x =   (Px − S0x) · ux + (Py< /sub> − S0y) · uy
    P′y = − (Px − S0x) · uy + (Py − S0y) · ux< /под>

В этом преобразовании точками отрезка S являются:

S′0 = (0, 0)
    S′1 = (s, 0)

Для преобразованных точек T′0 и T′1 компоненты y можно интерпретировать как расстояние со знаком до S. Теперь можно выполнить несколько тестов:

  • Первый тест заключается в том, находятся ли T'0 или T'1 на расстоянии r от сегмента S или в пределах радиуса r из S0′ или S1′. Если да, то у нас есть хит.

  • Следующий тест — пересекаются ли две прямые. Это может произойти только в том случае, если знаки T′0y или T′1y различны. Если да, то у нас есть хит.

  • Для последнего теста мы обращаем первый тест, преобразовывая S в S'' в системе, где T выровнен по оси x. Затем проверьте, находится ли одна из преобразованных точек S′′0 или S′′1 в пределах r от T′′. Если да, то у нас есть попадание, иначе — промах.

Код Python ниже. Я также обновил свой JS Fiddle.

Заметки:

  • Продольная переменная a и расстояние d в моем старом ответе фактически были такими же, как x' и y' здесь. Я думаю, что трансформация проще.

  • Это решение проверяет только (1) находятся ли точки T на расстоянии r от S, (2) пересекаются ли прямые и (3) находятся ли точки S на расстоянии < em>r из T. Случай коллинеарных отрезков улавливается тестами (1) и (3).

  • Приведенный ниже код не обрабатывает сегменты нулевой длины (S0 = S1 или T0 = T1 ) явно, но возврат ненулевого вектора в качестве нормы нулевого вектора, кажется, помогает — тесты (1) и (3) улавливают эти случаи.

Код Python:

import math

class Point:
    """ A point P(x, y) in 2D space
    """

    def __init__(self, x, y):
        self.x = float(x)
        self.y = float(y)

class Vector:
    """ A vector v(x, y) in 2D space
    """

    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def mag(self):
        """ magnitude of the vector
        """
        
        return math.hypot(self.x, self.y)
    
    def norm(self):
        """ return the normalized vector or (0, 0)
        """
    
        a = self.mag()
        
        if a*a < 1.0e-16:
            return Vector(1, 0)
        
        return Vector(self.x / a, self.y / a)
    


def diff(p, q):
    """ difference vector (q - p)
    """

    return Vector(q.x - p.x, q.y - p.y)

def within(p, dx, r):
    """ Is p within r of point (dx, 0)?
    """

    x = p.x - dx
    y = p.y
    
    return x*x + y*y <= r*r

def rot(p, u):
    """ Rotate point p to a coordinate system aligned with u.
    """

    return Point(p.x * u.x + p.y * u.y,
                -p.x * u.y + p.y * u.x)

def collision(s, t, r):
    """ Do the line segments s and t collide with a radius r
    """

    ds = diff(s[0], s[1])
    ss = ds.mag()    
    u = ds.norm()
    
    a0 = rot(diff(s[0], t[0]), u)
    a1 = rot(diff(s[0], t[1]), u)

    # Test T0 and T1 against S
    
    if -r <= a0.y <= r and -r <= a0.x <= ss + r:
        if a0.x < 0: return within(a0, 0, r)
        if a0.x > ss: return within(a0, ss, r)
        return True    
    
    if -r <= a1.y <= r and -r <= a1.x <= ss + r:
        if a1.x < 0: return within(a1, 0, r)
        if a1.x > ss: return within(a1, ss, r)
        return True

    # Test intersection
    
    if a0.y * a1.y < -0.9 * r * r:
        a = -a0.y * (a1.x - a0.x) / (a1.y - a0.y) + a0.x
        
        if 0 <= a <= ss: return True

    # Test S0 and S1 against T
    
    dt = diff(t[0], t[1])
    tt = dt.mag()    
    v = dt.norm()
    
    b0 = rot(diff(t[0], s[0]), v)
    b1 = rot(diff(t[0], s[1]), v)

    if 0 <= b0.x <= tt and -r <= b0.y <= r: return True
    if 0 <= b1.x <= tt and -r <= b1.y <= r: return True
       
    return False
person M Oehm    schedule 17.03.2021
comment
Здравствуйте, спасибо за ваш ответ. Это очень познавательно и полезно. Я играю с вашим JS Fiddle и обнаружил случай, который не охвачен вашим кодом: когда головки сегментов расположены рядом, но головки не зависают в середине каждого сегмента. Например, если поставить строки рядом (в продолжении), аналогично .---. .---. куда . являются головками сегментов и - телами сегментов. Как бы вы поступили в этом случае? Спасибо еще раз! - person Alexandre Guichet; 17.03.2021
comment
Неважно, я понял, что иметь дело с головами довольно легко. Большое спасибо за помощь! - person Alexandre Guichet; 17.03.2021
comment
Хм. Также есть проблема, когда линии коллинеарны, вероятно, потому, что я не улавливаю случая, когда d‹sub›0‹/sub› = d‹sub›1‹/sub›. Я не проверял кружок вокруг подсказок, потому что думал, что этот случай будет пойман обратной проверкой, но в вашем примере нет. - person M Oehm; 18.03.2021
comment
Я реализовал ваше решение на Python и протестировал его с моим набором данных. Кажется, что есть деление на 0 при вычислении apos и aneg. Координаты сегмента: S([2834, 813], [2844, 813]) и T([2843, 809], [2855, 809]). Так что кажется, что эта коллинеарность действительно является проблемой для меня. Как бы вы решили это? - person Alexandre Guichet; 18.03.2021
comment
Да, есть проблема, когда d₀ = d₁. Я думаю, вы можете уловить это после их вычисления и до вычисления апоса и анега. Если d₀ = d₁, вернуть true, если |d₀| ‹ r и если хотя бы одно из a₀ или a₁ находится между 0 и s и ложно в противном случае. Обратная проверка должна выявить возможность того, что a₀ ‹ 0 и a₁ › s. - person M Oehm; 18.03.2021
comment
(В Javascript деление на ноль просто вводит NaN и не выдает ошибку. Python более строгий. Исправление этого должно быть частью ответа, но у меня сейчас нет времени, чтобы правильно реализовать и протестировать все.) - person M Oehm; 18.03.2021
comment
Спасибо за зеленую галочку. Если вам все еще интересно, я переписал свой ответ, используя, как мне кажется, более простой и надежный подход. - person M Oehm; 21.03.2021
comment
Спасибо за совет. Я попробую переписать код, чтобы сравнивать одну строку со всеми остальными строками сразу. Так как я делаю один и тот же расчет для каждой строки отдельно. Я буду работать с матрицами, чтобы ds, ss и u можно было сделать для всех строк одновременно. a0 и a1 тоже должны быть. Моя цель состояла бы в том, чтобы вернуть массив [True, False] для всей строки. Затем я бы извлек наборы в соответствии с возвращенным массивом. Это улучшит производительность, но мне придется разобрать классы Point и Vector. Я поделюсь своим решением, если у меня будет время это сделать: D! Большое спасибо за помощь. - person Alexandre Guichet; 25.03.2021
comment
Я также подумаю, как заставить его работать для всех строк одновременно... возможно, возвращая 2D-матрицу [True, False], представляющую пересечение всех линий. Поскольку мои данные относительно малы (в среднем ~ 2500 строк), работа с матрицей 2500 * 2500 должна быть быстрее, чем просто зацикливание. Возможно, создав одну матрицу для ds, затем преобразовав все значения в u, а затем выполнив сравнение с использованием собственных функций pandas. Я должен подумать об этом. - person Alexandre Guichet; 25.03.2021