Simulating dynamic vehicular detours based on edge travel time in SUMO
Table of contents
- Introduction
- Prerequisites
- Getting started
— Create the network
— Create traffic demand
— Optional visualization
- Running the simulation
— Start the simulation
— Incrementing the simulation clock
— Closing the simulation
— Putting it all together
— Run the simulation
- Introducing the detour
— Detour logic
— Applying logic to vehicles
— Change vehicle color
— Putting things together
- Final result
Introduction
This article will show you how to dynamically select vehicles to detour around specified roads within a SUMO traffic simulation.
Detours are a common event in urban driving and are caused by many reasons, including construction and vehicular accidents, to name a few. Whatever the reason, not all vehicles will choose to avoid these roads; some may drive straight through without a care, while others might take to the side streets. We will simulate both behaviours.
Prerequisites
I assume you have already installed SUMO on your machine and configured your PATH and SUMO_HOME environment variables. Read the documentation to learn how to get set up. To test that your machine is ready, you should be able to execute the following commands in a terminal:
sumo --version
sumo-gui
python --version
netedit
Getting started
If you’re familiar with SUMO traffic simulations or have read my other tutorials, then you might already know that the foundational components to run a simulation in SUMO include:
- a network
- traffic demand
In SUMO, simulations are deterministic by default, but there are various ways to introduce randomness. For our purposes, we’ll leverage a tool called Traffic Control Interface (TraCI for short) provided by SUMO to allow modifying the simulation in real-time.
TraCI enables us to introduce custom logic (in the form of Python code) to track and manipulate many aspects of the simulation, including the routes of individual vehicles. This article provides various source code examples to help you follow along. Three configuration files are grouped in a config folder, and one Python script — called main.py — contains all the logic to run and manipulate our simulation. To get started, mimic this folder structure on your machine.
Create the network
This section focuses on the network. The shape and complexity of the network are irrelevant. What matters is that you identify the name (id
) of an edge you wish for the vehicles to avoid in the simulation. For the convenience of clarity, I’ll edit the network to rename three edges:
- the origin edge (as “origin”) where all vehicles will depart;
- the destination edge (as “destination”) where all vehicles are destined;
- and the edge some vehicles will avoid (as “closed”).
If you’re unfamiliar with creating networks, check out my tutorial on using netedit to design your own networks.
For those following along and wanting more practice in using netedit, I’ve provided two tables below for the lists of junctions and edges you can use as reference when recreating the example network.
List of junctions
List of edges
For those who feel too lazy to draw their own networks, here is the source code for the example network shown above. Simply paste it into a file called network.net.xml in the config folder.
Create traffic demand
This section is about traffic demand, another foundational component for traffic simulations. For our purposes, we can keep things simple using a handful of identical vehicles initially following the same route. We’ll later introduce custom logic to modify these routes dynamically.
- Route ⇒ an expanded trip including the origin and destination edges and all edges in between.
- Trip ⇒ a vehicular movement from one place to another.
There are many ways to generate routes in SUMO, with the choice depending on your available inputs. For our needs, we know the origin and destination edges. Still, we can save time by leaving out the intermediate edges and allowing SUMO to figure out the best path. To learn more about demand modelling, read the documentation.
With the example network, there are only two possible routes a vehicle can travel from origin to destination. In the figure below, these routes are illustrated with coloured arrows. By default, SUMO estimates travel time based on edge speed limits and distance, and it’ll choose the optimal path (aka. route) for individual vehicles to follow.
Let’s start by describing our trips. Although it’s possible to configure traffic demand using tools like netedit, it’s much easier to write the source code directly in this instance. Start by creating a file in the config folder, and let’s name it trips.trips.xml.
The basic description of a trip requires only an identifier (id
), the origin (from
) and destination (to
) edges, as well as the time when the vehicle enters the network (depart
). As previously mentioned, we’re leveraging automatic routing to generate our routes during runtime. To learn more about automatic routing, check out the documentation.
Optional visualization
By default, SUMO GUI uses the “standard” view setting, representing vehicles as triangles and colouring the network grey. I prefer to use the “real world” view setting, which, as the name suggests, adds a more realistic visual representation of the elements in the simulation.
With SUMO GUI open, you can switch between the available view settings, but to change the default when the application loads, the config file viewSettings.xml is required.
Running the simulation
By this point, you should have a project structure that resembles the one shown in the introduction, with two (optionally three) configuration files in the config folder. What’s missing is the runner script that orchestrates the simulation — and later, the handling of our detouring logic. This section focuses on the runner script, so let’s get started!
At the very least, the runner script will initiate the simulation, move the clock forward, and close the simulation once all the vehicles have exited.
Start the simulation
To simply get the simulation to run, we need TraCI to pipe our network and trip files into SUMO GUI (the graphical interface of SUMO). We’ll define a function, startSim()
, to handle this operation. The following two procedures include moving the simulation time forward and also stopping the simulation.
To use TraCI, we must provide it with a binary of an application and execution options. This binary will be SUMO-GUI, and we’ll obtain its binary using the checkBinary
function in the sumolib library. The execution options depend on the application. For our purposes, we’re required to use the following options:
--net-file [FILE]
→ Reads the network file.--route-files [FILES]
→ Reads the routes/trips file.
While the following options are optional:
--delay [FLOAT]
→ Adds [FLOAT] delay between simulation steps, which we'll use to essentially slow it down so human eyes can watch.--gui-settings-file [FILE]
→ Reads visualization settings from the file, which we'll use to apply the "real world" skin to the road and vehicles.--start
→ Automatically starts the simulation once loaded, saving us the trouble of pressing the start button manually.
👉 To view the list of available execution options, it’s general practice to execute the application directly in a terminal, followed by the option
--help
.
Incrementing the simulation clock
Without instructing TraCI when or how to increment the simulation clock, the simulation will remain frozen in time. We can easily increment the clock by calling the traci.simulationStep()
method. However, this method only moves the clock by one second, so we’ll need to wrap this in a loop to continuously increment time.
To prevent the simulation from running forever, we’ll create a new function, shouldContinueSim()
, to check if all the vehicles from our trips have entered and exited the network.
Closing the simulation
And finally, to stop TraCI from running, we’ll use its traci.close()
method.
Putting it all together
Let’s put everything together in the main.py file.
Run the simulation
Let’s run the simulation. In the terminal, execute the runner script with the command python main.py
. You’ll watch all our vehicles enter the network from the leftmost edge and drive their optimal routes towards the rightmost edge. Voila! Next, we’ll introduce the detour logic.
Introducing the detour
So we’ve got our simulation generating vehicles and their respective routes based on our trip descriptions. Now it’s time to introduce the detour logic. In this section, we’ll select a handful of vehicles to detour and change their colour to help visually distinguish them from the unaffected vehicles.
Detour logic
Our approach to deter our vehicle from driving through the specific edge is to tell the vehicle that its travel time through the edge would be infinitely large. This approach works if the routing algorithm used by SUMO considers travel time in its calculations (which it does by default). Each vehicle considers edge travel time individually, and to change its perspective of these estimated travel times, TraCI provides the traci.vehicle.setAdaptedTraveltime()
method.
Changing a vehicle’s perspective of edge travel times does nothing on its own because the vehicle decided its route when it initially entered the network and will not automatically change it. We need to force it to recalculate its route using the traci.vehicle.rerouteTraveltime()
method. Together, these two methods will convince the vehicle to choose an alternative route to avoid the targeted edges. We'll create a function, avoidEdge()
, to handle this operation.
Applying logic to vehicles
Now that we have the logic, we need to select and apply it to vehicles and the edge to avoid.
There are many ways to select vehicles, but we’ll take the most straightforward approach and manually record the names from our trips file. Knowing the IDs of the vehicles and edge is required for this step. I’ll use a global variable array, VEHICLES
, to contain the names of these vehicles and another variable, EDGE_ID
, to contain the edge's name.
Unfortunately, SUMO may throw an error if you try to access specific vehicle properties while the vehicle is not on the network. So we need to ensure the vehicle exists before we try to change anything, but there is no direct way to do this. One technique is to check if its name is among the list of vehicles that entered the network during this time by using the traci.simulation.getDepartedIDList()
method. We'll create a function, getOurDeparted()
, to handle this operation.
Change vehicle color
Next, but optional, is to change the colour of our vehicle to red using the traci.vehicle.setColor()
method. This will help us visually identify it among the other vehicles. We'll create a function, setVehColor()
, to handle this operation.
Putting things together
With all the above components, we can start and stop the simulation, increment its clock, and change vehicle colour and routes. With these, your main.py file should look like the following:
Final result
Suppose you run the simulation now (python main.py
). In that case, you'll notice that the vehicles we previously selected appear red once they enter the network. Additionally, as they approach the first intersection, they'll slow down, signal and turn right down the side street. At the same time, all other yellow vehicles continue straight (or change lanes to avoid a collision).
You’ll recall that we did not specify any intermediate edges in the description of our trips; this is all handled by SUMO. In fact, this behaviour is dynamic and based on various aspects of the network and traffic. For instance, by adding more side streets or introducing traffic lights, the vehicles would likely choose separate routes to avoid causing traffic jams.
Congratulations! Through this article, you designed a network, described traffic demand, configured visualization settings, set up TraCI to manage the simulation, and introduced dynamic detouring logic.
If you have any questions/suggestions, let’s connect on Github or LinkedIn!