2

If I have a MultiLineString representing a branched network with a common starting point (red network in pic below):

enter image description here

Does anyone know how to convert this to a set of individual single-line strings in Shapely, with each individual LineString starting at the common starting point and ending at each specific endpoint? I assume I have a Shapely MultiLine object as a starting point.

1 Answer 1

2

You can create a networkx Graph and find all paths from the start node to all end nodes:

from shapely.wkt import loads
from shapely.ops import unary_union
import geopandas as gpd
import networkx as nx

#Create a dataframe with one multiline in it
wkt = "MULTILINESTRING((470251.15 7163554.497, 470210.75 7163578.507, 470096.809 7163595.932, 470066.473 7163580.399, 470065.627 7163545.341, 470049.173 7163516.967, 470036.995 7163509.509, 470033.228 7163534.687, 470006.878 7163542.973, 470000 7163555.5302, 469995.948 7163562.928, 469931.863 7163603.969, 469918.5168 7163593.7073, 469878.87 7163550.24, 469846.241 7163553.948, 469845.88 7163545.309, 469860.68 7163533.453, 469858.623 7163526.065, 469825.844 7163520.866, 469788.862 7163539.181, 469758.275 7163539.329, 469731.298 7163525.963, 469680.905 7163542.735, 469670.694 7163552.39, 469656.454 7163585.698, 469644.025 7163566.484, 469565.2947 7163539.8986, 469499.805 7163573.18, 469434.6372 7163590.821, 469409.7 7163593.886, 469380.74 7163567.463, 469354.179 7163570.061, 469302.72 7163558.266, 469239.19 7163594.305, 469193.9407 7163610.1917, 469173.2456 7163626.3981, 469147.578 7163631.776, 469148.712 7163665.877, 469141.04 7163676.337, 469128.834 7163670.567, 469120.15 7163643.633, 469105.925 7163645.178, 469072.683 7163672.805, 469047.671 7163728.537, 469033.0524 7163733.8213), (469033.0524 7163733.8213, 469000.1685 7163657.3838, 468986.413 7163644.847, 468961.897 7163639.569, 468924.599 7163683.581, 468868.474 7163664.961, 468806.17 7163698.771, 468751.226 7163679.908, 468720.761 7163709.98, 468692.3776 7163719.5052, 468675.556 7163736.903, 468661.841 7163735.226, 468652.888 7163723.963, 468640.417 7163720.384, 468626.605 7163742.798, 468569.271 7163743.693, 468563.078 7163769.272, 468550.287 7163780.7371), (468550.287 7163780.7371, 468515.8427 7163790.2658, 468490.646 7163782.185, 468454.94 7163719.141, 468415.972 7163692.071, 468348.166 7163687.504, 468287.267 7163674.762, 468226.429 7163685.147, 468201.128 7163680.652, 468180.669 7163667.747, 468152.067 7163625.775, 468109.221 7163596.903, 468020.948 7163595.161, 467974.317 7163548.965, 467949.569 7163539.858, 467899.439 7163553.805, 467860.701 7163545.646, 467849.522 7163555.965, 467839.132 7163585.045, 467747.325 7163593.049, 467737.1405 7163604.7149, 467699.644 7163677.084, 467672.401 7163697.183, 467586.079 7163661.862), (468550.287 7163780.7371, 468546.755 7163794.266), (468546.755 7163794.266, 468517.1254 7163812.9619, 468481.1088 7163825.4923, 468421.264 7163832.865, 468376.2785 7163823.3142, 468284.2254 7163861.4415, 468208.42 7163868.012, 468155.193 7163890.949, 468136.17 7163890.395, 468105.758 7163874.767, 468073.352 7163868.982, 468045.7986 7163886.8522, 467986.71 7163912.972, 467953.107 7163907.861, 467926.308 7163895.89, 467901.165 7163903.234, 467861.801 7163963.673, 467828.672 7163980.077, 467792.139 7164008.702, 467746.989 7164065.784, 467703.417 7164080.877, 467668.972 7164106.741, 467642.05 7164113.532, 467572.772 7164156.106, 467449.869 7164195.211, 467443.2657 7164211.2733, 467464.556 7164297.638, 467523.043 7164389.619), (469033.0524 7163733.8213, 469031.74 7163782.309, 469005.534 7163825.215, 468997.5735 7163851.7218, 468987.658 7163969.509, 469041.51 7164086.829, 469041.1054 7164153.5724, 469050.132 7164238.615, 469046.4168 7164274.4355, 469071.0739 7164363.9428, 469072.5112 7164407.0619, 469083.06 7164462.417, 469116.732 7164494.933, 469144.226 7164560.911, 469180.52 7164599.142, 469195.0198 7164643.3378, 469251.4104 7164714.6386, 469286.967 7164779.634, 469333.392 7164792.081, 469366.942 7164813.493, 469394.3057 7164869.8749, 469421.161 7164909.62, 469503.348 7164937.393, 469625.431 7165015.63, 469707.1951 7165043.9859, 469740.434 7165042.621, 469802.207 7165012.679, 469829.088 7165011.077, 469854.836 7165019.326, 469930.992 7164981.081, 469964.336 7164970.946, 470000 7164946.6135, 470033.7733 7164923.571, 470075.065 7164911.524, 470109.326 7164917.689), (468546.755 7163794.266, 468541.953 7163807.426, 468520.318 7163828.265, 468512.755 7163874.216, 468489.599 7163918.801, 468478.582 7163959.354, 468458.221 7163981.901, 468450.337 7164039.065, 468430.402 7164065.146, 468429.849 7164135.365, 468405.933 7164172.387, 468368.292 7164281.876, 468330.3929 7164341.2059, 468265.875 7164400.088, 468260.1 7164423.354, 468257.501 7164536.257, 468211.103 7164608.681, 468207.7318 7164741.3695, 468218.111 7164769.197, 468249.988 7164810.247, 468255.468 7164827.557, 468245.556 7164905.491, 468256.421 7164933.226, 468251.18 7164961.243, 468260.456 7165010.73, 468253.103 7165023.62, 468229.413 7165029.529, 468226.698 7165039.751, 468269.8064 7165141.8549, 468257.141 7165160.625, 468236.969 7165168.73, 468238.788 7165201.318, 468297.313 7165240.055, 468351.902 7165265.611, 468425.312 7165256.076, 468456.613 7165266.074, 468517.139 7165303.794, 468547.449 7165300.63, 468569.493 7165304.9789, 468665.436 7165361.571, 468704.439 7165405.194, 468764.021 7165433.086, 468801.2557 7165477.3811, 468870.569 7165528.544, 468978.09 7165576.3042, 469047.8143 7165581.0337, 469071.2812 7165588.0105, 469082.932 7165598.688))"
multiline = loads(wkt)
df = gpd.GeoDataFrame(geometry=[multiline], crs=3006)

df = df.explode() #Explode the multiline to single part lines; from 1 row to 7

#Create columns for each lines start and end points
df["b"] = df.boundary
df["start_point"] = df.apply(lambda x: x.b.geoms[0], axis=1)
df["end_point"] = df.apply(lambda x: x.b.geoms[-1], axis=1)

#Create a graph using the start and end point as edges and the line geometry as an attribute
G = nx.DiGraph()
_ = df.apply(lambda x: G.add_edge(x.start_point, x.end_point, geom=x.geometry), axis=1)

#For each end node, find the path from the start to the end,
#  extract the edge attribute; the linestring, union the line segments into one
end_nodes = [n for n in G.nodes() if G.out_degree(n)==0]
start_node = [n for n in G.nodes() if G.in_degree(n)==0][0]
path_lines = []
for end_node in end_nodes: #For each end node
    path = list(nx.all_simple_paths(G, source=start_node, target=end_node))[0] #A list of nodes 
    #  ie. the start and end points: [<POINT (470251.15 7163554.497)>, <POINT (469033.052 7163733.821)>,...,]
    
    lines = [] #A list to hold line segments connecting the nodes in the path
    
    for node1, node2 in zip(path, path[1:]): #Extract the edge attribute (the line geometry) using the node points
        the_line = G.get_edge_data(node1, node2)["geom"]
        lines.append(the_line)
    
    path_lines.append(unary_union(lines)) #Union / dissolve the lines into one multiline
    
path_df = gpd.GeoDataFrame(geometry=path_lines, crs=df.crs)
path_df.to_file(r"C:\gistest\all_paths.shp")

The multiline starts at bottom right: enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.