共线性有两个方面:线段之间的角度和接近度。您可以分别测试这些方面,并根据其中任何一个短路检查。我建议进行接近检查,完全不需要角度检查。下面的角度检查是出于遗留原因显示的,因为它可能对其他原因有用。
角度
交叉产品仅在 3D 中真正定义。点积在任何地方都有定义。两个向量之间的角度定义为它们的单位向量的点积的反余弦值。这适用于 2D 和 10D。
你可以从原点定义两个向量
a = np.array([segment_A_x2 - segment_A_x1,
segment_A_y2 - segment_A_y1])
b = np.array([segment_B_x2 - segment_B_x1,
segment_B_y2 - segment_B_y1])
a /= np.linalg.norm(a)
b /= np.linalg.norm(b)
angle = np.arccos(a.dot(b))
angle 将在-np.pi 和np.pi 之间运行。接近 pi 的角度表示反平行线,而接近零的角度表示平行线。您可以实现类似的测试
if abs(angle) < angular_threshold or abs(angle) > np.pi - angular_threshold:
...
如果您发现自己经常执行此计算,则可以通过完全跳过 arccos 来节省一些周期。当角度变为零或 pi 时,点积分别变为 +1 或 -1。这意味着您只需要预先计算一次dot_threshold = np.cos(angular_threshold):
if abs(a.dot(b)) >= dot_threshold:
接近度
要测试接近度,您需要定义距离的测量方式。对于完全平行的线,这是明确的:两条线必须在彼此的distance_threshold 范围内。
对于不完全平行的线,您可以做类似的事情:一个线段上的任何点都不能比另一线段的线的distance_threshold 更远。使用此定义,您可以将单独角度计算的需要直接吸收到计算中。
参考下图:
您必须对照其他行检查所有四个端点。如果您愿意,您可以根据距离的差异计算角度,并可能根据线段的长度将比例因子应用于阈值。
您也可以使用点积计算距离:
def dist(p, p1, p2):
s = p2 - p1
q = p1 + (p - p1).dot(s) / s.dot(s) * s
return np.linalg.norm(p - q)
a1 = np.array([segment_A_x1, segment_A_y1])
a2 = np.array([segment_A_x2, segment_A_y2])
b1 = np.array([segment_B_x1, segment_B_y1])
b2 = np.array([segment_B_x2, segment_B_y2])
dists = np.array([[dist(a1, b1, b2), dist(b1, a1, a2)],
[dist(a2, b1, b2), dist(b2, a1, a2)]])
我制作的实用程序库中提供了一个更强大的dist 版本,称为haggis。您可以使用haggis.math.segment_distance,如下所示:
dists = segment_distance([[a1, b1], [a2, b2]], # From point
[b1, a1], # Segment start
[b2, a2], # Segment end
axis=-1, # Axis containing vectors
segment=False) # Distance to entire line
输入一起广播,axis 适用于广播的形状,因此您无需重复两次端点。
最简单的测试版本是直接约束距离:
if (dists < distance_threshold).all():
...
您可以通过按线段长度缩放来计算角度。长 100 倍的线段可能会偏离其他线段的线 100 倍以上,但仍被认为是共线的。在这种情况下,您将distance_threshold 定义为一个unit 向量可以距另一个向量的线的最远距离。数字必须小于 1 才有意义:
scale = np.linalg.norm([a2 - a1, b2 - b1], axis=-1)
if (dists < distance_threshold * scale).all():
...
这个版本预设了我们在两个计算版本中对dists 施加的形状,因为scale 的元素数量与dists 的列数一样多。
也可以考虑线段之间的距离的更复杂的方案。这种接近度的定义留给读者作为练习。