Search code examples
pythongeopandasosmnx

How to order LineString geometries sequentially in returned OSMNX edges GeoDataFrame


I have a geodataframe of edges that are returned from OSMNX:

import osmnx as ox

# Getting graph for specific road
cf = '["highway"!~"motorway"]["ref"~"TF-563"]'
G = ox.graph_from_place('Tenerife, Spain', network_type='walk', simplify=False, custom_filter=cf)

# Adding length weights
G = ox.distance.add_edge_lengths(G)

# Getting nodes and edges of graph
nodes, edges = ox.convert.graph_to_gdfs(G, nodes=True)

This gives a geometry column where each row contains a Linestring. What I want is to have a one long Linestring that is ordered correctly, so I could make a plot that resembles ``` edges.plot()`` where all the linestrings plot contiguously.`

I use the following to create a multilinestring:

multi_line = geometry.MultiLineString(list(edges['geometry']))

But when I plot it things look a mess, which I assume is because the order of the linestrings in the edges geodataframe are not ordered and therefore the linestrings are connecting randomly.

    m = folium.Map(location=[28.105040, -16.610664], zoom_start=15)
    
    # Extract coordinates from MultiLineString
    def get_coordinates(multi_line):
        all_coords = []
        for line in multi_line.geoms:
            all_coords.extend(list(line.coords))
        return all_coords
    
    # Get the coordinates
    coordinates = get_coordinates(multi_line)
    
    # Add the coordinates as PolyLines 
    folium.PolyLine([(coord[1], coord[0]) for coord in coordinates], color="blue").add_to(m)
    m

Example of randomly connected multilinestring

Does anyone know if there's a way to order the geometry of edges returned by OSMNX, or is there another reason why I'm seeing this issue?


Solution

  • Unless you really need one single PolyLine, one option would be to reindex the edges :

    extremities = [n for n, d in G.in_degree() if d == 1] # must be exactly 2 !
    route = [(u,v) for u,v,*_ in list(nx.all_simple_edge_paths(G, *extremities))[0]]
    
    # this requires geopandas>=1.0.0
    single_ls = (
        edges.droplevel(2).reindex(route)
        .dissolve().geometry.line_merge().squeeze()
    )
    # otherwise, use from shapely.ops import linemerge
    # single_ls = (
    #     linemerge(
    #         edges.droplevel(2).reindex(route)
    #         .dissolve().geometry.squeeze()
    #     )
    # )
    
    m = folium.Map(location=[28.105040, -16.610664], zoom_start=15)
    
    folium.PolyLine(
        [(coord[1], coord[0]) for coord in single_ls.coords],
        color="blue",
    ).add_to(m)
    

    Otherwise, you can simply explore both nodes/edges:

    m = edges.explore(
        style_kwds=dict(weight=5, color="black"),
        location=[28.11114, -16.61616],
        zoom_start=16,
    )
    m = nodes.explore(
        m=m,
        marker_type="circle_marker",
        marker_kwds=dict(radius=5),
        style_kwds=dict(fillColor="cyan", color="black"),
    )
    

    Output (m) :

    enter image description here