Note
Go to the end to download the full example code.
Active transport assignment#
We can also assign the resulting active transport trips to the walk and bike networks using AequilibraE
For this integration we have used [AequilibraE](https://www.aequilibrae.com/), which is an open source modelling package with incredibly fast static assignment capabilities, but similar integrations are possible with other packages.
sphinx_gallery_thumbnail_path = ‘../../examples/modelling_like_the_old_days/active_transport.png’
import numpy as np
import pandas as pd
from aequilibrae.matrix import AequilibraeMatrix
from aequilibrae.paths import TrafficAssignment, TrafficClass
from scipy.sparse import coo_matrix
from polaris import Polaris
from polaris.runs.static_assignment.static_active_graph import ActiveGraph
from polaris.utils.testing.temp_model import TempModel
We create a new model directory with a grid network that has already been run
project_dir = TempModel("Grid")
pol = Polaris.from_dir(project_dir)
Before anything else, we build the graphs
ag = ActiveGraph(pol.supply_file)
walk_graph = ag.walkgraph
bike_graph = ag.bikegraph
/venv-py312/lib/python3.12/site-packages/geopandas/array.py:1770: UserWarning: CRS not set for some of the concatenation inputs. Setting output's CRS as NAD83 / UTM zone 16N (the single non-null crs provided).
return GeometryArray(data, crs=_get_common_crs(to_concat))
/venv-py312/lib/python3.12/site-packages/aequilibrae/paths/graph.py:218: ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignment
build_compressed_graph(self, remove_dead_ends)
/venv-py312/lib/python3.12/site-packages/geopandas/array.py:1770: UserWarning: CRS not set for some of the concatenation inputs. Setting output's CRS as NAD83 / UTM zone 16N (the single non-null crs provided).
return GeometryArray(data, crs=_get_common_crs(to_concat))
/venv-py312/lib/python3.12/site-packages/aequilibrae/paths/graph.py:218: ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignment
build_compressed_graph(self, remove_dead_ends)
Cleaning the network#
The bike and walk networks may not be fully connected, so let’s make sure we only assign trips between nodes that are part of the main network
walk_graph.set_graph("time")
walk_graph.set_skimming(["time"])
all_nodes = ag.nodes.node.to_numpy()
res = walk_graph.compute_path(all_nodes[0], all_nodes[1])
res.predecessors[walk_graph.nodes_to_indices[all_nodes[0]]] = 0
connected = walk_graph.all_nodes[np.where(res.predecessors >= 0)]
missing_nodes = np.setdiff1d(all_nodes, connected)
# So we got the actual "main island" of the network
assert connected.shape[0] > 0.95 * all_nodes.shape[0]
print(f"Disconnected nodes: {missing_nodes.shape[0]:,}")
Disconnected nodes: 0
locs = pol.network.tables.get("Location")
nodes = ag.nodes[ag.nodes.node.isin(connected)]
Grab the trips#
walk_trips = pol.demand.tables.get("Trip", filter='mode in (8)')
bike_trips = pol.demand.tables.get("Trip", filter='mode in (7)')
Functions to process trips intro matrices and assign them to the graph#
def transform_trips(locs, nodes, df_trips, graph):
valid_nodes = np.hstack([graph.network.a_node, graph.network.b_node])
nodes =nodes[nodes.node.isin(valid_nodes)]
node_loc = locs[["location", "geo"]].sjoin_nearest(nodes[["node", "geo"]])[["node", "location"]]
node_loc1 = node_loc.rename(columns={"location": "origin", "node":"orig_node"})
node_loc2 = node_loc.rename(columns={"location": "destination", "node":"dest_node"})
df_trips2 = df_trips[["origin", "destination"]].merge(node_loc1, on="origin").merge(node_loc2, on="destination")
df_trips2 = df_trips2.groupby(["orig_node", "dest_node"]).size().reset_index()
df_trips2.columns = ["origin", "destination", "trips"]
centroids = np.unique(np.hstack([df_trips2.origin.unique(), df_trips2.destination.unique()]))
df1 = pd.DataFrame({"origin": centroids, "orig_node": np.arange(centroids.shape[0])})
df2 = pd.DataFrame({"destination": centroids, "dest_node": np.arange(centroids.shape[0])})
df = df_trips2.merge(df1, on="origin").merge(df2, on="destination")
coo_ = coo_matrix((df.trips.values, (df.orig_node.values, df.dest_node.values)))
demand_mat = AequilibraeMatrix()
demand_mat.create_empty(zones=centroids.shape[0], matrix_names=["trips"], memory_only=True)
demand_mat.index[:] = centroids[:]
demand_mat.matrices[:, :] = 0
demand_mat.matrices[:, :, 0] = coo_.todense()[:, :]
return demand_mat
def assign(graph, matrix):
matrix.computational_view(["trips"])
graph.prepare_graph(matrix.index)
graph.set_blocked_centroid_flows(False)
graph.set_skimming([])
# Create the assignment class
assigclass = TrafficClass(name="all_trips", graph=graph, matrix=matrix)
assig = TrafficAssignment()
# We start by adding the list of traffic classes to be assigned
assig.add_class(assigclass)
# This stuff we don't need, but the API requires
# --------------------------------------
assig.set_capacity_field("distance")
assig.set_vdf("BPR") # This is not case-sensitive
assig.set_vdf_parameters({"alpha": 0.15, "beta": 4.0})
# --------------------------------------
assig.set_time_field("time")
# And the algorithm we want to use to assign
assig.set_algorithm("all-or-nothing")
# Let's set parameters that make this example run very fast
assig.max_iter = 1
assig.rgap_target = 0.01
# we then execute the assignment
assig.execute()
return assig.results().reset_index()
Assign Walk trips#
cols = []
walk_trips_mat = transform_trips(locs, nodes, walk_trips, walk_graph)
walk_link_loads = assign(walk_graph, walk_trips_mat)[["link_id", "trips_ab", "trips_ba", "trips_tot"]]
walk_links = pol.network.tables.get("Transit_Walk").rename(columns={"walk_link":"link_id"})[["link_id", "geo"]]
walk_links = walk_links.merge(walk_link_loads, on="link_id")
factor = 30 / walk_links.trips_tot.max()
walk_links.to_crs(epsg=4326).explore(
color="blue",
style_kwds={
"style_function": lambda x: {
"weight": x["properties"]["trips_tot"] * factor,
}
},
)
/venv-py312/lib/python3.12/site-packages/aequilibrae/paths/graph.py:218: ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignment
build_compressed_graph(self, remove_dead_ends)
all_trips : 0%| | 0/173 [00:00<?, ?it/s]
Equilibrium Assignment : 0%| | 0/1 [00:00<?, ?it/s]
Assign Bike trips#
bike_trips_mat = transform_trips(locs, nodes, bike_trips, bike_graph)
bike_link_loads = assign(bike_graph, bike_trips_mat)[["link_id", "trips_ab", "trips_ba", "trips_tot"]]
bike_links = pol.network.tables.get("Transit_Bike").rename(columns={"bike_link":"link_id"})[["link_id", "geo"]]
bike_links = bike_links.merge(bike_link_loads, on="link_id")
factor = 30 / bike_links.trips_tot.max()
bike_links.explore(
color="red",
style_kwds={
"style_function": lambda x: {
"weight": x["properties"]["trips_tot"] * factor,
}
},
)
/venv-py312/lib/python3.12/site-packages/aequilibrae/paths/graph.py:218: ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignment
build_compressed_graph(self, remove_dead_ends)
all_trips : 0%| | 0/175 [00:00<?, ?it/s]
Equilibrium Assignment : 0%| | 0/1 [00:00<?, ?it/s]
Total running time of the script: (0 minutes 4.939 seconds)