提问人:ae_fed 提问时间:11/9/2023 最后编辑:ae_fed 更新时间:11/11/2023 访问量:114
如何检查所有给定点(在空间中)是否位于同一条线上?
How can I check if all given points (in space) lie on the same line?
问:
我需要实现一个函数,该函数将任意数量的点的坐标作为输入数据,并根据这些点是否位于同一条线上返回 True 或 False。
我使用 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
此方法基于一条线通过两点的方程:
这种方法的缺点是,如果要检查属于同一平面的点,则会引发错误(在这种情况下,三个坐标中的一个始终等于零,因此其中一个分母为零)。我需要更好的实现。先谢谢你!
答:
您可以使用以下方法:
-以参数化形式
在两个第一个点上画一条线 - 使用标量(点)积
找到该线上其他点的垂直投影长度 - 将投影长度与公差进行比较
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))
通过使代码更可靠,可以改进实现以处理三个坐标之一始终为零的情况。下面是函数的更新实现:
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
此更新的代码检查除以零,并使用容差来处理点属于同一平面的情况。它应该更强大,适用于更广泛的情况。
取你的任何一个坐标,把它作为你的新原点,相应地转换所有坐标。现在,将每个坐标视为位置向量。对每个向量进行归一化。现在,如果任何两个向量平行,则它们的点积为 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
同样,值得强调的是,这避免了除法、平方根或任何会引入浮点比较的东西,否则可能是整数坐标,它的速度更快,因为它只是乘法和加法,并且它没有围绕特定坐标类的奇怪边缘情况。
谢谢大家的回复!我想了一下,想出了一个使用向量叉积的实现。我们选择一个点作为原点(感谢 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
评论
ry
are_colinear = np.linalg.matrix_rank(points - points[0]) == 1
,使用此答案。如果您有浮点精度问题,可能想作为第二个参数。tolerance
matrix_rank