Source code for aequilibrae.paths.sub_area

import itertools
import logging
from typing import Union
from collections import defaultdict

import geopandas as gpd
import pandas as pd
from aequilibrae.context import get_active_project
from aequilibrae.matrix import AequilibraeMatrix
from aequilibrae.paths import RouteChoice
from aequilibrae.paths.graph import Graph


[docs] class SubAreaAnalysis:
[docs] def __init__( self, graph: Graph, subarea: gpd.GeoDataFrame, demand: Union[pd.DataFrame, AequilibraeMatrix], project=None, ): """ Construct a sub-area matrix from a provided sub-area GeoDataFrame using route choice. This class aims to provide a semi-automated method for constructing the sub-area matrix. The user should provide the Graph object, demand matrix, and a GeoDataFrame whose geometry union represents the desired sub-area. Perform a route choice assignment, then call the ``post_process`` method to obtain a sub-area matrix. Check how to run sub-area analysis :ref:`here <example_usage_sub_area_analysis>`. :Arguments: **graph** (:obj:`Graph`): AequilibraE graph object to use **subarea** (:obj:`gpd.GeoDataFrame`): A GeoPandas GeoDataFrame whose geometry union represents the sub-area. **demand** (:obj:`Union[pandas.DataFrame, AequilibraeMatrix]`): The demand matrix to provide to the route choice assignment. """ project = project if project is not None else get_active_project() self.logger = project.logger if project else logging.getLogger("aequilibrae") self.graph = graph self.sub_area_demand = None links = gpd.GeoDataFrame(project.network.links.data) self.interior_links = links[links.crosses(subarea.union_all().boundary)].sort_index() nodes = gpd.GeoDataFrame(project.network.nodes.data).set_index("node_id") self.interior_nodes = nodes.sjoin(subarea, how="inner").sort_index() self.interior_graph = ( self.graph.graph.set_index("link_id") .loc[self.interior_links.link_id] .drop(self.graph.dead_end_links, errors="ignore") .reset_index() .set_index(["link_id", "direction"]) ) self.edge_pairs = {x: (x,) for x in itertools.permutations(self.interior_graph.index, r=2)} self.single_edges = {x: ((x,),) for x in self.interior_graph.index} self.logger.info(f"Created: {len(self.edge_pairs)} edge pairs from {len(self.single_edges)} edges") self.rc = RouteChoice(self.graph) self.rc.add_demand(demand) self.rc.set_select_links(self.single_edges | self.edge_pairs, link_loading=False)
[docs] def post_process(self, demand_cols=None): """ Apply the necessary post processing to the route choice assignment select link results. :Arguments: **demand_cols** (*Optional*: :obj:`[list[str]]`): If provided, only construct the sub-area matrix for these demand matrices. :Returns: **sub_area_demand** (:obj:`pd.DataFrame`): A DataFrame representing the sub-area demand matrix. """ if demand_cols is None: demand_cols = self.rc.demand.df.columns sl_od = self.rc.get_select_link_od_matrix_results() sub_area_demand = [] for col in demand_cols: edge_totals = {k: sl_od[k][col].to_scipy() for k in self.single_edges} edge_pair_values = {k: sl_od[k][col].to_scipy() for k in self.edge_pairs} entered = defaultdict(float) exited = defaultdict(float) for (link_id, dir), v in edge_totals.items(): link = self.interior_graph.loc[link_id, dir] for (o, d), load in v.todok().items(): o = self.graph.all_nodes[o] d = self.graph.all_nodes[d] o_inside = o in self.interior_nodes.index d_inside = d in self.interior_nodes.index if o_inside and not d_inside: exited[o, self.graph.all_nodes[link.b_node]] += load elif not o_inside and d_inside: entered[self.graph.all_nodes[link.a_node], d] += load elif not o_inside and not d_inside: pass through = defaultdict(float) for (l1, l2), v in edge_pair_values.items(): link1 = self.interior_graph.loc[l1] link2 = self.interior_graph.loc[l2] for (o, d), load in v.todok().items(): o_inside = o in self.interior_nodes.index d_inside = d in self.interior_nodes.index if not o_inside and not d_inside: through[self.graph.all_nodes[link1.a_node], self.graph.all_nodes[link2.b_node]] += load interior = [] for o, d in self.rc.demand.df.index: if o in self.interior_nodes.index and d in self.interior_nodes.index: interior.append((o, d)) sub_area_demand.append( pd.DataFrame( list(entered.values()) + list(exited.values()) + list(through.values()), index=pd.MultiIndex.from_tuples( list(entered.keys()) + list(exited.keys()) + list(through.keys()), names=["origin id", "destination id"], ), columns=[col], ) ) interior = [] for o, d in self.rc.demand.df.index: if o in self.interior_nodes.index and d in self.interior_nodes.index: interior.append((o, d)) self.sub_area_demand = pd.concat(sub_area_demand, axis=1).fillna(0.0) self.sub_area_demand = pd.concat([self.sub_area_demand, self.rc.demand.df.loc[interior]]).sort_index() return self.sub_area_demand