1. Introduction to GeNet¶
This section goes through basic usage examples. Available as a jupyter notebook and a wiki page.
Network
data structure¶
Main schema:
- nodes: exist uniquely, hold spatial information.
- e.g. A
- edges: directed pairs of
from
andto
nodes, often more than one link exists for one(from, to)
nodes pair. Sometimes unique.- e.g. A-B has 3 links: Bus, Car and a combined Walk+Bike
- links: single edge between a
(from, to)
nodes pair. Always unique.- e.g. the A-B Bus link (Bus lane going from A to B)
Data is stored on nodes and edges, where:
- nodes hold spatial information
- each edge/link holds information such as mode of transport allowed, speed, capacity, length, OSM-inherited tags
Schedule
consists of:
- List of
Service
s, where eachService
has:- List of
Route
s, where eachRoute
has:- List of
Stop
s (The order ofStop
s characterises theRoute
object), where eachStop
has:- Spatial information
- For a multimodal transport network each
Stop
should reference a link on the network
- Dictionary of trips which share that route (with time at first stop and vehicle ID)
- List of offsets for arrival and departure from
Stop
s
- List of
- List of
minimal_transfer_times
betweenStop
s, optional.vehicles
: dictionary of vehicle IDs from Route objects, mapping them to vehicle types in vehicle_types. Looks like this:{veh_id : {'type': 'bus'}}
. Defaults to None and generates itself from the vehicles IDs in Routes, maps to the mode of the Route. Checks if those modes are defined in thevehicle_types
.vehicle types
: dictionary of vehicle types and their specification. Indexed by the vehicle type that vehicles in thevehicles
attribute are referring to. E.g.
{'bus' : {
'capacity': {'seats': {'persons': '70'}, 'standingRoom': {'persons': '0'}},
'length': {'meter': '18.0'},
'width': {'meter': '2.5'},
'accessTime': {'secondsPerPerson': '0.5'},
'egressTime': {'secondsPerPerson': '0.5'},
'doorOperation': {'mode': 'serial'},
'passengerCarEquivalents': {'pce': '2.8'}}}
Setting-up a Network object¶
from genet import Network
To initialise an empty Network
, you need a coordinate system. We've gone with the British National Grid.
n = Network(epsg="epsg:27700")
You can get quick stats on the Network
by calling n.print()
method or just running the cell with network object.
n
<Network instance at 140419464489296: with graph: Name: Network graph Type: MultiDiGraph Number of nodes: 0 Number of edges: 0 and schedule Schedule: Number of services: 0 Number of routes: 0 Number of stops: 0
n.print()
Graph info: Name: Network graph Type: MultiDiGraph Number of nodes: 0 Number of edges: 0 Schedule info: Schedule: Number of services: 0 Number of routes: 0 Number of stops: 0
Right now the Network
is empty. You can fill it in with MATSim network data, using Open Street Map (OSM) data (Please refer to notebooks on reading data) or we can add links ourselves. A single link or a few links at once.
link_id = n.add_link(link_id="1", u="A", v="B", attribs={"modes": ["car", "walk"]})
2022-07-14 15:15:47,508 - Added Link with index 1, from node:A to node:B, under multi-index:0, and data={'modes': ['car', 'walk'], 'from': 'A', 'to': 'B', 'id': '1'}
link_id
'1'
Even though you specify a link id in this method, this id can change if there already exists a link with that id in the Network
. This is why this method returns the link id under which the link was added. Let's try to add the same link again.
link_id = n.add_link(link_id="1", u="A", v="B", attribs={"modes": ["car", "walk"]})
2022-07-14 15:15:47,548 - Generated 1 link ids. 2022-07-14 15:15:47,563 - Generated link id 0. 2022-07-14 15:15:47,565 - `1` already exists. Generated a new unique_index: `0` 2022-07-14 15:15:47,582 - Added Link with index 0, from node:A to node:B, under multi-index:1, and data={'modes': ['car', 'walk'], 'from': 'A', 'to': 'B', 'id': '0'}
link_id
'0'
If you are adding many edges between the same two nodes you can also specify which multi index you want a link to use
link_id = n.add_link(
link_id="1", u="A", v="B", attribs={"modes": ["car", "walk"]}, multi_edge_idx=5
)
2022-07-14 15:15:47,638 - Generated 1 link ids. 2022-07-14 15:15:47,642 - Generated link id 2. 2022-07-14 15:15:47,643 - `1` already exists. Generated a new unique_index: `2` 2022-07-14 15:15:47,650 - Added Link with index 2, from node:A to node:B, under multi-index:5, and data={'modes': ['car', 'walk'], 'from': 'A', 'to': 'B', 'id': '2'}
To add several links it is faster to use the add_links
method. This expects a dictionary with keys referring to desired link ids and the values being attribute dictionaries saved on those links. At the minimum each attribute dictionary needs a 'from'
and 'to'
key referring to from and to nodes the link is connecting.
reindexing_dict, links_and_attributes = n.add_links(
links_and_attributes={
"1": {"from": "A", "to": "B", "modes": ["bike"]},
"10": {"from": "B", "to": "A", "modes": ["bike"]},
}
)
2022-07-14 15:15:47,690 - Generated 1 link ids. 2022-07-14 15:15:47,699 - Added 2 links
This method also checks for links with clashing indices and returns a dictionary showing which ids we're reindex and what their new indices are, as well as an updated links_and_attributes dictionary showing final link attributes added to the Network
.
reindexing_dict
{'1': '3'}
links_and_attributes
{'3': {'from': 'A', 'to': 'B', 'modes': ['bike'], 'id': '3'}, '10': {'from': 'B', 'to': 'A', 'modes': ['bike'], 'id': '10'}}
Each node should have a spatial reference. For now we worked with nodes A
and B
, which dont have this information. To check what information is saved under nodes or links
n.node_attribute_summary(data=False)
attribute
n.link_attribute_summary(data=True)
attribute ├── modes: ['walk', 'car', 'bike'] ├── from: ['B', 'A'] ├── to: ['B', 'A'] └── id: ['1', '2', '3', '0', '10']
To add spatial information to nodes we can use apply_attributes_to_node
or apply_attributes_to_nodes
methods. We have two nodes so let's use the latter. GeNet expects values x
and y
in the coordinate system declared at the time of initiating the Network
.
n.apply_attributes_to_nodes(
new_attributes={
"A": {"x": 528704.1425925883, "y": 182068.78193707118},
"B": {"x": 528835.203274008, "y": 182006.27331298392},
}
)
2022-07-14 15:15:47,798 - Changed Node attributes for 2 nodes
n.node_attribute_summary(data=False)
attribute ├── x └── y
Now that we have spatial information for the nodes, we can do a quick plot of the Network
.
# n.plot()
The plots get much more interesting the more links you have in the Network
. Any additions and changes we made are recorded in the Network
s changelog.
n.change_log.head(10)
timestamp | change_event | object_type | old_id | new_id | old_attributes | new_attributes | diff | |
---|---|---|---|---|---|---|---|---|
0 | 2022-07-14 15:15:47 | add | link | None | 1 | None | {'modes': ['car', 'walk'], 'from': 'A', 'to': ... | [(add, , [('modes', ['car', 'walk']), ('from',... |
1 | 2022-07-14 15:15:47 | add | link | None | 0 | None | {'modes': ['car', 'walk'], 'from': 'A', 'to': ... | [(add, , [('modes', ['car', 'walk']), ('from',... |
2 | 2022-07-14 15:15:47 | add | link | None | 2 | None | {'modes': ['car', 'walk'], 'from': 'A', 'to': ... | [(add, , [('modes', ['car', 'walk']), ('from',... |
3 | 2022-07-14 15:15:47 | add | link | None | 3 | None | {'from': 'A', 'to': 'B', 'modes': ['bike'], 'i... | [(add, , [('from', 'A'), ('to', 'B'), ('modes'... |
4 | 2022-07-14 15:15:47 | add | link | None | 10 | None | {'from': 'B', 'to': 'A', 'modes': ['bike'], 'i... | [(add, , [('from', 'B'), ('to', 'A'), ('modes'... |
5 | 2022-07-14 15:15:47 | modify | node | A | A | {} | {'x': 528704.1425925883, 'y': 182068.78193707118} | [(add, , [('x', 528704.1425925883), ('y', 1820... |
6 | 2022-07-14 15:15:47 | modify | node | B | B | {} | {'x': 528835.203274008, 'y': 182006.27331298392} | [(add, , [('x', 528835.203274008), ('y', 18200... |
Another important part of the Network
is the Schedule
element describing public transit.
from genet import Schedule
n.schedule.print()
Schedule: Number of services: 0 Number of routes: 0 Number of stops: 0
It is initiated empty with a Network
. Right now, GeNet does not have nice methods to add and change Schedules. You can generate a Schedule
using different Schedule
elements: Service
, Route
and Stop
, or by reading GTFS data (Please refer to notebooks on reading data).
from genet import Route, Service, Stop
Each Schedule
consists of Services
. A Service
corresponds to a specific transit line, for example the Piccadilly London Underground line. Each Service
will have a number of Routes
which are characterised by an ordered sequence of Stop
s. For a network to be a valid multimodal network each Route
needs to have a valid reference to Network
links. Let's create a Schedule
with a bus Service
.
s = Schedule(
epsg="epsg:27700",
services=[
Service(
id="service1",
routes=[
Route(
id="1",
route_short_name="route1",
mode="bus",
stops=[
Stop(
id="0",
x=529455.7452394223,
y=182401.37630677427,
epsg="epsg:27700",
linkRefId="0",
),
Stop(
id="1",
x=529350.7866124967,
y=182388.0201078112,
epsg="epsg:27700",
linkRefId="1",
),
],
trips={
"trip_id": ["route1_04:40:00"],
"trip_departure_time": ["04:40:00"],
"vehicle_id": ["veh_bus_0"],
},
arrival_offsets=["00:00:00", "00:02:00"],
departure_offsets=["00:00:00", "00:02:00"],
route=["0", "1"],
),
Route(
id="2",
route_short_name="route2",
mode="bus",
stops=[
Stop(
id="1",
x=529455.7452394223,
y=182401.37630677427,
epsg="epsg:27700",
linkRefId="1",
),
Stop(
id="2",
x=529350.7866124967,
y=182388.0201078112,
epsg="epsg:27700",
linkRefId="2",
),
],
trips={
"trip_id": ["route2_05:40:00"],
"trip_departure_time": ["05:40:00"],
"vehicle_id": ["veh_bus_1"],
},
arrival_offsets=["00:00:00", "00:03:00"],
departure_offsets=["00:00:00", "00:05:00"],
route=["1", "2"],
),
],
)
],
)
s.print()
Schedule: Number of services: 1 Number of routes: 2 Number of stops: 3
# s.plot()
You can replace the Network
schedule by your new Schedule
.
n.schedule = s
n.print()
Graph info: Name: Network graph Type: MultiDiGraph Number of nodes: 2 Number of edges: 5 Average in degree: 2.5000 Average out degree: 2.5000 Schedule info: Schedule: Number of services: 1 Number of routes: 2 Number of stops: 3