docs: def save_graph_shapefile(G, filename='graph', folder=None, encoding='utf-8'):
      """
      Save graph nodes and edges as ESRI shapefiles to disk.
  
      Parameters
      ----------
      G : networkx multidigraph
      filename : string
          the name of the shapefiles (not including file extensions)
      folder : string
          the folder to contain the shapefiles, if None, use default data folder
      encoding : string
          the character encoding for the saved shapefiles
  
      Returns
      -------
      None
      """
  
      start_time = time.time()
      if folder is None:
          folder = settings.data_folder
  
      # convert directed graph G to an undirected graph for saving as a shapefile
      G_save = get_undirected(G.copy())
  
      # create a GeoDataFrame of the nodes and set CRS
      nodes, data = zip(*G_save.nodes(data=True))
      gdf_nodes = gpd.GeoDataFrame(list(data), index=nodes)
      gdf_nodes.crs = G_save.graph['crs']
  
      # create a geometry column then drop the x and y columns
      gdf_nodes['geometry'] = gdf_nodes.apply(lambda row: Point(row['x'], row['y']), axis=1)
      gdf_nodes = gdf_nodes.drop(['x', 'y'], axis=1)
  
      # make everything but geometry column a string
      for col in [c for c in gdf_nodes.columns if not c == 'geometry']:
          gdf_nodes[col] = gdf_nodes[col].fillna('').map(make_str)
  
      # create a list to hold our edges, then loop through each edge in the graph
      edges = []
      for u, v, key, data in G_save.edges(keys=True, data=True):
  
          # for each edge, add key and all attributes in data dict to the
          # edge_details
          edge_details = {'key':key}
          for attr_key in data:
              edge_details[attr_key] = data[attr_key]
  
          # if edge doesn't already have a geometry attribute, create one now
          if 'geometry' not in data:
              point_u = Point((G_save.nodes[u]['x'], G_save.nodes[u]['y']))
              point_v = Point((G_save.nodes[v]['x'], G_save.nodes[v]['y']))
              edge_details['geometry'] = LineString([point_u, point_v])
  
          edges.append(edge_details)
  
      # create a geodataframe from the list of edges and set the CRS
      gdf_edges = gpd.GeoDataFrame(edges)
      gdf_edges.crs = G_save.graph['crs']
  
      # make everything but geometry column a string
      for col in [c for c in gdf_edges.columns if not c == 'geometry']:
          gdf_edges[col] = gdf_edges[col].fillna('').map(make_str)
  
      # if the save folder does not already exist, create it with a filename
      # subfolder
      folder = os.path.join(folder, filename)
      if not os.path.exists(folder):
          os.makedirs(folder)
  
      # save the nodes and edges as separate ESRI shapefiles
      gdf_nodes.to_file('{}/nodes'.format(folder), encoding=encoding)
      gdf_edges.to_file('{}/edges'.format(folder), encoding=encoding)
      log('Saved graph "{}" to disk as shapefiles at "{}" in {:,.2f} seconds'.format(G_save.name, folder, time.time()-start_time))