Getting Started
The Python API of PLANit allows you to conduct traffic assignment runs quickly and easily, while still remaining highly configurable. In this getting started we will take you through the process of setting up the environment and constructing your first PLANit assignment run.
Alternatively you may want to create your first (network) Converter. We also provide an example that takes an OSM network and converts it into (GIS) Shape layers.
For more (but less detailed) examples have a look at the examples section
Make sure you installed PLANit-Python, as per the Installation guide
Traffic Assignment
In this section we will be creating our very first PLANit-Python traffic assignment application!
Creating inputs
PLANit requires - at minimum - a network, demands, and zoning structure as inputs to be able to construct a valid
transport network. Let’s provide all three in the same XML file. Our XML file is based on
the Default PLANit Data Format. Copy the contents of the XML snippet below
and save them somewhere on disk, e.g. "c:\Users\Public\planit_test\"
, as - for example - "planit_input.xml"
.
Alternatively, use planit_input.xml
<PLANit xmlns:gml="http://www.opengis.net/gml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- Demand component -->
<macroscopicdemand>
<id>d1</id>
<demandconfiguration>
<timeperiods>
<timeperiod id="0">
<duration>3600</duration>
</timeperiod>
</timeperiods>
</demandconfiguration>
<oddemands>
<odcellbycellmatrix timeperiodref="0">
<o ref="1">
<d ref="2">1000</d>
</o>
</odcellbycellmatrix>
</oddemands>
</macroscopicdemand>
<!-- Physical network component -->
<macroscopicnetwork>
<id>n1</id>
<infrastructurelayers>
<layer id="road">
<nodes>
<node id="1" />
<node id="2" />
</nodes>
<links>
<link id="1" nodearef="1" nodebref="2">
<linksegment id="1" dir="a_b" />
<linksegment id="2" dir="b_a" />
<length>10</length>
</link>
</links>
</layer>
</infrastructurelayers>
</macroscopicnetwork>
<!-- zoning structure component -->
<macroscopiczoning>
<id>z1</id>
<zones>
<zone id="1">
<centroid>
<name>Zone 1 centroid</name>
</centroid>
<connectoids>
<connectoid id="1" noderef="1" />
</connectoids>
</zone>
<zone id="2">
<centroid>
<name>Zone 2 centroid</name>
</centroid>
<connectoids>
<connectoid id="2" noderef="2" />
</connectoids>
</zone>
</zones>
</macroscopiczoning>
</PLANit>
Demands
The travel demand is provided in matrix format in veh/h. In this case for the default mode (we did not specify a mode explicitly) and set to a value of 1000:
Origin/Destination | 1 | 2 |
---|---|---|
1 | - | 1000 |
2 | - | - |
Also note that the inputs define a single time period that has a duration of exactly 1 hour, i.e., 3600 seconds.
Network and zoning structure
As you can see, the demands, zoning, and network all have their own section in the XML. Here, the network comprises:
- 1 link (segment)
- 2 nodes (at upstream and downstream of link),
- 2 zones,
- 2 centroids, one per zone,
- two connectoids, one per zone
Each zone represents a geographical area. It has a centroid from which travel demand can depart/arrive. To access the physical road network, each zone has one or more connectoids, i.e., nodes marked as eligible points for ingress/egress. PLANit will construct virtual links, connecting each connectoid node to the centroid. In this case, both nodes are marked as connectoids for one of the two zones.
PLANit model configuration
Let us now configure our traffic assignment run using the above inputs.
- Open a text editor to create our Python script.
It is advised to use an editor that supports syntax highlighting for Python. Possibly even an IDE, so you can debug your code, e.g. Eclipse with PyDev for example, Or Jetbrains PyCharm.
- create a new Python script - for example -
my_first_planit.py
, or alternatively download the below script here - save it in the same directory as where you created the XML input file
The *.py
extension signals that this is a python script that can be interpreted and run by Python.
- Copy the below script into the file and save it. We’ll go through the script line by line to explain what is going on
from planit import *
# main class
planit = Planit()
# access assignment project
planit_project = planit.create_project()
# COMPONENTS
planit_project.set(TrafficAssignment.TRADITIONAL_STATIC)
planit_project.assignment.set(PhysicalCost.BPR)
planit_project.assignment.set(VirtualCost.FIXED)
planit_project.assignment.set(Smoothing.MSA)
# CONFIGURE COST COMPONENT
# BPR Travel time function
alpha = 0.9
beta = 4.5
planit_project.assignment.physical_cost.set_default_parameters(alpha, beta)
# CONFIGURE OUTPUT
planit_project.assignment.output_configuration.set_persist_only_final_Iteration(True)
# RUN ASSIGNMENT
planit_project.run()
First, an instance of PLANit project is created, named plan_it
, via
from planit import *
planit = Planit()
The Planit()
implementation is available as part of the planit module. Since we might need more than just the Planit() class we import the entire API functionality first via from planit import *
before we instantiate it. Leaving out the import will cause the script to fail since it won’t be able to find the necessary PLANit code.
You can give the instance any name you want, it is not tied to planit, it could also be
i_love_assignment
, orp
, orwhat_now
. Do note that a descriptive name is usually the best choice.
Since we can do more than only assignments, you have to indicate you want to start an assignment project. This is done by collecting the assignment project from planit via
# create an assignment project
planit_project = planit.create_project()
This project instance allows you to configure the assignment. To do so, you must first decide on what traffic assignment components you want to use. The most important and first component that must be chosen is the type of traffic assignment. Here, we adopt the traditional static traffic assignment model. It is configured by setting it on the project, via its set method:
planit_project.set(TrafficAssignment.TRADITIONAL_STATIC)
Like all components that the user can choose, you provide its enum identifier to initialise the underlying object(s). For traffic assignment options, this is the TrafficAssignment.<enum>.
Each traffic assignment comes with a number of default components that will be activated automatically. For the sake of this example, we explicitly activate the defaults (BPR, FIXED, MSA) in the script via:
planit_project.assignment.set(PhysicalCost.BPR)
planit_project.assignment.set(VirtualCost.FIXED)
planit_project.assignment.set(Smoothing.MSA)
Once you have completed this example, you can try running it again, but now with the above three lines removed. You will see they yield the same result, i.e., these components are activated by default.
As you can see, each line calls the .set()
method on planit.assignment
.
Because we chose our assignment to be the traditional static assignment, planit exposes the configuration for this assignment
via its .assignment
property.
Physical cost
Here, we choose to set the physical cost, which represents the cost structure imposed on the physical road network, to
BPR. This is the most common travel time function used for this type of assignment method.
Virtual cost
You can also configure the costs for the virtual part of the network that connects the travel demand from the zones
to the physical network (via virtual links between connectoids and centroids) separately. Here, we choose that cost to
be FIXED, using its default value.
Smoothing
Traditional static assignment is a so-called within-day equilibrium method based on Wardrop’s first principle, meaning
that it seeks to finds a solution where all travellers find their optimal path having the smallest cost given the choices
of all other travellers (who do the same). This requires an iterative procedure. During this iteration process, so called
smoothing is applied. This ensures that changes between iterations result in a smooth transition towards the equilibrium
solution. there exist many ways to apply smoothing. Here, MSA smoothing is used because
of its simplicity and computational attractiveness.
Different assignment types have different components for cost, smoothing, etc. that they support. Make sure that the choice for each component is compatible with your assignment method.
Let us now look at how to configure options on one of our components; the BPR cost function:
alpha = 0.9
beta = 4.5
planit.assignment.physical_cost.set_default_parameters(alpha,beta)
Because we chose BPR, the BPR configuration is made availabe via planit_project.assignment.physical_cost
.
The same holds for the other components, each one is available via a property on
planit_project.assignment
.
The BPR function has two parameters (alpha and beta) that you can configure in various ways,
see BPR documentation. Here, we will just change the defaults for all link segments,
modes, and link segment types simultaneously. We do so via
.set_default_parameters()
.
Before we start the simulation run, we also explicitly make sure that we only store results of the final iteration. Why? Well, because a simulation might need hundreds of iterations to solve. Then, you’re likely only interested in the final result and do not want PLANit to waste precious time and space on generating outputs for intermediate iterations.
In this example, this is not the case, but for demonstration purposes it is useful to demonstrate this useful feature.
To make this happen we call
planit_project.assignment.output_configuration.set_persist_only_final_Iteration(True)
Everything related to general choices regarding outputs is configured via
.output_configuration
. Dedicated configuration for link, od, and path outputs also exist (when activated). They are accessible viaplan_it_project.assignment.link_configuration.<call>
,plan_it_project.assignment.od_configuration.<call>
, andplan_it_project.assignment.path_configuration.<call>
Running the script
Before running the script make sure it ends with the line:
planit_project.run()
This will trigger the simulation to start. Make sure all configuration is conducted before calling this method, because otherwise it has no impact on the simulation.
- Open a new console window
- Go to the directory where you saved the input and your python script
- Execute the following command:
python my_first_planit.py
Replace
my_first_planit.py
with your own script name if you chose a different name.
If all went well the script should run. On the console you will see some information regarding the run, the located inputs and the generated outputs. Since we did not change the default settings for the output; all outputs are persisted in the same location as where we found the inputs.
- Go to the directory where your scripts is located
You should see that PLANit generated two files, an XML file, and a CSV file. The former contains meta-data about the latter. The CSV file contains the actual results of your run.
Result files
The Default Output Formatter generates meta-data for each run as well as CSV files that contain the actual data. Let’s take a quick look.
XML meta-data
The XML meta-data file you generally only use when you’re still unfamiliar with the column information in the CSV, or to get some information about the run when you forgot how it was generated, otherwise you are likely to skip it and only concern yourself with the CSV results
Your XML meta-data file contains information about the simulation run, but most importantly provides information on the column information in the CSV. Each column is listed with information on:
- name, related to the column heading in the CSV
- type, type of the values, e.g., integer, string, double, etc.
- unit, unit of the values, e.g., km, h, m, veh/h, etc.
Below you’ll find the XML meta-data for this run (you should have something very similar)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<metadata xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="<path_to_xsd>/metadata.xsd">
<timestamp>2020-06-01T15:29:18.480+10:00</timestamp>
<version>X.Y.Z</version>
<description>A_DESCRIPTION</description>
<outputconfiguration>
<assignment>TraditionalStaticAssignment</assignment>
<physicalcost>BprLinkTravelTimeCost</physicalcost>
<virtualcost>FixedConnectoidTravelTimeCost</virtualcost>
<smoothing>MSASmoothing</smoothing>
<gapfunction>LinkBasedRelativeDualityGapFunction</gapfunction>
<stopcriterion>StopCriterion</stopcriterion>
<timeperiod>
<id>0</id>
<name></name>
</timeperiod>
</outputconfiguration>
<simulation>
<iteration>
<nr>2</nr>
<csvdata type="Link">LINK_TO_CSV_RESULT_FILE</csvdata>
</iteration>
</simulation>
<columns>
<column>
<name>Downstream Node XML Id</name>
<units>none</units>
<type>string</type>
</column>
<column>
<name>Link Segment XML Id</name>
<units>none</units>
<type>string</type>
</column>
<column>
<name>Mode XML Id</name>
<units>none</units>
<type>string</type>
</column>
<column>
<name>Time Period XML Id</name>
<units>none</units>
<type>string</type>
</column>
<column>
<name>Upstream Node XML Id</name>
<units>none</units>
<type>string</type>
</column>
<column>
<name>Maximum Speed</name>
<units>km/h</units>
<type>double</type>
</column>
<column>
<name>Calculated Speed</name>
<units>km/h</units>
<type>double</type>
</column>
<column>
<name>Cost</name>
<units>h</units>
<type>double</type>
</column>
<column>
<name>Flow</name>
<units>veh/h</units>
<type>double</type>
</column>
</columns>
</metadata>
CSV Results
The CSV result file - with default settings - has a heading row with the column names (corresponding to the columns in the XML meta-data file). The other rows contain the result values, separated by a comma. In this very simple example we only have a single result row for the one link. The result could look something like the following:
Downstream Node XML Id,Link Segment XML Id,Mode XML Id,Time Period XML Id,Upstream Node XML Id,Maximum Speed Calculated Speed,Cost,Flow
2,1,1,0,1,130.0000000,122.1916755,0.0818386,1000.0000000
As you can see, we find that the flow is indeed 1000 veh/h as stated in our input. The speed is somewhat reduced compared to free flow resulting in a slightly increased travel time (cost)
Many of columns you are likely not interested in for most applications. You can add and remove columns very easily. More information on this can be found under the Link Configuration section of the manual.
Activating more output types
By default, only link results are generated. There are however other types of output that you can activate via the chosen traffic assignment instance, such as:
- Path results, configurable via the Path Output Configuration
- OD results, configurable via the OD Output Configuration
- …
To do so, include the following lines in your script:
planit_project.assignment.activate_output(OutputType.PATH)
planit_project.assignment.activate_output(OutputType.OD)
If you run your script again, you will see that four additional files are created, two new XML files (one for paths, one for ODs) and two new CSV files (one for paths and one for ODs).
This concludes your first assignment application. We hope you enjoyed it!
Network Converter
The second examples shows how you can convert an Open Street Map network into a GIS Shape files using the PLANit converters.
Downloading inputs
The Open Street Map converter works best with pre-downloaded OSM files. You can directly stream from OSM but this is limited to a small area. For this example we will be parsing a section of the Sydney (Australia) network. If you want to download regions yourself perhaps have a look at the geofabrik website, for Oceania there are readily available downloads here
We will be using the following pre-downloaded portion of Sydney: github.com/TrafficPLANit/PLANitOSM/resources/osm/sydneycbd_2023.osm.pbf
Please download and save the file to a convenience location such as the directory you will be running your python script we are
about to create from, e.g., c:\Users\Public\sydneycbd_2023.osm.pbf
PLANit converter configuration
Let us now configure our converter script using the above input.
- Open a text editor to create our Python script.
It is advised to use an editor that supports syntax highlighting for Python. Possibly even an IDE, so you can debug your code, e.g. Eclipse with PyDev for example, Or Jetbrains PyCharm.
- create a new Python script - for example -
my_first_converter.py
, or alternatively download the below script here - save it in the same directory as where you downloaded the OSM PBF file to.
The *.py
extension signals that this is a python script that can be interpreted and run by Python.
- Copy the below script into the file and save it (if you did not download the ready-made one). We will go through the script line by line to explain what is going on
from planit import *
# create a network converter
planit_instance = Planit()
network_converter = planit_instance.converter_factory.create(ConverterType.NETWORK)
################### OSM READER ###########
osm_reader = network_converter.create_reader(NetworkReaderType.OSM, "Australia")
osm_reader.settings.set_input_file(r"c:\Users\Public\sydneycbd_2023.osm.pbf")
################### SHAPE WRITER ########
geo_writer = network_converter.create_writer(NetworkWriterType.SHAPE)
geo_writer.settings.set_output_directory(r"c:\Users\Public\")
geo_writer.settings.set_country("Australia")
# Explicitly set what to persist from the network (these flags are the default, but are included for illustration purposes)
geo_writer.settings.set_persist_nodes(True)
geo_writer.settings.set_persist_links(True)
# perform conversion
network_converter.convert(osm_reader, geo_writer)
First, an instance of PLANit project is created, named planit
, via
from planit import *
planit = Planit()
The Planit()
implementation is available as part of the planit module. Since we might need more than just the Planit()
class we import the entire API functionality first via from planit import *
before we instantiate it. Leaving out the
import will cause the script to fail since it won’t be able to find the necessary PLANit code.
You can give the instance
planit
any name you want, it is not tied toplanit
, it could also bei_love_assignment
, orp
, orwhat_now
. Do note that a descriptive name is usually the best choice.
We now need to create a converter capable of performing network conversions. This is achieved by the following lines of code.
network_converter = planit_instance.converter_factory.create(ConverterType.NETWORK)
The ConverterType
is set to NETWORK
here to indicate we want a network converter. Other converters are also supported
such as intermodal converters (combining multiple converters to support networks with public transport and zoning for example), or
zoning and/or demand converters. See the reference documentation for examples.
With the network converter available, you can creat network readers and writers of different types to configure and perform conversions. Here, we choose an OSM network reader to parse our Open Street Map inputs via
osm_reader = network_converter.create_reader(NetworkReaderType.OSM, "Australia")
osm_reader.settings.set_input_file(r"c:\Users\Public\sydneycbd_2023.osm.pbf")
The first line indicates we want the OSM network reader NetworkReaderType.OSM
and that we expect to parse an OSM file from an
Australia context "Australia"
. This matters because it impacts the driving rules, the default speed limits the reader will apply and
the projection used to store the geospatial information. Then, each reader can be configured via its settings, i.e., osm_reader.settings.<something>
.
Have a look at the reference documentation for the options on the OSM reader. Here we stick with the defaults and only specify the
location of the input file (which is mandatory). The notation used, i.e., r"c:\..."
is the Python way to allow you to use paths with just
single slashes, if you would leave out the r
in front of the string, you would need to write “c:\Users\Public… etc.” which is messy.
Next, we also need to configure a writer. Here we want to produce shape files to be able to explore the results in - for example - ArcGis, and/or QGis. We do so via
geo_writer = network_converter.create_writer(NetworkWriterType.SHAPE)
geo_writer.settings.set_output_directory(r"c:\Users\Public\")
geo_writer.settings.set_country("Australia")
# Explicitly set what to persist from the network (these flags are the default, but are included for illustration purposes)
geo_writer.settings.set_persist_nodes(True)
geo_writer.settings.set_persist_links(True)
First we create the writer, same as with the reader, only now ask for a writer and specify the writer type to be shape via
network_converter.create_writer
and NetworkWriterType.SHAPE
, respectively. We then set the mandatory output location
of the results via geo_writer.settings.set_output_directory(r"c:\Users\Public\")
If you want to use a different output location just replace the example location with your preferred location. Just make sure the directory exists and you have permission to access it.
We also indicate the country the results contains, this will allow PLANit to look up the most suitable projection that will be used. the used projection will be logged, so you always know what the chosen projection is.
Then for the purpose of this example we have explicitly set some of the configuration options (you may leave them at as we choose
settings that are the default). You can switch off the construction of certain Shape layers, here we state to generate both link
and nodes layers. If they were set to False
they would no longer be produced.
Running the script
Before running the script make sure it ends with the line:
network_converter.convert(osm_reader, geo_writer)
this will invoke the conversion with the created reader and writer provided. Make sure all configuration is conducted before calling this method, because otherwise it has no impact on the performed conversion.
- Open a new console window
- Go to the directory where you have placed your script
- If you have a virtual environment setup make sure it is active ofr the purpose of this test (optional)
- Execute the following command:
python my_first_converter.py
Replace
my_first_converter.py
with your own script name if you chose a different name.
If all went well the script should run. On the console you will see some information regarding the run, the located inputs, the generated outputs and logged anomalies and/or worthwhile other information.
Result files
Now go to the directory where your script stated the outputs should be saved to in your file explorer
You should see that PLANit generated a large number of GIS files with various file extensions. You should at least have files with the extension *.shp for the following types of geospatial network components.
- layer_all_planit_links
- layer_all_planit_linksegments
- layer_all_planit_nodes
Each of these has supporting files with the same name but different extensions as this is how the Shape format works.
The layer_all_planit_links
contains general information on road sections (without directional information), the directional
information of these links is provided in layer_all_planit_linksegments
. If a road is bi-directional, both directions will be given
the same geometry, so they are placed on top of each other, when selecting them in a GIS software, you can verify the direction by
looking at their directional information which relates to the nodes on either end. They either run from A to B node or vice versa.
The nodes layer is separate and available through layer_all_planit_nodes
. Lastly, we prefix all spatial layers with layer_all_
. This
has nothing to do with it being a spatial layer. The layer
refers to layers as they exist in PLANit, where it is possible to have layers
by mode. For this simple example though, all modes are combined in a single layer all
, hence this prefix.
Install or open ArcGis or QGis or another application that can display shape files and open the three above listed files (the ones with the *.shp extension) one by one. This should then allow you to see the network and should look something like the following: