Update the keys of edges that share a u, v with another edge but differ in
    geometry. For example, two one-way streets from u to v that bow away from
    each other as separate streets, rather than opposite direction edges of a 
    single street.
    
    Parameters
    ----------
    G : networkx multidigraph

    Returns
    -------
    networkx multigraph

def update_edge_keys(G):
    """
    Update the keys of edges that share a u, v with another edge but differ in
    geometry. For example, two one-way streets from u to v that bow away from
    each other as separate streets, rather than opposite direction edges of a 
    single street.
    
    Parameters
    ----------
    G : networkx multidigraph

    Returns
    -------
    networkx multigraph
    """

    # identify all the edges that are duplicates based on a sorted combination
    # of their origin, destination, and key. that is, edge uv will match edge vu
    # as a duplicate, but only if they have the same key
    edges = graph_to_gdfs(G, nodes=False, fill_edge_geometry=False)
    edges['uvk'] = edges.apply(lambda row: '_'.join(sorted([str(row['u']), str(row['v'])]) + [str(row['key'])]), axis=1)
    edges['dupe'] = edges['uvk'].duplicated(keep=False)
    dupes = edges[edges['dupe']==True].dropna(subset=['geometry'])

    different_streets = []
    groups = dupes[['geometry', 'uvk', 'u', 'v', 'key', 'dupe']].groupby('uvk')
    
    # for each set of duplicate edges
    for label, group in groups:
        
        # if there are more than 2 edges here, make sure to compare all
        if len(group['geometry']) > 2:
            l = group['geometry'].tolist()
            l.append(l[0])
            geom_pairs = list(zip(l[:-1], l[1:]))
        # otherwise, just compare the first edge to the second edge
        else:
            geom_pairs = [(group['geometry'].iloc[0], group['geometry'].iloc[1])]
        
        # for each pair of edges to compare
        for geom1, geom2 in geom_pairs:
            # if they don't have the same geometry, flag them as different streets
            if not is_same_geometry(geom1, geom2):
                # add edge uvk, but not edge vuk, otherwise we'll iterate both their keys
                # and they'll still duplicate each other at the end of this process
                different_streets.append((group['u'].iloc[0], group['v'].iloc[0], group['key'].iloc[0]))

    # for each unique different street, iterate its key + 1 so it's unique
    for u, v, k in set(different_streets):
        # filter out key if it appears in data dict as we'll pass it explicitly
        attributes = {k:v for k, v in G[u][v][k].items() if k != 'key'}
        G.add_edge(u, v, key=k+1, **attributes)
        G.remove_edge(u, v, key=k)

    return G