给定两行line_a和line_b,如何找到代表从line_a到line_b的较小路径的一对点?
答案 0 :(得分:2)
不幸的是,没有合理的操作来做到这一点。 当被要求扩展解决方案时,我正在考虑这个问题 问题find-coordinate-of-closest-point-on-polygon-shapely,处理两个多边形。答案取决于几何定理 直觉上是正确的(尽管正式证据需要一些微积分 在没有LaTex的情况下写这里有点长。 该定理告诉我们:
两个不相交的线串之间的最小距离 其他(作为一个连续的段序列的线串), 总是在线串的一个边缘点实现。
考虑到这一点,问题减少到计算最小距离 在LineString的每个边缘与另一个LineString之间。
另一种看待此问题的方法是减少它以计算最小值 每对段之间的距离。并注意到距离 在两个不相交的段之间,在两个端点之间实现, 或者在端点与该端点相对于另一端点的投影之间 分割。
代码已经过优化,以避免冗余计算,但是
也许你可以得到一个更优雅的版本。或者,如果您熟悉numpy
,则可能会获得使用numpy
向量距离和点积的较短版本。
注意,如果要处理多边形中的数千个点,则应优化此例程以避免计算边缘之间的所有距离。也许,在计算边缘到边缘距离时,您可以通过引入一些巧妙的过滤来丢弃边缘。
from shapely.geometry import LineString, Point
import math
def get_min_distance_pair_points(l1, l2):
"""Returns the minimum distance between two shapely LineStrings.
It also returns two points of the lines that have the minimum distance.
It assumes the lines do not intersect.
l2 interior point case:
>>> l1=LineString([(0,0), (1,1), (1,0)])
>>> l2=LineString([(0,1), (1,1.5)])
>>> get_min_distance_pair_points(l1, l2)
((1.0, 1.0), (0.8, 1.4), 0.4472135954999578)
change l2 slope to see the point changes accordingly:
>>> l2=LineString([(0,1), (1,2)])
>>> get_min_distance_pair_points(l1, l2)
((1.0, 1.0), (0.5, 1.5), 0.7071067811865476)
l1 interior point case:
>>> l2=LineString([(0.3,.1), (0.6,.1)])
>>> get_min_distance_pair_points(l1, l2)
((0.2, 0.2), (0.3, 0.1), 0.1414213562373095)
Both edges case:
>>> l2=LineString([(5,0), (6,3)])
>>> get_min_distance_pair_points(l1, l2)
((1.0, 0.0), (5.0, 0.0), 4.0)
Parallels case:
>>> l2=LineString([(0,5), (5,0)])
>>> get_min_distance_pair_points(l1, l2)
((1.0, 1.0), (2.5, 2.5), 2.1213203435596424)
Catch intersection with the assertion:
>>> l2=LineString([(0,1), (1,0.8)])
>>> get_min_distance_pair_points(l1, l2)
Traceback (most recent call last):
...
assert( not l1.intersects(l2))
AssertionError
"""
def distance(a, b):
return math.sqrt( (a[0]-b[0])**2 + (a[1]-b[1])**2 )
def get_proj_distance(apoint, segment):
'''
Checks if the ortogonal projection of the point is inside the segment.
If True, it returns the projected point and the distance, otherwise
returns None.
'''
a = ( float(apoint[0]), float(apoint[1]) )
b, c = segment
b = ( float(b[0]), float(b[1]) )
c = ( float(c[0]), float(c[1]) )
# t = <a-b, c-b>/|c-b|**2
# because p(a) = t*(c-b)+b is the ortogonal projection of vector a
# over the rectline that includes the points b and c.
t = (a[0]-b[0])*(c[0]-b[0]) + (a[1]-b[1])*(c[1]-b[1])
t = t / ( (c[0]-b[0])**2 + (c[1]-b[1])**2 )
# Only if t 0 <= t <= 1 the projection is in the interior of
# segment b-c, and it is the point that minimize the distance
# (by pitagoras theorem).
if 0 < t < 1:
pcoords = (t*(c[0]-b[0])+b[0], t*(c[1]-b[1])+b[1])
dmin = distance(a, pcoords)
return a, pcoords, dmin
elif t <= 0:
return a, b, distance(a, b)
elif 1 <= t:
return a, c, distance(a, c)
def get_min(items1, items2, distance_func, revert=False):
"Minimum of all distances (with points) achieved using distance_func."
a_min, b_min, d_min = None, None, None
for p in items1:
for s in items2:
a, b, d = distance_func(p, s)
if d_min == None or d < d_min:
a_min, b_min, d_min = a, b, d
if revert:
return b_min, a_min, d_min
return a_min, b_min, d_min
assert( not l1.intersects(l2))
l1p = list(l1.coords)
l2p = list(l2.coords)
l1s = zip(l1p, l1p[1:])
l2s = zip(l2p, l2p[1:])
edge1_min = get_min(l1p, l2s, get_proj_distance)
edge2_min = get_min(l2p, l1s, get_proj_distance, revert=True)
if edge1_min[2] <= edge2_min[2]:
return edge1_min
else:
return edge2_min
if __name__ == "__main__":
import doctest
doctest.testmod()