【问题标题】:improve in coding saving how to check if two line segments are crossing in Python改进编码保存如何在 Python 中检查两条线段是否相交
【发布时间】:2013-03-08 15:33:46
【问题描述】:

考虑以下交叉线示例:

l1 = ((20,5),(40,20))
l2 = ((20,20),(40,5))
l3 = ((30,30),(30,5)) # vertical line 

我开发了以下代码来计算交叉点的 x,y(参见理论细节)

def gradient(l):
    """Returns gradient 'm' of a line"""
    m = None
    # Ensure that the line is not vertical
    if l[0][0] != l[1][0]:
        m = (1./(l[0][0]-l[1][0]))*(l[0][1] - l[1][1])
        return m

def parallel(l1,l2):
    if gradient(l1) != gradient(l2):
        return False
    return True


def intersect(l):
    """Returns intersect (b) of a line using the equation of
    a line in slope and intercepet form (y = mx+b)"""
    return l[0][1] - (gradient(l)*l[0][0])


def line_intersection(l1,l2):
    """Returns the intersection point (x,y) of two line segments. Returns False
    for parallel lines"""
    # Not parallel
    if not parallel(l1,l2):
        if gradient(l1) is not None and gradient(l2) is not None:
            x = (1./(gradient(l1) - gradient(l2))) * (intersect(l2) - intersect(l1))
            y = (gradient(l1)*x) + intersect(l1)
        else:
            if gradient(l1) is None:
                x = l1[0][0]
                y = (gradient(l2)*x) + intersect(l2)
            elif gradient(l2) is None:
                x = l2[0][0]
                y = (gradient(l1)*x) + intersect(l1)
        return (x,y)
    else:
        return False

示例会话:

>>> line_intersection(l1,l2)
(30.0, 12.5)
>>> line_intersection(l2,l3)
(30, 12.5)

我希望在长度有限的线段的情况下以一种有效的方式改进我的代码,并且它们实际上可能不相交。

l1 = ((4,4),(10,10)) 
l2 = ((11,5),(5,11))
l3 = ((11,5),(9,7))

line_intersection(l1,l2) #valid
(8.0, 8.0)
line_intersection(l1,l3) # they don't cross each other
(8.0, 8.0)
line_intersection(l2,l3) #line parallel
False

我的 inelegant 解决方案如下。

def crosses(l1,l2):
    if not parallel(l1,l2):
        x = line_intersection(l1,l2)[0]
        xranges = [max(min(l1[0][0],l1[1][0]),min(l2[0][0],l2[1][0])),min(max(l1[0][0],l1[1][0]),max(l2[0][0],l2[1][0]))]
        if min(xranges) <= x <= max(xranges):
            return True
        else:
            return False
    else:
        return False


crosses(l1,l2)
True
crosses(l2,l3)
False

我正在寻找是否可以改进我在 python 中的函数样式

【问题讨论】:

  • 由于您只处理直线结构,因此检查交点是否在其中一条线的 x 坐标内就足够了。
  • 谢谢@WaleedKhan。我需要找到一个保存编码功能
  • 只是一个提示.. 不要使用斜率/相交。以参数形式写出你的行 x1 = p11 (1-a)/2 + p12 (1+a)/2 x2 = p21 (1-b)/2+p22(1+b)/2 求解 a,b 和检查 a,b 是否都在 -1,1 范围内。这样您就不需要将垂直线视为特殊情况。
  • 这个问题要么离题(应该在codereview.stackexchange.com上),要么与stackoverflow.com/questions/3838329/…重复,这取决于你如何看待它。

标签: python coding-style geometry line


【解决方案1】:

在我的书中,任何返回正确答案的代码都非常棒。干得好。

以下是一些建议:

def parallel(l1,l2):
    if gradient(l1) != gradient(l2):
        return False
    return True

可以写成

def parallel(l1,l2):
    return gradient(l1) == gradient(l2)

同样,

if min(xranges) <= x <= max(xranges):
    return True
else:
    return False

可以写成

return min(xranges) <= x <= max(xranges)

尽可能避免使用整数索引,尤其是像l1[0][0] 这样的双层整数索引。

单词或变量名比整数索引更容易阅读和理解。

整数索引的一种方法是使用“元组解包”:

(x1, y1), (x2, y2) = l1

然后l1[0][0] 变为x1。 这可以提高gradientcrosses 函数中代码的可读性。


线平行有两种情况。如果线不共线, 然后他们永远不会相交。但如果线共线,它们相交 无处不在。

好像不太准确

line_intersection(line, line)

当线条共线时为False。当有问题的行是完全相同的行时,它似乎更加错误(如果这样的事情是可能的:))。

如果线条是,我建议返回任意交点 共线,None 如果线平行但非共线。


在比较浮点数是否相等时会出现一个错误:

    In [81]: 1.2 - 1.0 == 0.2
    Out[81]: False

这不是 Python 中的错误,而是a problem caused by the internal representation of floats,它影响以任何语言完成的所有浮点计算。它可以在任何试图比较浮点数是否相等的代码中引入一个错误——比如这里:

def parallel(l1,l2):
    if gradient(l1) == gradient(l2): ...

因此,与其比较浮点数的相等性,我们能做的最好的事情是测试两个浮点数是否相等 浮动在一定的公差范围内彼此靠近。例如,

def near(a, b, rtol=1e-5, atol=1e-8):
    # Essentially borrowed from NumPy 
    return abs(a - b) < (atol + rtol * abs(b))

def parallel(l1,l2):
    if near(gradient(l1), gradient(l2)): ...

PEP8 style guide says

切勿使用字符“l”(小写字母 el)、“O”(大写字母) 字母 oh),或 'I'(大写字母眼睛)作为单个字符变量 名字。

在某些字体中,这些字符与 数字一和零。

所以我建议使用line1 而不是l1


现在,正如@george 指出的那样,代码处理的地方有很多 垂直线作为特例 (if gradient is None.) 如果我们使用 线的参数形式,我们可以以相同的方式处理 all 线。编码 会更简单,因为数学会更简单。

如果你知道一条线上的两个点,(x1, y1)(x2, y2), 那么线的参数形式是

l(t) = (x1, y1)*(1-t) + (x2, y2)*t

其中t 是一个标量。随着t 的变化,您将获得不同的分数。请注意有关参数形式的一些相关事实:

  • t = 1 时,右侧的第一个 项丢失,所以你 留下(x2, y2)

  • t = 0 时,右侧的第二个 项丢失,所以你就剩下了 (x1, y1)*(1-0) = (x1, y1)

  • 等式的右边线性取决于t。那里 没有t**2 术语或任何其他对t 的非线性依赖。 所以参数形式描述了一条线


为什么线的参数形式强大?

  • 线段(x1,y1)到(x2,y2)内的点分别对应 t 介于 0 和 1(含)之间的值。 t 的所有其他值 对应线段外的点。

  • 还要注意,垂直线并没有什么特别之处 参数形式有关。你不必担心无限 连续下坡。 每一行都可以用同样的方式处理


我们如何利用这一事实?

如果我们有两条参数形式的线:

l1(t) = (x1, y1)*(1-t) + (x2, y2)*t

l2(s) = (u1, v1)*(1-s) + (u2, v2)*s

(将 x1, y1, x2, y2, u1, v1, u2, v2 视为给定的常数),那么当

l1(t) = l2(s)

现在,l1(t) 是一个二维点。 l1(t) = l2(s) 是一个二维方程。 x-坐标有一个方程,y-坐标有一个方程,内置于l1(t) = l2(s)。 所以我们真的有两个方程和两个未知数(ts)。 我们可以为ts 求解这些方程! (希望。如果线不相交,那么ts 没有解决方案。


所以让我们做一些数学运算:)

l1(t) = (x1, y1) + (x2-x1, y2-y1)*t
l2(s) = (u1, v1) + (u2-u1, v2-v1)*s

l1(t) = l2(s) 意味着两个标量方程:

x1 + (x2-x1)*t = u1 + (u2-u1)*s
y1 + (y2-y1)*t = v1 + (v2-v1)*s

(x2-x1)*t - (u2-u1)*s = u1-x1
(y2-y1)*t - (v2-v1)*s = v1-y1

我们可以将其重写为矩阵方程:

使用Cramer's Rule 我们可以求解ts:如果

然后

请注意,从数学角度来看,Cramer 规则是有效的(并且易于编码),但它具有 poor numerical properties(另请参见 GEPP vs Cramer's Rule)。对于严肃的应用程序,请使用 LU decomposition 或 LAPACK(可通过 NumPy 获得)。


所以我们可以这样编码:

def line_intersection(line1, line2):
    """
    Return the coordinates of a point of intersection given two lines.
    Return None if the lines are parallel, but non-collinear.
    Return an arbitrary point of intersection if the lines are collinear.

    Parameters:
    line1 and line2: lines given by 2 points (a 2-tuple of (x,y)-coords).
    """
    (x1,y1), (x2,y2) = line1
    (u1,v1), (u2,v2) = line2
    (a,b), (c,d) = (x2-x1, u1-u2), (y2-y1, v1-v2)
    e, f = u1-x1, v1-y1
    # Solve ((a,b), (c,d)) * (t,s) = (e,f)
    denom = float(a*d - b*c)
    if near(denom, 0):
        # parallel
        # If collinear, the equation is solvable with t = 0.
        # When t=0, s would have to equal e/b and f/d
        if near(float(e)/b, float(f)/d):
            # collinear
            px = x1
            py = y1
        else:
            return None
    else:
        t = (e*d - b*f)/denom
        # s = (a*f - e*c)/denom
        px = x1 + t*(x2-x1)
        py = y1 + t*(y2-y1)
    return px, py


def crosses(line1, line2):
    """
    Return True if line segment line1 intersects line segment line2 and 
    line1 and line2 are not parallel.
    """
    (x1,y1), (x2,y2) = line1
    (u1,v1), (u2,v2) = line2
    (a,b), (c,d) = (x2-x1, u1-u2), (y2-y1, v1-v2)
    e, f = u1-x1, v1-y1
    denom = float(a*d - b*c)
    if near(denom, 0):
        # parallel
        return False
    else:
        t = (e*d - b*f)/denom
        s = (a*f - e*c)/denom
        # When 0<=t<=1 and 0<=s<=1 the point of intersection occurs within the
        # line segments
        return 0<=t<=1 and 0<=s<=1

def near(a, b, rtol=1e-5, atol=1e-8):
    return abs(a - b) < (atol + rtol * abs(b))

line1 = ((4,4),(10,10)) 
line2 = ((11,5),(5,11))
line3 = ((11,5),(9,7))
line4 = ((4,0),(10,6)) 

assert all(near(a,b) for a,b in zip(line_intersection(line1,line2), (8.0, 8.0)))
assert all(near(a,b) for a,b in zip(line_intersection(line1,line3), (8.0, 8.0)))
assert all(near(a,b) for a,b in zip(line_intersection(line2,line3), (11, 5)))

assert line_intersection(line1, line4) == None # parallel, non-collinear
assert crosses(line1,line2) == True
assert crosses(line2,line3) == False    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-19
    • 1970-01-01
    • 2014-11-07
    • 2011-10-27
    • 1970-01-01
    • 2016-05-30
    • 1970-01-01
    • 2013-01-03
    相关资源
    最近更新 更多