[GIS] Fiona + Shapely: Loading a set of LineStrings and writing their centroids to a shapefile, including original properties

convertfionapandaspythonshapely

I am using Fiona to load a set of links. Then I put every record in the rows of a pandas.DataFrame. Then I get the centroid of each LineString using Shapely. Afterwards I copy the schema of the original links.shp and change its geometry to 'Point'. Then I write the records from the DataFrame, changing only the coordinates. Take a look:

import pandas as pd
import fiona
from shapely.geometry import LineString
def links_centroider(links, name_for_centroids):
    collection = fiona.open(links)
    records = []
    for i in range(len(collection)):
        records.append( next(collection))
    collection.close()
    geoDF = pd.DataFrame({'type': [i['geometry']['type'] for i in records],
                          'properties': [i['properties'] for i in records],
                          'coordinates': [i['geometry']['coordinates'] for i in records]},
                         index = [i['id'] for i in records])
    geoDF['centroid'] = geoDF.coordinates.apply(lambda x: LineString(x).centroid.coords[:][0])
    with fiona.open(links) as source:
        source_driver = source.driver
        source_crs = source.crs
        source_schema = source.schema
        source_schema['geometry'] = 'Point'
    with fiona.open(name_for_centroids,
                    'w',
                    driver=source_driver,
                    crs=source_crs,
                    schema=source_schema) as collection:
        print(len(collection))
        for i in geoDF.index:
            a_record = {'geometry':{'coordinates':geoDF.loc[i].centroid,
                                    'type': 'Point'},
                        'id': str(i),
                        'properties': geoDF.loc[i].properties,
                        'type': 'Feature'}
            collection.write(a_record)
        print(len(collection))
    print collection.closed

The output shapefile has no info for string properties. For numbers in those properties I get mostly zeros. In other words, values in the fields of the original attribute table of links.shp are not being written to the centroids.shp I want to get.

Any idea about how to solve this?

Best Answer

Then I put every record in the rows of a pandas.DataFrame

Why ?

If you only want to copy the original attributes (LineString) to the new shapefile (Points), after computing the centroid, you don't need Pandas:

import fiona
from shapely.geometry import shape, mapping 
with fiona.open("polyline.shp") as input:
    # change only the geometry of the schema: LineString -> Point
    input.schema['geometry'] = "Point"
    # write the Point shapefile
    with fiona.open('centroid.shp', 'w', 'ESRI Shapefile', input.schema.copy(), input.crs) as output:
       for elem in input:
           # GeoJSON to shapely geometry
           geom = shape(elem['geometry'])
           # shapely centroid to GeoJSON
           elem['geometry'] = mapping(geom.centroid)
           output.write(elem)

If you absolutely want to use Pandas, use GeoPandas which "mix" Pandas, Fiona and shapely.

import geopandas as gp
input = gp.read_file('polyline.shp')
print type(input)
<class 'geopandas.geodataframe.GeoDataFrame'> -> a GeoDataFrame
print input['geometry']
0  LINESTRING (266351.05107 161433.039507, 266362...  
....
# only change the geometry of the dataframe
input['geometry'] = input['geometry'].centroid
print input['geometry']
0    POINT (266369.1881962401 161457.6017265563)
....
# save resulting shapefile
input.to_file("centroids.shp")
Related Question