Skip to content

Usage: 4.2. Using Network: Road Pricing

Kasia Kozlowska edited this page Jul 14, 2022 · 5 revisions

Using the Network object

This page goes through the process of generating a road pricing MATSim file. Available as a jupyter notebook or wiki page.

Road Pricing

Let's read an example MATSim network to work with.

from genet import read_matsim
import pandas as pd
import os

path_to_matsim_network = '../example_data/pt2matsim_network'

network = os.path.join(path_to_matsim_network, 'network.xml')
schedule = os.path.join(path_to_matsim_network, 'schedule.xml')
vehicles = os.path.join(path_to_matsim_network, 'vehicles.xml')
n = read_matsim(
    path_to_network=network, 
    epsg='epsg:27700', 
    path_to_schedule=schedule, 
    path_to_vehicles=vehicles
)
# you don't need to read the vehicles file, but doing so ensures all vehicles
# in the schedule are of the expected type and the definition of the vehicle
# is preserved
n.link_attribute_summary()
attribute
├── id
├── from
├── to
├── freespeed
├── capacity
├── permlanes
├── oneway
├── modes
├── s2_from
├── s2_to
├── attributes
│   ├── osm:way:access
│   ├── osm:way:highway
│   ├── osm:way:id
│   ├── osm:way:name
│   ├── osm:relation:route
│   ├── osm:way:lanes
│   ├── osm:way:oneway
│   ├── osm:way:tunnel
│   ├── osm:way:psv
│   ├── osm:way:vehicle
│   ├── osm:way:traffic_calming
│   ├── osm:way:junction
│   └── osm:way:service
└── length

Our current workflow relies on OSM way ids being saved to the network in the nested 'attributes' dictionary. We query OSM to extract those OSM ids and find them in the network.

attribute
├── id
├── from
├── to
...
├── attributes
...
│   ├── osm:way:id
...

Of course this can be streamlined by just reading and saving the toll tag when creating a network from OSM. For more info see Usage: 2.2. Reading Data: OSM. This would manifest itself in the following way in the links data:

attribute
├── id
├── from
├── to
...
├── attributes
...
│   ├── osm:way:toll
...

where you would look for 'osm:way:toll':'yes'

To do this via OSM ids, you can head over to https://overpass-turbo.eu/, use the quiery Wizard to find toll=yes zooming into the right place on the map. You can then grab the relevant OSM data by clicking Export. Under Data tab, you can click on download/copy as raw OSM data. This will copy the data into clipboard. Below I paste a small example:

osm_data = {
  "version": 0.6,
  "generator": "Overpass API 0.7.56.8 7d656e78",
  "osm3s": {
    "timestamp_osm_base": "2020-12-16T15:46:02Z",
    "copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL."
  },
  "elements": [
    {
      "type": "way",
      "id": 26997928,
      "nodes": [
        107790,
        1102995756,
        5479634639,
        5364578862,
        21665585,
        5479634644,
        107791
      ],
      "tags": {
        "highway": "primary",
        "lit": "yes",
        "maxspeed": "20 mph",
        "maxspeed:type": "GB:zone20",
        "name": "Charing Cross Road",
        "ref": "A400",
        "sidewalk": "both",
        "surface": "asphalt",
        "toll": "yes",
        "wikidata": "Q1063230"
      }
    },
      {
      "type": "way",
      "id": 546461337,
      "nodes": [
        1556097185,
        1951372935,
        1951372927
      ],
      "tags": {
        "foot": "no",
        "highway": "primary",
        "lit": "yes",
        "maxspeed": "20 mph",
        "name": "Byward Street",
        "oneway": "yes",
        "operator": "Transport for London",
        "postal_code": "EC3",
        "ref": "A3211",
        "sidewalk": "none",
        "surface": "asphalt",
        "toll": "yes"
      }},
    {
      "type": "node",
      "id": 107790,
      "lat": 51.511322,
      "lon": -0.1283895
    },
    {
      "type": "node",
      "id": 107791,
      "lat": 51.5118562,
      "lon": -0.1283797
    },
    {
      "type": "node",
      "id": 21665585,
      "lat": 51.5116901,
      "lon": -0.1283715
    },
    {
      "type": "node",
      "id": 1102995756,
      "lat": 51.511415,
      "lon": -0.1283857
    },
    {
      "type": "node",
      "id": 5364578862,
      "lat": 51.511599,
      "lon": -0.1283762
    },
    {
      "type": "node",
      "id": 5479634639,
      "lat": 51.5114884,
      "lon": -0.1283819
    },
    {
      "type": "node",
      "id": 5479634644,
      "lat": 51.5117331,
      "lon": -0.1283705
    }
  ]
}

All that is left is extracting the OSM way IDs of interest. In the case above it's just a couple.

It is also useful to record the ref and name or any other data that may relate to a dataset you have for tolls to make it human readable or to be able to join the two datasets. This will make it easier to decide on how much the toll should be.

def extract_data(d, key):
    try:
        return d[key]
    except KeyError:
        return float('nan')

osm_id = []
osm_ref = []
osm_name = []

for element in osm_data['elements']:
    if element['type']=='way':
        # what you get from overpass should all just be tolls but let's
        # put an extra condition here anyway
        if ('toll' in element['tags']) and (element['tags']['toll'] == 'yes'):
            osm_id.append(element['id'])
            osm_ref.append(extract_data(element['tags'], 'ref'))
            osm_name.append(extract_data(element['tags'], 'name'))

df_tolls = pd.DataFrame(
    {'osm_id': osm_id, 
     'osm_ref': osm_ref, 
     'osm_name': osm_name})
# the osm IDs in our network are of float type. Make sure you search for data with matching data types
df_tolls['osm_id'] = df_tolls['osm_id'].astype(float)
df_tolls.head()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
osm_id osm_ref osm_name
0 26997928.0 A400 Charing Cross Road
1 546461337.0 A3211 Byward Street

We now write this to a csv file.

df_tolls.to_csv('../example_data/pt2matsim_network/road_pricing/osm_toll_id_ref.csv', 
                index=False)

We can now use GeNet's road pricing methods to map the OSM ids in df_tolls to network links.

from genet.use import road_pricing

road_pricing.extract_network_id_from_osm_csv(
        network=n, 
        attribute_name='osm:way:id',
        osm_csv_path='../example_data/pt2matsim_network/road_pricing/osm_toll_id_ref.csv',
        outpath='../example_data/pt2matsim_network/road_pricing',
        osm_dtype=float # the osm IDs in our network are of float type. Make sure you search for data with matching data types

        )
100%|██████████| 2/2 [00:00<00:00, 37.33it/s]





(        osm_id osm_ref            osm_name  network_id
 0   26997928.0    A400  Charing Cross Road        True
 1  546461337.0   A3211       Byward Street        True,
 {26997928.0: ['1', '2', '3', '4'], 546461337.0: ['998', '999']})

This step can take a long time because the relationship between OSM ways and MATSim network links is rarely 1-to-1. For a given OSM way, there can be multiple network links (i.e. multiple links sharing the same osm:way:id) or no network links at all (i.e. during network creation/manipulation some OSM links were deleted or merged). Therefore, the matching OSM ways to network links has to be done on a case-by-case basis.

Upon completion, there will be two new files in the output folder ../example_data/pt2matsim_network/road_pricing specified above:

  • osm_tolls_with_network_ids.csv: this file will be a copy of the input osm_toll_id_ref.csv but augmented with a True/False value indicating whether each OSM way id was successfully matched with one of more network link ids.
  • osm_to_network_ids.json: this file contains a mapping of each OSM way id in osm_toll_id_ref.csv with one or more network link ids.

The next step is to decide on the vehicle_type, toll_amount, start_time and end_time for the toll. You may have other data that you can join onto osm_tolls_with_network_ids.csv. In the example below, we make it up.

df_tolls = pd.read_csv(
    '../example_data/pt2matsim_network/road_pricing/osm_tolls_with_network_ids.csv')

df_tolls.head()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
osm_id osm_ref osm_name network_id
0 26997928.0 A400 Charing Cross Road True
1 546461337.0 A3211 Byward Street True
df_tolls['vehicle_type'] = 'type2'
df_tolls['toll_amount'] = '2.9'
df_tolls['start_time'] = '00:00'
df_tolls['end_time'] = '23:59'
df_tolls.head()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
osm_id osm_ref osm_name network_id vehicle_type toll_amount start_time end_time
0 26997928.0 A400 Charing Cross Road True type2 2.9 00:00 23:59
1 546461337.0 A3211 Byward Street True type2 2.9 00:00 23:59
df_tolls.to_csv(
    '../example_data/pt2matsim_network/road_pricing/osm_tolls_with_network_ids.csv')

Next we can generate the road pricing file.

xml_tree = road_pricing.build_tree_from_csv_json(
    '../example_data/pt2matsim_network/road_pricing/osm_tolls_with_network_ids.csv', 
    '../example_data/pt2matsim_network/road_pricing/osm_to_network_ids.json')

road_pricing.write_xml(xml_tree, '../example_data/pt2matsim_network/road_pricing')