如何检查所有给定点(在空间中)是否位于同一条线上?

How can I check if all given points (in space) lie on the same line?

提问人:ae_fed 提问时间:11/9/2023 最后编辑:ae_fed 更新时间:11/11/2023 访问量:114

问:

我需要实现一个函数,该函数将任意数量的点的坐标作为输入数据,并根据这些点是否位于同一条线上返回 TrueFalse

我使用 Python 来解决这个问题,现在我有以下实现:

def are_colinear(points, tolerance): # variable "points" is a list of lists with points coordinates
    for i in range(2, len(points)):
        if (points[i][0] - points[i-2][0])/(points[i-1][0] - points[i-2][0]) - (points[i][1] - points[i-2][1])/(points[i-1][1] - points[i-2][1]) < tolerance and \
           (points[i][1] - points[i-2][1])/(points[i-1][1] - points[i-2][1]) - (points[i][2] - points[i-2][2])/(points[i-1][2] - points[i-2][2]) < tolerance:
            continue
        else:
            return False
    return True

此方法基于一条线通过两点的方程:

enter image description here

这种方法的缺点是,如果要检查属于同一平面的点,则会引发错误(在这种情况下,三个坐标中的一个始终等于零,因此其中一个分母为零)。我需要更好的实现。先谢谢你!

Python 数组算法 几何

评论

5赞 n. m. could be an AI 11/9/2023
如果 a/b == c/d,则 ad == bc。无论如何,比较浮点数是否相等并不是一个好主意。
1赞 Luatic 11/9/2023
以一个点为原点,然后确定其他点的相对位置(rx、ry)。计算“斜率”rx / ry,检查所有这些斜率是否相等(您可以使用 Python 分数来精确地做到这一点 - 当您比较分数时,引擎盖下会发生什么大致是 n.m. 概述的);如果 Any 为零,请检查所有点是否具有相同的 Y 坐标。ry
0赞 ae_fed 11/9/2023
@n.m.couldbeanAI,谢谢!
1赞 pjs 11/9/2023
您可以通过线性回归模型对点进行调整,并查看残差方差是否小于容差。这将推广到任何数量的维度。
1赞 Amadan 11/9/2023
are_colinear = np.linalg.matrix_rank(points - points[0]) == 1,使用此答案。如果您有浮点精度问题,可能想作为第二个参数。tolerancematrix_rank

答:

1赞 MBo 11/9/2023 #1

您可以使用以下方法:
-以参数化形式
在两个第一个点上画一条线 - 使用标量(点)积
找到该线上其他点的垂直投影长度 - 将投影长度与公差进行比较

def are_colinear(points, tolerance):
    dx01 = points[1][0]-points[0][0]
    dy01 = points[1][1]-points[0][1]
    dz01 = points[1][2]-points[0][2]
    mult = 1.0 / (dx01*dx01+dy01*dy01+dz01*dz01)
    for i in range(2, len(points)):
        cdx = points[i][0] - points[0][0]
        cdy = points[i][1] - points[0][1]
        cdz = points[i][2] - points[0][2]
        t = (cdx*dx01+cdy*dy01+cdz*dz01) * mult
        perplen = math.sqrt((t*dx01-cdx)**2+ (t*dy01-cdy)**2 +(t*dz01-cdz)**2)
        if perplen > tolerance:
            return False
    return True

 print(are_colinear([[0,0,0],[2,2,2],[1,0.9999999,1.0000001]], 1e-6))
 print(are_colinear([[0,0,0],[2,2,2],[1,0.9999,1.00001]], 1e-6))
2赞 Mohd Rahman 11/9/2023 #2

通过使代码更可靠,可以改进实现以处理三个坐标之一始终为零的情况。下面是函数的更新实现:

def are_colinear(points, tolerance):
    if len(points) < 3:
        return True

    for i in range(2, len(points)):
        x1, y1, z1 = points[i - 2]
        x2, y2, z2 = points[i - 1]
        x3, y3, z3 = points[i]

        # Check for division by zero and avoid it by using a tolerance
        if abs((x1 - x2) * (y2 - y3) - (x2 - x3) * (y1 - y2)) > tolerance or \
           abs((x1 - x2) * (z2 - z3) - (x2 - x3) * (z1 - z2)) > tolerance or \
           abs((y1 - y2) * (z2 - z3) - (y2 - y3) * (z1 - z2)) > tolerance:
            return False

    return True

此更新的代码检查除以零,并使用容差来处理点属于同一平面的情况。它应该更强大,适用于更广泛的情况。

3赞 Dillon Davis 11/9/2023 #3

取你的任何一个坐标,把它作为你的新原点,相应地转换所有坐标。现在,将每个坐标视为位置向量。对每个向量进行归一化。现在,如果任何两个向量平行,则它们的点积为 1。事实上,它们是相同的向量。如果两个向量是反平行的,则它们的点积为 -1,一个向量是另一个向量的否定。

当你把它全部展开时,你会发现你不需要做任何除法,可以避免任何平方根,也没有特殊的边缘情况需要处理

1 ?= abs(dot(norm(u), norm(v)) 
   = abs(dot(u, v) / (mag(u) * mag(v)))
   = dot(u, v)^2 / (mag(u) * mag(v))^2

1 = (ux*vx + uy*vy + uz*vz)^2 / (sqrt(ux^2 + uv^2 + uz^2) * sqrt(vx^2 + vy^2 + vz^2))^2
1 = (ux*vx + uy*vy + uz*vz)^2 / ((ux^2 + uv^2 + uz^2) * (vx^2 + vy^2 + vz^2))

(ux^2 + uv^2 + uz^2) * (vx^2 + vy^2 + vz^2) = (ux*vx + uy*vy + uz*vz)^2

这很容易编码:

def are_parallel(points):
    points = set(points)  # dedupe
    if len(points) < 3:
        return True

    xo, yo, zo = points.pop()  # extract origin

    translated_points = [(x-xo, y-yo, z-zo) for x, y, z in points]
    ux, uy, uz = translated_points[0]  # first/reference vector
    u_mag_sqr = ux**2 + uy**2 + uz**2

    for vx, vy, vz in translated_points[1:]:  # rest of vectors
        v_mag_sqr = vx**2 + vy**2 + vz**2
        uv_dot_sqr = (ux*vx + uy*vy + uz*vz)**2
        if u_mag_sqr * v_mag_sqr != uv_dot_sqr:
            return False
    return True

同样,值得强调的是,这避免了除法、平方根或任何会引入浮点比较的东西,否则可能是整数坐标,它的速度更快,因为它只是乘法和加法,并且它没有围绕特定坐标类的奇怪边缘情况。

0赞 ae_fed 11/10/2023 #4

谢谢大家的回复!我想了一下,想出了一个使用向量叉积的实现。我们选择一个点作为原点(感谢 Dillon Davis 的这个想法)并计算其余点的相对坐标,这些点同时是来自原点的向量坐标。如果所有这些向量都位于同一条线上,则任何一对向量的叉积必须等于零向量。编写一个循环来检查这个条件,我们得到以下函数:

def are_colinear(points, tolerance):
if len(points) < 3:
    return True

x0, y0, z0 = points.pop(0) # the origin
translated_points = [(x - x0, y - y0, z - z0) for x, y, z in points]

for i in range(len(translated_points) - 1):
    x1, y1, z1 = translated_points[i]
    x2, y2, z2 = translated_points[i + 1]

    xy = (y1*z2 - z1*y2, z1*x2 - x1*z2, x1*y2 - y1*x2) # the cross product

    len_xy = (xy[0]**2 + xy[1]**2 + xy[2]**2)**0.5

    if 0 <= abs(len_xy) <= tolerance:
        continue
    else:
        return False
return True