Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove assignable #6

Merged
merged 24 commits into from
Sep 25, 2024
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,
lachlan-git marked this conversation as resolved.
Show resolved Hide resolved
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")
lachlan-git marked this conversation as resolved.
Show resolved Hide resolved

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,
lachlan-git marked this conversation as resolved.
Show resolved Hide resolved
)

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):
lachlan-git marked this conversation as resolved.
Show resolved Hide resolved
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],
lachlan-git marked this conversation as resolved.
Show resolved Hide resolved
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
lachlan-git marked this conversation as resolved.
Show resolved Hide resolved

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)
lachlan-git marked this conversation as resolved.
Show resolved Hide resolved
# 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"
lachlan-git marked this conversation as resolved.
Show resolved Hide resolved
).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
lachlan-git marked this conversation as resolved.
Show resolved Hide resolved
# 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
lachlan-git marked this conversation as resolved.
Show resolved Hide resolved
(
(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
lachlan-git marked this conversation as resolved.
Show resolved Hide resolved
].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))
lachlan-git marked this conversation as resolved.
Show resolved Hide resolved
].to_dict('records')
lachlan-git marked this conversation as resolved.
Show resolved Hide 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
Loading