我有一组与其他LineStrings相交的LineStrings,我想在这些交叉点将LineString拆分成单独的段。我有一个解决方案,但我不认为这是最好的方法。
假设我们正在处理一个LineString:
>>> import shapely
>>> from shapely.geometry import *
>>> import geopandas as gpd
>>>
>>> MyLine=LineString([(0,0),(5,0),(10,3)])
>>> MyLine
<shapely.geometry.linestring.LineString object at 0x1277EEB0>
>>>
与此LineString相交的2条线:
>>> IntersectionLines=gpd.GeoSeries([LineString([(2,1.5),(3,-.5)]), LineString([(5,.5),(7,.5)])])
>>> IntersectionLines
0 LINESTRING (2 1.5, 3 -0.5)
1 LINESTRING (5 0.5, 7 0.5)
dtype: object
>>>
我可以得到如下交点:
>>> IntPoints=MyLine.intersection(IntersectionLines.unary_union)
>>> IntPointCoords=[x.coords[:][0] for x in IntPoints]
>>> IntPointCoords
[(2.75, 0.0), (5.833333333333333, 0.5)]
>>>
然后我获取起点和终点,并创建这些和将用于形成线段的交点的对:
>>> StartPoint=MyLine.coords[0]
>>> EndPoint=MyLine.coords[-1]
>>> SplitCoords=[StartPoint]+IntPointCoords+[EndPoint]
>>>
>>> def pair(list):
... for i in range(1, len(list)):
... yield list[i-1], list[i]
...
>>>
>>> SplitSegments=[LineString(p) for p in pair(SplitCoords)]
>>> SplitSegments
[<shapely.geometry.linestring.LineString object at 0x127F7A90>, <shapely.geometry.linestring.LineString object at 0x127F7570>, <shapely.geometry.linestring.LineString object at 0x127F7930>]
>>>
>>> gpd.GeoSeries(SplitSegments)
0 LINESTRING (0 0, 2.75 0)
1 LINESTRING (2.75 0, 5.833333333333333 0.5)
2 LINESTRING (5.833333333333333 0.5, 10 3)
dtype: object
>>>
然而,我的方法有一个问题是我知道哪个交叉点应该与LineString起点连接,哪个交叉点应该与LineString端点配对。如果沿着该行以与开始和结束点相同的顺序列出交叉点,则该程序有效。我想有可能会出现这种情况并非总是如此?有没有办法概括这种方法还是完全有更好的方法?
答案 0 :(得分:4)
这是一种更通用的方法:计算所有点沿线的距离(线的起点和终点+要分割的点),按这些点排序,然后按正确的顺序生成线段。在一个功能中一起:
def cut_line_at_points(line, points):
# First coords of line (start + end)
coords = [line.coords[0], line.coords[-1]]
# Add the coords from the points
coords += [list(p.coords)[0] for p in points]
# Calculate the distance along the line for each point
dists = [line.project(Point(p)) for p in coords]
# sort the coords based on the distances
# see http://stackoverflow.com/questions/6618515/sorting-list-based-on-values-from-another-list
coords = [p for (d, p) in sorted(zip(dists, coords))]
# generate the Lines
lines = [LineString([coords[i], coords[i+1]]) for i in range(len(coords)-1)]
return lines
在您的示例中应用此功能:
In [13]: SplitSegments = cut_line_at_points(MyLine, IntPoints)
In [14]: gpd.GeoSeries(SplitSegments)
Out[14]:
0 LINESTRING (0 0, 2.75 0)
1 LINESTRING (2.75 0, 5.833333333333333 0.5)
2 LINESTRING (5.833333333333333 0.5, 10 3)
dtype: object
唯一的问题是这不会保留原始线的角落(但问题中的示例也没有这样做,所以我不知道这是否是一个要求。这可能会有点但是有点更复杂)
更新保持原始线条角落不变的版本(我的方法是保留一个0/1列表,指示是否要拆分坐标):
def cut_line_at_points(line, points):
# First coords of line
coords = list(line.coords)
# Keep list coords where to cut (cuts = 1)
cuts = [0] * len(coords)
cuts[0] = 1
cuts[-1] = 1
# Add the coords from the points
coords += [list(p.coords)[0] for p in points]
cuts += [1] * len(points)
# Calculate the distance along the line for each point
dists = [line.project(Point(p)) for p in coords]
# sort the coords/cuts based on the distances
# see http://stackoverflow.com/questions/6618515/sorting-list-based-on-values-from-another-list
coords = [p for (d, p) in sorted(zip(dists, coords))]
cuts = [p for (d, p) in sorted(zip(dists, cuts))]
# generate the Lines
#lines = [LineString([coords[i], coords[i+1]]) for i in range(len(coords)-1)]
lines = []
for i in range(len(coords)-1):
if cuts[i] == 1:
# find next element in cuts == 1 starting from index i + 1
j = cuts.index(1, i + 1)
lines.append(LineString(coords[i:j+1]))
return lines
应用于示例:
In [3]: SplitSegments = cut_line_at_points(MyLine, IntPoints)
In [4]: gpd.GeoSeries(SplitSegments)
Out[4]:
0 LINESTRING (0 0, 2.75 0)
1 LINESTRING (2.75 0, 5 0, 5.833333333333333 0.5)
2 LINESTRING (5.833333333333333 0.5, 10 3)
dtype: object
答案 1 :(得分:1)
这是我尝试通过joris调整函数,以便包含线段的角落。这还不能完美地运行,因为除了包括包含角落的合并段之外,它还包括原始的未合并段。
def cut_line_at_points(line, points):
#make the coordinate list all of the coords that define the line
coords=line.coords[:]
coords += [list(p.coords)[0] for p in points]
dists = [line.project(Point(p)) for p in coords]
coords = [p for (d, p) in sorted(zip(dists, coords))]
lines = [LineString([coords[i], coords[i+1]]) for i in range(len(coords)-1)]
#Now go through the lines and merge together as one segment if there is no point interrupting it
CleanedLines=[]
for i,line in enumerate(lines):
if i<>len(lines)-1:
LinePair=[line,lines[i+1]]
IntPoint= LinePair[0].intersection(LinePair[1])
if IntPoint not in points:
CleanedLine=shapely.ops.linemerge(LinePair)
else:
CleanedLine=line
else:
CleanedLine=line
CleanedLines.append(CleanedLine)
return CleanedLines
>>> SplitSegments = cut_line_at_points(MyLine, IntPoints)
>>> gpd.GeoSeries(SplitSegments)
0 LINESTRING (0 0, 2.75 0)
1 LINESTRING (2.75 0, 5 0, 5.833333333333333 0.5)
2 LINESTRING (5 0, 5.833333333333333 0.5)
3 LINESTRING (5.833333333333333 0.5, 10 3)
dtype: object
>>>
答案 2 :(得分:1)
我喜欢joris的做法。不幸的是,在尝试使用它时遇到了一个关键的困难:如果线串在同一坐标处有两个点,则它们的投影是不明确的。两者都将得到相同的投影值并一起排序。
如果您的路径在同一点开始和结束,则这一点尤为明显。结束点获得0的投影并在开始时进行排序,这会抛出整个算法,因为它期望最后的“削减”值为“1”。
这是一个形状合理的解决方案1.6.1:
import shapely.ops
from shapely.geometry import MultiPoint
def cut_linestring_at_points(linestring, points):
return shapely.ops.split(linestring, MultiPoint(points))
是的,真的很简单。这里的问题是点必须正好在线上。如果不是,请将其按照this answer中的行进行捕捉。
返回值为MultiLineString
,您可以使用LineString
方法获取组件geoms
。
答案 3 :(得分:0)
@joris 的方法非常好,但是如果您尝试向它传递点列表,它会出错,其中一些点实际上并未与线相交,在我的情况下,这是因为我预先计算了一个交叉点列表多行列表。
在继续执行该函数之前,我能够通过将输入点列表预过滤为仅实际相交的点来修复它。对于大量的点列表,它不会有效率,但就我而言,我的列表总是很小,所以对我来说已经足够了。如果没有与线相交的点,它也可以工作,并且在这种情况下只会短路返回原始线作为列表(为了一致性)
我最初使用 line.intersects(point)
但它总是返回 False,可能是由于插值精度。
def cut_line_at_points(line, points):
# Filter out any points that are not on the line
# 0.01 is arbitrary, make it smaller for more precision
points = [point for point in points if line.distance(point) < 0.01]
if not points:
return [line]
# First coords of line
coords = list(line.coords)
# Keep list coords where to cut (cuts = 1)
cuts = [0] * len(coords)
cuts[0] = 1
cuts[-1] = 1
# Add the coords from the points
coords += [list(p.coords)[0] for p in points]
cuts += [1] * len(points)
# Calculate the distance along the line for each point
dists = [line.project(Point(p)) for p in coords]
# sort the coords/cuts based on the distances
# see http://stackoverflow.com/questions/6618515/sorting-list-based-on-values-from-another-list
coords = [p for (d, p) in sorted(zip(dists, coords))]
cuts = [p for (d, p) in sorted(zip(dists, cuts))]
# generate the Lines
# lines = [LineString([coords[i], coords[i+1]]) for i in range(len(coords)-1)]
lines = []
for i in range(len(coords) - 1):
if cuts[i] == 1:
# find next element in cuts == 1 starting from index i + 1
j = cuts.index(1, i + 1)
lines.append(LineString(coords[i:j + 1]))
return lines