7

There are lots of MultiLineString features in a polyline shapefile. The problem is to split these lines into separate individual lines. There is a similar question with PostGIS, but I need to code in Python.

I have been planning to extract the geometry from each MultiLineString feature and create separate features and append a shpfile with those new features. Later, in the second pass, I would delete the MultiLineString object which is no more required as it is now split into its components. It will require reading data from original shpfile and appending the newly created features to a copy of the shpfile. Then the appended (copy) shpfile needs to be rid of the MultiLineString features. It seems cumbersome as I find no way to read and write the shapefile simultaneously.

There should be better ideas to do that. How to split these lines into separate individual lines using Python with OGR, Shapely, Fiona or Pyshp? A snippet of code to give an idea is below.

import fiona
from shapely.geometry import LineString

original_shpfile = fiona.open(path_to_road_network_shapefile)
with fiona.open(path_to_road_network_shapefile, 'a') as copy_shpfile:
    rec = original_shpfile.next()
    while rec:
        try:
            vertices_on_link = rec['geometry']['coordinates']
            LineString(vertices_on_link) #reconstruct the line from vertices
            #if multipart, 
            #has multiple lists within the list, 
            #one for each of the Linestring component. also throws AssertionError

        except AssertionError:
            number_of_parts = len(vertices_on_link) 
            for x in range(0,number_of_parts):
                part = rec
                part['geometry']['coordinates'] = vertices_on_link[x]
                copy_shpfile.write(part)
        rec = original_shpfile.next()

#in the next phase, delete each of the the MultiLineString features from the copy of the shapefile

4 Answers 4

14

A MultiLineString is a list of lines:

from shapely.geometry import  MultiLineString, mapping, shape
coords = [((0, 0), (1, 1)), ((-1, 0), (1, 0))]
lines = MultiLineString(coords)
print lines
MULTILINESTRING ((0 0, 1 1), (-1 0, 1 0))
for line in lines:
     print line
LINESTRING (0 0, 1 1)
LINESTRING (-1 0, 1 0)

# convert to GeoJSON format:
print mapping(lines)
{'type': 'MultiLineString', 'coordinates': (((0.0, 0.0), (1.0, 1.0)), ((-1.0, 0.0), (1.0, 0.0)))}
# convert from GeoJSON to shapely
print shape(mapping(lines))
MULTILINESTRING ((0 0, 1 1), (-1 0, 1 0))

It is the same when you read a shapefile with Fiona:

import fiona
from shapely.geometry import shape
with fiona.open(path_to_road_network_shapefile) as copy_shpfile:
    for feature in copy_shpfile:
        geom = feature['geometry'])
        # geom in GeoJSON format -> {'type': 'MultiLineString', 'coordinates': (((0.0, 0.0), (1.0, 1.0)), ((-1.0, 0.0), (1.0, 0.0)))}
           if geom['type'] == 'MultiLineString':
               # convert to shapely geometry
               shapely_geom = shape(geom) # = MULTILINESTRING ((0 0, 1 1), (-1 0, 1 0))
               for lines in shapely_geom:
                   print lines 

New

A shapefile has no MultiLineString schema geometries: a shapefile that indicates LineString in its schema may yield either LineString or MultiLineString features (simple list of LineStrings)

However, it is the same algorithm:

# open the original shapefile
with fiona.open('multiline.shp') as source:
     # create the new shapefile
     with fiona.open('lines.shp','w', driver='ESRI Shapefile',crs=source.crs,schema=source.schema) as ouput:
         # iterate the features of the original shapefile
         for elem in source:
               # iterate the list of geometries in one element (split the MultiLineString)
               for line in shape(elem['geometry']): 
                    # write the line to the new shapefile
                    ouput.write({'geometry':mapping(line),'properties':elem['properties']})
3
  • Pardon if the question was too elaborate. For your example, there are two parts in the multilinestring. Actually I need to get two separate line features in the shapefile instead of the single multilinestring feature. Could you enlighten more on that?
    – Asif Rehan
    Jul 7, 2014 at 8:17
  • look a solution above
    – gene
    Jul 7, 2014 at 11:51
  • A linestring A may touch the start or end vertex of another linestring B, in this case, A should be further split into multiple segment. Not sure whether existing method consider this.
    – Chang
    Apr 23, 2021 at 7:18
3

This should go as a comment. But is too long. Thanks much Gene!

There are both LineString and MultiLineString features. So this code

for line in shape(elem['geometry']) 

shows TypeError: 'LineString' object is not iterable when feature is MultiLineString. So the final code that works is as below.

with fiona.open(source_path) as source:
    with fiona.open(destination,'w', driver='ESRI Shapefile',
                crs=source.crs,schema=source.schema) as ouput:
    for elem in source:
        reconstruct = shape(elem['geometry']) 
        if elem['geometry']['type'] == 'MultiLineString':
            for line in reconstruct: 
                ouput.write({'geometry':mapping(line),'properties':elem['properties']})
        elif elem['geometry']['type'] == 'LineString':
            ouput.write({'geometry':mapping(reconstruct),'properties':elem['properties']})
3

Faster way:

ogr2ogr -f "ESRI Shapefile" -nlt LINESTRING -explodecollections \
output.shp input.shp

The trick is in the parameter -explodecollections. I also recommend adding the option -lco ENCODING=UTF-8 for the output so the full command would be:

ogr2ogr -f "ESRI Shapefile" -nlt LINESTRING -explodecollections \
-lco ENCODING=UTF-8 output.shp input.shp
1

Shapely's MultiLineString provides a .geoms attribute that provides a list of LineString.

>>> from shapely.geometry import MultiLineString
>>> coords = [((0, 0), (1, 1)), ((-1, 0), (1, 0))]
>>> lines = MultiLineString(coords)
>>> len(lines.geoms)
2

Your Answer

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

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