toruseo/UXsim

Create network from OSMnx graph

Closed this issue · 9 comments

I was curious if it's possible to import a road network from OSMnx.

For the links, the start_node, end_node, length, free_flow_speed could all be determined from OSMnx itself. Then, you could add a function to infer jam_density based on the number of lanes, maximum speed and road type.

For the nodes x and y could be determined from the OSMnx graph, while the signal could be inferred from the intersection type (or left to default).

Thanks for suggestion!

Theoretically, it is possible. Actually I have tried once. But unfortunately raw outputs of OSMnx have a lot of short links which cause some difficulty in simulation (e.g., they tend to cause gridlocks). So I had to do postprocessing to make them suitable for simulation. In fact, Tokyo data (https://github.com/toruseo/UXsim/blob/main/dat/tokyo_arterial_nodes.csv, https://github.com/toruseo/UXsim/blob/main/dat/tokyo_arterial_links.csv) had been generated in this manner.

The postprocessing method needs to be sophisticated. I will try it later.

Thanks for getting back! I got it working last night. It looks something like this:

# Create a graph from a place name
place_name = "Delft, Netherlands"
custom_filter = (
    '["highway"~"motorway|motorway_link|trunk|trunk_link|primary|primary_link|'
    'secondary|secondary_link|tertiary|tertiary_link"]'
)
original_graph = ox.graph_from_place(place_name, network_type="drive", simplify=False, custom_filter=custom_filter)
place_name = "Delft, Netherlands"
custom_filter = (
    '["highway"~"motorway|motorway_link|trunk|trunk_link|primary|primary_link|'
    'secondary|secondary_link|tertiary|tertiary_link"]'
)
original_graph = ox.graph_from_place(place_name, network_type="drive", simplify=False, custom_filter=custom_filter)

# Add speed limits and travel times to the edges
original_graph = ox.add_edge_speeds(original_graph)
original_graph = ox.add_edge_travel_times(original_graph)

# Simplify the graph
graph = ox.simplify_graph(original_graph, endpoint_attrs=["speed_kph"])

# Initialize the simulation environment (World)

# Set simulation parameters
simulation_name = "MyUXsimWorld"
simulation_duration = 3600  # in seconds (e.g., 1 hour)
platoon_size = 5  # vehicles per platoon
reaction_time = 1  # in seconds
duo_update_time = 300  # in seconds, for dynamic user equilibrium (DUO) route choice update
duo_update_weight = 0.5  # weight for DUO update
duo_noise = 0.01  # noise for DUO route choice to prevent identical choices
eular_dt = 120  # in seconds, for Eulerian traffic state computation
eular_dx = 100  # in meters, for Eulerian traffic state computation
random_seed = 42  # for reproducibility

# Create the World object with the specified parameters
world = uxsim.World(name=simulation_name,
              deltan=platoon_size,
              reaction_time=reaction_time,
              duo_update_time=duo_update_time,
              duo_update_weight=duo_update_weight,
              duo_noise=duo_noise,
              eular_dt=eular_dt,
              eular_dx=eular_dx,
              random_seed=random_seed,
              print_mode=1,  # Enable printing simulation progress
              save_mode=1,  # Enable saving simulation results
              show_mode=0,  # Disable showing results via matplotlib (for faster performance)
              route_choice_principle="homogeneous_DUO",
              show_progress=1,  # Show simulation progress
              show_progress_deltat=600,  # Interval for showing progress, in seconds
              tmax=simulation_duration)  # Total simulation duration

# Helper function to determine max density based on road type and number of lanes
def calculate_max_density(road_type):
    default_density = 0.15  # Default maximum density in vehicles per meter per lane
    if road_type in ['motorway', 'trunk']:
        return 0.07  # Lower density due to higher speeds and longer headways
    elif road_type in ['primary', 'secondary']:
        return 0.10
    elif road_type in ['residential', 'tertiary']:
        return 0.20  # Higher density due to lower speeds
    else:
        return default_density  # Default for unspecified or other road types

# Create Nodes in UXsim from OSMnx graph nodes
for node_id, data in graph.nodes(data=True):
    world.addNode(name=str(node_id), x=data['x'], y=data['y'])

# Create Links in UXsim from OSMnx graph edges
for u, v, data in graph.edges(data=True):
    start_node_name = str(u)
    end_node_name = str(v)
    length = data['length']  # Assuming 'length' attribute exists
    # Assuming 'speed' attribute exists, convert speed from km/h to m/s
    speed_limit = data.get('speed', 30) * 1000 / 3600  # Default speed: 30 km/h
    # Calculate max density based on road type and lanes
    road_type = data.get('highway', '')
    max_density = calculate_max_density(road_type)
    priority = 1  # Example value
    world.addLink(name=f"{u}_{v}", start_node=start_node_name, end_node=end_node_name, length=length, free_flow_speed=speed_limit, jam_density=max_density, merge_priority=priority)

The capacity estimation is still very blunt, but maybe we can come up with a better and more flexible way to allow users to estimate the capacity from OSMnx data. The following columns are available in OSMnx:

  • ['osmid', 'oneway', 'lanes', 'ref', 'highway', 'maxspeed', 'reversed', 'length', 'speed_kph', 'travel_time', 'geometry', 'name', 'bridge', 'width', 'junction']

I think by combining highway, maxspeed, lanes, oneway, length, width and/or junction we should be able to come up with an useful jam_density estimate.

Have you run simulation on this imported network?

In my case, gridlocks occurred frequently due to network topology even if the demand was small. In order to avoid gridlocks, I had aggregated neighboring nodes.

If this issue was resolved, the parameter values can be determined relatively easily as you suggested.

I have implemented this function. It is still experimental though. Please see this notebook.

BTW, I forgot to clarify that the concept of a lane is not explicitly considered in UXsim. Currently, all links must be 1-lane road. For the details, please see the updated readme.

Thanks! Are you considering lane count in the future?

yes, I have a plan

On simplification of the network, how do you recommend consolidating intersections to work best with UXsim?

One thing I would like to do is use an existing OSMnx network. I have a graph for you here:

You can load the graph with:

road_network = ox.load_graphml("merged_network.graphml")

in which road_network is a networkx.classes.multidigraph.MultiDiGraph.

On simplification of the network, how do you recommend consolidating intersections to work best with UXsim?

In principle, 1 intersection should be represented as 1 node. But I am not very sure as it is still experimental. The quality and granularity of OSM varies greatly from place to place, so trial and error is recommended.

osmnx_graph.zip

It is not difficult to generate UXsim's Nodes and Links based on networkx graph. However, I think the conversion method should differ depending on the details of the graph specifications and the purpose of the analysis, so it is not provided as a module. Maybe you can try.