https://od2net.org includes some example networks. This tutorial will teach you how to generate these yourself, then modify the cost function. This is a good tutorial to complete before trying to create your own od2net input.
It's based on Python scripts. See the R package README for a basic example in R.
I'm assuming some familiarity running commands in a terminal (through Mac or Linux -- on Windows, you probably need WSL). You'll first need to install a few things:
- Rust (1.73 or newer)
- Python 3 (only standard library modules needed)
- ogr2ogr from GDAL
- tippecanoe
- osmium
- git
- You should already have standard Unix tools
curl
andgunzip
on your system
Instead of installing the Rust toolchain and compiling od2net, you can use pre-built Docker images. In commands below, instead of cargo run --release config.json
, do this:
docker run -v $(pwd):/app ghcr.io/urban-analytics-technology-platform/od2net:main /app/config.json
Open your terminal and let's get started! First we'll clone the git repo and
navigate into the od2net
directory:
git clone https://github.com/Urban-Analytics-Technology-Platform/od2net
cd od2net # if you're not already in the repo
Then we'll navigate to the Edinburgh example.
cd examples/edinburgh
Next we'll prepare all of the input data. Take a look through setup.py to see the steps that will happen:
- Download the
osm.pbf
file containing Edinburgh from Geofabrik - Use
osmium
to clip that file to a rectangle around just Edinburgh - Use
ogr2ogr
to get the centroid of every building, storing as points in a GeoJSON file. These will be the origins and destinations of trips in od2net. - Download a GeoJSON file with polygons representing census zones, courtesy of the Network Planning Tool for Scotland (NPT)
- Download (and transform) a CSV file that describes how many trips to calculate between every zone, also from NPT.
Depending on your network and computer, this should take just a few minutes:
python3 setup.py
You can take a look through the files in input/
if you're curious. When you're ready, let's run od2net!
cargo run --release config.json
This will take a few minutes to compile od2net the first time. After that, running for this example should take under a minute. You'll see some output stats printed at the end. These are also shown in the web app version.
Let's see the results! Open https://od2net.org in your browser, "Choose
file", then load od2net/examples/edinburgh/output/rnet.pmtiles
.
The thickness of lines shows the number of trips made using that road -- so thicker roads could be used by many cyclists, and need to have safe infrastructure. The four colors indicate the current "Cycling Level of Traffic Stress" (LTS) rating. You can hover on any road to see all the details: how many trips routed over this road, the reason why it has a certain LTS rating, and all of the OSM tags.
Do you notice any patterns?
This example made a pretty bold assumption: cyclists choose the shortest route. That means they'll treat a quiet residential street, segregated cycle lane, and a motorway all the same. The LTS colors show this. This results imply interventions on any road segment are equally plausible, regardless of political or economic costs. This is a useful starting point but unrealistic in most places. What if we instead want to see how the network looks if people avoid stressful roads? That should show what existing quiet streets form useful routes and reveal any gaps where using quiet options isn't possible.
Edit config.json to do this. This file refers to the input files we created with the Python script and describes the origin/destination requests we want to route for. It also describes how cyclists will choose routes.
Open config.json
in a text editor, and replace the "cost": "Distance",
line with something like this:
- "cost": "Distance",
+ "cost": {
+ "ByLTS": {
+ "lts1": 1.0,
+ "lts2": 1.5,
+ "lts3": 3.0,
+ "lts4": 5.0
+ }
+ },
These factors will be multiplied by the length of each road, and higher results are worse. So totally safe LTS 1 streets will just use distance as the cost. For LTS 2 routes, we'll penalize them by multiplying by 1.5. For high-stress LTS 4 roads, we'll penalize them by a factor of 5. That means a cyclist would choose to take a quiet LTS 1 route that's 5 times longer, just to avoid the LTS 4 road. You can tune these numbers, of course!
After you've edited the config, you can rerun od2net. But first you need to delete some intermediate files, since the cost function has changed.
rm -rf intermediate/
# Rename the first output file, so you can compare the two later
mv output/rnet.pmtiles output/rnet_direct.pmtiles
cargo run --release config.json
# Give the new output file a better name
mv output/rnet.pmtiles output/rnet_quiet.pmtiles
Now you can open output/rnet_quiet.pmtiles
in the web app and compare results. What changes? Are there any gaps in the quiet network that might be cheap and quick to fix?
That new cost function is still pretty simple. What if your definition of the "best route" doesn't just depend on distance and LTS, or what if you don't like the predefined LTS classification? You can write your own cost function from scratch in any language you like. od2net will give you all of the OSM tags for a road segment, its distance, and some other info, and you then return a number indicating the cost for routing over that road.
First let's tell od2net to use a custom command for cost. Edit config.json
and change cost
to this:
"cost": {
"ExternalCommand": "python3 cost.py"
},
Then create a new file called cost.py
, copying from a different example cost.py. The program gets a JSON array of dictionaries in STDIN and needs to print a JSON array of pairs of integer numbers as a result. Each dictionary input gives you:
length_meters
tags
, a JSON dictionary with the raw OSM tagslts
as a number 0 to 4, with 0 representing "cyclists not allowed here"nearby_amenities
, the number of shops that're closest to this road- Optional
slope
, the percent grade in the forwards direction
The output is a corresponding JSON array with a pair of costs for each edge. The pair is [forward_cost, backward_cost]
, which may be the same.
The example does something very boring -- if highway = residential, just return length. Otherwise, multiply by 10, meaning all other roads will be heavily penalized. Write something more interesting here!
Some tips for writing your own cost function:
- You can use any language you want. It just needs to read STDIN and write to STDOUT in the way that was described.
- The output costs need to be rounded to integers.
- If you want to debug your script, you can't print to STDOUT, because od2net will try to parse this as the JSON number result. You can instead write to STDERR or to a temporary log file. Keep in mind od2net will call your script multiple times when running (there's some internal batch size set), so if you write to a file, name it something unique.
Now you can setup od2net in a new place, with your own origin/destination data!