Skip to content
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 104 additions & 124 deletions lasso/emme.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Setup Emme project, database (Emmebank) and import network data."""


import os as _os
from collections import defaultdict as _defaultdict
from copy import deepcopy as _copy
from pathlib import Path

from pandas.core.frame import DataFrame

Expand All @@ -27,6 +29,13 @@
from .roadway import ModelRoadwayNetwork
from .parameters import Parameters
from .logger import WranglerLogger

try:
import ranch
except ImportError:
WranglerLogger.warning("'Ranch' is not installed in this environment, if you wish to rebuild connectors when writing the emme network you will need to install ranch https://github.com/BayAreaMetro/Ranch")
ranch = None

from .mtc import _is_express_bus, _special_vehicle_type

from lasso import StandardTransit
Expand All @@ -46,11 +55,11 @@ def create_emme_network(
include_transit: Optional[bool] =False,
links_df: Optional[GeoDataFrame]=None,
nodes_df: Optional[GeoDataFrame]=None,
shapes_df: Optional[GeoDataFrame]=None,
name: Optional[str]="",
path: Optional[str]="",
write_drive_network: bool = False,
write_taz_drive_network: bool = False,
write_maz_drive_network: bool = False,
gtfs_directory: Optional[str] = None,
Comment thread
lachlan-git marked this conversation as resolved.
Outdated
write_maz_active_modes_network: bool = False,
write_tap_transit_network: bool = False,
write_taz_transit_network: bool = False,
Expand Down Expand Up @@ -91,16 +100,23 @@ def create_emme_network(

model_tables = {}


if write_drive_network and (gtfs_directory is None):
raise ValueError("'gtfs_directory' is required when writing a taz network")
Comment thread
lachlan-git marked this conversation as resolved.
Outdated

if roadway_network:
links_df = roadway_network.links_mtc_df.copy()
nodes_df = roadway_network.nodes_mtc_df.sort_values("N").reset_index(drop=True).copy()
shapes_df = roadway_network.shapes_df.copy()

elif (len(links_df)>0) & (len(nodes_df)>0):
links_df = links_df.copy()
nodes_df = nodes_df.sort_values("N").reset_index(drop=True).copy()
if shapes_df is not None:
shapes_df = shapes_df.copy()

else:
msg = "Missing roadway network to write to emme, please specify either model_net or links_df and nodes_df."
msg = "Missing roadway network to write to emme, please specify either model_net or links_df and nodes_df"
WranglerLogger.error(msg)
raise ValueError(msg)

Expand Down Expand Up @@ -192,31 +208,9 @@ def create_emme_network(
model_tables = prepare_table_for_drive_network(
nodes_df=nodes_df,
links_df=links_df,
parameters=parameters
)

setup = SetupEmme(model_tables, out_dir, _NAME, include_transit, parameters)
setup.run()

if write_taz_drive_network:
_NAME = "emme_taz_drive_network"
include_transit = False
model_tables = prepare_table_for_taz_drive_network(
nodes_df=nodes_df,
links_df=links_df,
parameters=parameters
)

setup = SetupEmme(model_tables, out_dir, _NAME, include_transit, parameters)
setup.run()

if write_maz_drive_network:
_NAME = "emme_maz_drive_network"
include_transit = False
model_tables = prepare_table_for_maz_drive_network(
nodes_df=nodes_df,
links_df=links_df,
parameters=parameters
shapes_df=shapes_df,
parameters=parameters,
gtfs_directory=gtfs_directory,
Comment thread
lachlan-git marked this conversation as resolved.
Outdated
)

setup = SetupEmme(model_tables, out_dir, _NAME, include_transit, parameters)
Expand Down Expand Up @@ -268,61 +262,31 @@ def create_emme_network(
setup = SetupEmme(model_tables, out_dir, _NAME, include_transit, parameters)
setup.run()

def prepare_table_for_taz_drive_network(
nodes_df,
links_df,
parameters,
):

"""
prepare model table for taz-scale drive network, in which taz nodes are centroids
keep links that are drive_access == 1 and assignable

Arguments:
nodes_df -- node database
links_df -- link database

Return:
dictionary of model network settings
"""

model_tables = dict()

# use taz as centroids, drop maz nodes and connectors
model_tables["centroid_table"] = nodes_df[
nodes_df.N.isin(parameters.taz_N_list)
].to_dict('records')
def extract_gtfs_from_dir(path: str):
Comment thread
lachlan-git marked this conversation as resolved.
Outdated
path = Path(path)
shapes = pd.read_csv(path / "shapes.txt")
trips = pd.read_csv(path / "trips.txt")
routes = pd.read_csv(path / "routes.txt")

model_tables["connector_table"] = links_df[
(links_df.A.isin(parameters.taz_N_list)) | (links_df.B.isin(parameters.taz_N_list))
].to_dict('records')

drive_links_df = links_df[
~(links_df.A.isin(parameters.taz_N_list + parameters.maz_N_list)) &
~(links_df.B.isin(parameters.taz_N_list + parameters.maz_N_list)) &
((links_df.drive_access == 1) & (links_df.assignable == 1))
].copy()

model_tables["link_table"] = drive_links_df.to_dict('records')

drive_nodes_df = nodes_df[
(nodes_df.N.isin(drive_links_df.A.tolist()) + nodes_df.N.isin(drive_links_df.B.tolist()))
].copy()

model_tables["node_table"] = drive_nodes_df.to_dict('records')

return model_tables
bus_routes = routes.loc[routes["route_type"].isin([3]), "route_id"]
bus_trips = trips.loc[trips["route_id"].isin(bus_routes), "shape_id"]
bus_shapes = shapes[shapes["shape_id"].isin(bus_trips)]
return bus_shapes

def prepare_table_for_drive_network(
nodes_df,
links_df,
parameters,
nodes_df: pd.DataFrame,
links_df: pd.DataFrame,
shapes_df: Optional[GeoDataFrame],
parameters: Parameters,
gtfs_directory: Union[Path, str],
Comment thread
lachlan-git marked this conversation as resolved.
Outdated
maximum_ft: int=7,
regenerate_connectors: bool=False,
):

"""
prepare model table for drive network, in which taz nodes are centroids
maz and tap nodes are included, but not as centroids
keep links that are drive_access == 1 and assignable == 1
prepare model table for taz-scale drive network, in which taz nodes are centroids
keep links that are drive_access == 1 and assignable
Comment thread
lachlan-git marked this conversation as resolved.
Outdated

Arguments:
nodes_df -- node database
Expand All @@ -332,81 +296,97 @@ def prepare_table_for_drive_network(
dictionary of model network settings
"""


model_tables = dict()

# use taz as centroids
# use taz as centroids, drop maz nodes and connectors
model_tables["centroid_table"] = nodes_df[
nodes_df.N.isin(parameters.taz_N_list)
].to_dict('records')

# taz connectors as centroid connectors
model_tables["connector_table"] = links_df[
(links_df.A.isin(parameters.taz_N_list)) | (links_df.B.isin(parameters.taz_N_list))
].to_dict('records')

# links: not taz connectors, has to be drive, assignable, or maz links
# maz drive connectors are assignable
# tap connectors are non-drive, not assignable
# get the links where buses drive on the network
gtfs_shape_bus_routes = extract_gtfs_from_dir(gtfs_directory)
Comment thread
lachlan-git marked this conversation as resolved.
Outdated
# assuming shape_model_node_id are in order for this step
gtfs_shape_bus_routes["next_node_id"] = gtfs_shape_bus_routes["shape_model_node_id"].shift(1)
gtfs_shape_bus_routes = gtfs_shape_bus_routes.sort_values(by=["shape_id", "shape_pt_sequence"])
gtfs_shape_bus_routes = gtfs_shape_bus_routes[(gtfs_shape_bus_routes["shape_pt_sequence"] != 1)]
gtfs_shape_bus_routes = gtfs_shape_bus_routes.drop_duplicates(subset=["shape_model_node_id", "next_node_id"])
gtfs_shape_bus_routes["has_bus_on_link"] = True
links_df = pd.merge(links_df, gtfs_shape_bus_routes[["shape_model_node_id", "next_node_id", "has_bus_on_link"]],
left_on=["A", "B"],
right_on=["shape_model_node_id", "next_node_id"],
how="left"
Comment thread
lachlan-git marked this conversation as resolved.
Outdated
).drop(columns=["shape_model_node_id", "next_node_id"])
links_df["has_bus_on_link"] = links_df["has_bus_on_link"].fillna(False)

# links to keep:
# ft >= 7
Comment thread
lachlan-git marked this conversation as resolved.
Outdated
# links containing bus routes
# toll booths >= 1
# toll sag >= 1
# make sure connectors gone
# bus links need to be on
# if centroid is empty -> build new connectors
# rebuild connectors for all TAZ

# we want to include managed lanes connectors as well
managed_nodes = list(set(links_df[links_df["managed"] == 1]["A"]) | set(links_df[links_df["managed"] == 1]["B"]))
links_df["managed_lane_connector"] = (links_df["ft"] == 8) & (links_df["A"].isin(managed_nodes) | links_df["B"].isin(managed_nodes))

drive_links_df = links_df[
~(links_df.A.isin(parameters.taz_N_list)) &
~(links_df.B.isin(parameters.taz_N_list)) &
(
(links_df.drive_access == 1) & (links_df.assignable == 1)
)
].copy()

drive_nodes_df = nodes_df[
(nodes_df.N.isin(drive_links_df.A.tolist()) + nodes_df.N.isin(drive_links_df.B.tolist()))
~(links_df.A.isin(parameters.taz_N_list + parameters.maz_N_list)) &
~(links_df.B.isin(parameters.taz_N_list + parameters.maz_N_list)) &
( # ft > 7 should be kept in the nework
Comment thread
lachlan-git marked this conversation as resolved.
Outdated
(
(links_df.drive_access == 1) &
(links_df.ft <= maximum_ft)
) |
( # is a tollsegment, should be kept within the network
(links_df.tollseg != 0) |
(links_df.tollbooth != 0)
)
)
) | links_df["has_bus_on_link"] | links_df["managed_lane_connector"] # special cases we would like tp keep
Comment thread
lachlan-git marked this conversation as resolved.
Outdated
].copy()
if not regenerate_connectors:
model_tables["connector_table"] = links_df[
(links_df.A.isin(parameters.taz_N_list)) | (links_df.B.isin(parameters.taz_N_list))
Comment thread
lachlan-git marked this conversation as resolved.
Outdated
].to_dict('records')
Comment thread
lachlan-git marked this conversation as resolved.
else:
# check ranch is installed
if ranch is None:
raise ImportError("package 'ranch' is not installed, please go to https://github.com/BayAreaMetro/Ranch for install instructions")

if shapes_df is None:
raise ValueError("shapes_df must be provided to regenerate connectors")

# regenerate connectors
ranch_roadway = ranch.Roadway(nodes_df, links_df, shapes_df, parameters)
ranch_roadway.build_centroid_connectors(build_taz_active_modes=True, build_maz_drive=True)
model_tables["connector_table"] = ranch_roadway.links_df[
(ranch_roadway.links_df.A.isin(parameters.taz_N_list)) | (ranch_roadway.links_df.B.isin(parameters.taz_N_list))
].to_dict('records')


model_tables["link_table"] = drive_links_df.to_dict('records')
model_tables["node_table"] = drive_nodes_df.to_dict('records')

return model_tables

def prepare_table_for_maz_drive_network(
nodes_df,
links_df,
parameters,
):

"""
prepare model table for maz-scale drive network, in which there are no centroids, drop taz nodes and connectors
keep links that are drive_access == 1 and assignable == 1

Arguments:
nodes_df -- node database
links_df -- link database

Return:
dictionary of model network settings
"""

model_tables = dict()

# no centroids, drop taz nodes and connectors

model_tables["centroid_table"] = []

model_tables["connector_table"] = []

drive_links_df = links_df[
~(links_df.A.isin(parameters.taz_N_list)) &
~(links_df.B.isin(parameters.taz_N_list)) &
((links_df.drive_access == 1) & (links_df.assignable == 1))
].copy()

model_tables["link_table"] = drive_links_df.to_dict('records')

drive_nodes_df = nodes_df[
~(nodes_df.N.isin(parameters.taz_N_list)) &
(nodes_df.N.isin(drive_links_df.A.tolist()) + nodes_df.N.isin(drive_links_df.B.tolist()))
].copy()

model_tables["node_table"] = drive_nodes_df.to_dict('records')

return model_tables


def prepare_table_for_maz_active_modes_network(
nodes_df,
links_df,
Expand Down