Skip to content

Commit 862210d

Browse files
authored
Merge pull request #149 from wsp-sag/bicounty_emme
Apply transit project cards created from Emme ems files
2 parents 35abe28 + fcbf6a1 commit 862210d

18 files changed

+145749
-137212
lines changed

lasso/bicounty.py

+41-5
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def add_opposite_direction_to_link(
6969
link_gdf['A'] = link_gdf['A'].astype(int)
7070
link_gdf['B'] = link_gdf['B'].astype(int)
7171
link_gdf = link_gdf.drop(['A_X','A_Y','B_X','B_Y'], axis=1)
72+
link_gdf = link_gdf.reset_index(drop=True)
7273

7374
return link_gdf
7475

@@ -109,6 +110,15 @@ def build_pnr_connections(
109110
Start actual process
110111
"""
111112

113+
orig_crs = roadway_network.nodes_df.crs # record original crs
114+
interim_crs = CRS('epsg:26915') # crs for nearest calculation
115+
116+
roadway_network.links_df = roadway_network.links_df.to_crs(interim_crs)
117+
roadway_network.shapes_df = roadway_network.shapes_df.to_crs(interim_crs)
118+
roadway_network.nodes_df = roadway_network.nodes_df.to_crs(interim_crs)
119+
roadway_network.nodes_df["X"] = roadway_network.nodes_df["geometry"].x
120+
roadway_network.nodes_df["Y"] = roadway_network.nodes_df["geometry"].y
121+
112122
# (1) add pnr nodes
113123
# read pnr parking location
114124
pnr_col = ['Zone','X','Y']
@@ -117,7 +127,7 @@ def build_pnr_connections(
117127
pnr_nodes_df,
118128
geometry=gpd.points_from_xy(pnr_nodes_df['X'], pnr_nodes_df['Y']),
119129
crs=parameters.output_proj)
120-
pnr_nodes_df = pnr_nodes_df.to_crs(roadway_network.nodes_df.crs)
130+
pnr_nodes_df = pnr_nodes_df.to_crs(interim_crs)
121131
pnr_nodes_df["X"] = pnr_nodes_df["geometry"].apply(lambda g: g.x)
122132
pnr_nodes_df["Y"] = pnr_nodes_df["geometry"].apply(lambda g: g.y)
123133

@@ -133,8 +143,11 @@ def build_pnr_connections(
133143

134144
# assign a model_node_id to pnr parking node
135145
pnr_nodes_df['model_node_id'] = pnr_nodes_df['Zone'] + roadway_network.nodes_df.model_node_id.max()
146+
# add pnr flag attribute
147+
pnr_nodes_df['pnr'] = 1
136148

137149
# add pnr parking nodes to node_df
150+
roadway_network.nodes_df['pnr'] = 0
138151
roadway_network.nodes_df = pd.concat([roadway_network.nodes_df, pnr_nodes_df.drop(['Zone'], axis=1)],
139152
sort = False,
140153
ignore_index = True)
@@ -149,13 +162,13 @@ def build_pnr_connections(
149162
].copy()
150163

151164
# for each pnr nodes, search for the nearest walk and bike nodes
152-
dr_wlk_nodes_df = dr_wlk_nodes_df.to_crs(CRS('epsg:26915'))
165+
dr_wlk_nodes_df = dr_wlk_nodes_df.to_crs(interim_crs)
153166
dr_wlk_nodes_df['X'] = dr_wlk_nodes_df.geometry.map(lambda g:g.x)
154167
dr_wlk_nodes_df['Y'] = dr_wlk_nodes_df.geometry.map(lambda g:g.y)
155168
dr_wlk_node_ref = dr_wlk_nodes_df[['X', 'Y']].values
156169
tree = cKDTree(dr_wlk_node_ref)
157170

158-
pnr_nodes_df = pnr_nodes_df.to_crs(CRS('epsg:26915'))
171+
pnr_nodes_df = pnr_nodes_df.to_crs(interim_crs)
159172
pnr_nodes_df['X'] = pnr_nodes_df['geometry'].apply(lambda p: p.x)
160173
pnr_nodes_df['Y'] = pnr_nodes_df['geometry'].apply(lambda p: p.y)
161174

@@ -173,6 +186,10 @@ def build_pnr_connections(
173186
pnr_link_gdf = add_opposite_direction_to_link(pnr_link_gdf, nodes_df=roadway_network.nodes_df, links_df=roadway_network.links_df)
174187

175188
# specify link variables
189+
pnr_link_gdf['model_link_id'] = max(roadway_network.links_df['model_link_id']) + pnr_link_gdf.index + 1
190+
pnr_link_gdf['shstGeometryId'] = pnr_link_gdf.index + 1
191+
pnr_link_gdf['shstGeometryId'] = pnr_link_gdf['shstGeometryId'].apply(lambda x: "pnr" + str(x))
192+
pnr_link_gdf['id'] = pnr_link_gdf['shstGeometryId']
176193
pnr_link_gdf['roadway'] = 'pnr'
177194
pnr_link_gdf['lanes'] = 1
178195
pnr_link_gdf['walk_access'] = 1
@@ -184,12 +201,17 @@ def build_pnr_connections(
184201
ignore_index = True)
185202
roadway_network.links_df.drop_duplicates(subset = ['A', 'B'], inplace = True)
186203

204+
# update shsapes_df
205+
pnr_shape_df = pnr_link_gdf.copy()
206+
pnr_shape_df = pnr_shape_df[['id', 'geometry']]
207+
roadway_network.shapes_df = pd.concat([roadway_network.shapes_df, pnr_shape_df]).reset_index(drop=True)
208+
187209

188210
# (3) build PNR TAZ connectors
189211
if build_pnr_taz_connector:
190212
# select centroids
191213
centroids_df = roadway_network.nodes_df[roadway_network.nodes_df.model_node_id.isin(parameters.taz_N_list)].copy()
192-
centroids_df = centroids_df.to_crs(CRS('epsg:26915'))
214+
centroids_df = centroids_df.to_crs(interim_crs)
193215

194216
# for each centroid, draw a buffer,
195217
# connect the centroid to all pnr parking nodes that fall in the buffer
@@ -210,7 +232,11 @@ def build_pnr_connections(
210232
pnr_connector_gdf = pd.DataFrame(list(zip(centroid_node_id, pnr_node_id)), columns=['A','B'])
211233
pnr_connector_gdf = add_opposite_direction_to_link(pnr_connector_gdf, nodes_df=roadway_network.nodes_df, links_df=roadway_network.links_df)
212234

213-
# specify link variables
235+
# specify link variables
236+
pnr_connector_gdf['model_link_id'] = max(roadway_network.links_df['model_link_id']) + pnr_connector_gdf.index + 1
237+
pnr_connector_gdf['shstGeometryId'] = pnr_connector_gdf.index + 1
238+
pnr_connector_gdf['shstGeometryId'] = pnr_connector_gdf['shstGeometryId'].apply(lambda x: "pnrtaz" + str(x))
239+
pnr_connector_gdf['id'] = pnr_connector_gdf['shstGeometryId']
214240
pnr_connector_gdf['roadway'] = "pnr"
215241
pnr_connector_gdf['lanes'] = 1
216242
pnr_connector_gdf['drive_access'] = 1
@@ -221,5 +247,15 @@ def build_pnr_connections(
221247
ignore_index = True)
222248
roadway_network.links_df.drop_duplicates(subset = ['A', 'B'], inplace = True)
223249

250+
# update shsapes_df
251+
pnr_connector_shape_df = pnr_connector_gdf.copy()
252+
pnr_connector_shape_df = pnr_connector_shape_df[['id', 'geometry']]
253+
roadway_network.shapes_df = pd.concat([roadway_network.shapes_df, pnr_connector_shape_df]).reset_index(drop=True)
254+
255+
roadway_network.links_df = roadway_network.links_df.to_crs(orig_crs)
256+
roadway_network.shapes_df = roadway_network.shapes_df.to_crs(orig_crs)
257+
roadway_network.nodes_df = roadway_network.nodes_df.to_crs(orig_crs)
258+
roadway_network.nodes_df["X"] = roadway_network.nodes_df["geometry"].x
259+
roadway_network.nodes_df["Y"] = roadway_network.nodes_df["geometry"].y
224260

225261
return roadway_network

lasso/mtc.py

+36-7
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ def calculate_facility_type(
103103
join_gdf["oneWay"].fillna("", inplace = True)
104104
join_gdf["oneWay"] = join_gdf["oneWay"].apply(lambda x: "NA" if x in [None, np.nan, float('nan')] else x)
105105
join_gdf["oneWay"] = join_gdf["oneWay"].apply(lambda x: str(x) if type(x) == bool else x)
106+
join_gdf["oneWay"] = join_gdf["oneWay"].apply(lambda x: str(x) if type(x) == int else x)
106107
join_gdf["oneWay"] = join_gdf["oneWay"].apply(lambda x: x if type(x) == str else ','.join(map(str, x)))
107108
join_gdf["oneWay_binary"] = join_gdf["oneWay"].apply(lambda x: 0 if "False" in x else 1)
108109

@@ -286,6 +287,8 @@ def determine_number_of_lanes(
286287
"""
287288
mtc_osm_df = pd.read_csv(mtc_osm_lanes_attributes)
288289
mtc_osm_df = mtc_osm_df.rename(columns = {"min_lanes": "osm_min_lanes", "max_lanes": "osm_max_lanes"})
290+
mtc_osm_df.loc[mtc_osm_df['oneWay'].str.contains('False'), 'osm_min_lanes'] = np.ceil(mtc_osm_df['osm_min_lanes'] / 2)
291+
mtc_osm_df.loc[mtc_osm_df['oneWay'].str.contains('False'), 'osm_max_lanes'] = np.ceil(mtc_osm_df['osm_max_lanes'] / 2)
289292

290293
sj_osm_df = pd.read_csv(sj_osm_lanes_attributes)
291294
sj_osm_df = sj_osm_df.rename(columns = {'osm_lanes_min' : 'osm_min_lanes', 'osm_lanes_max':'osm_max_lanes'})
@@ -1710,7 +1713,7 @@ def roadway_standard_to_mtc_network(
17101713
roadway_network.links_df["assignable"]
17111714
)
17121715

1713-
roadway_network = calculate_cntype(roadway_network, parameters)
1716+
roadway_network = calculate_cntype(roadway_network, parameters, overwrite=True)
17141717
roadway_network = calculate_transit(roadway_network, parameters)
17151718
roadway_network = calculate_useclass(roadway_network, parameters)
17161719
roadway_network = calculate_facility_type(roadway_network, parameters, update_network_variable = True)
@@ -1735,6 +1738,8 @@ def roadway_standard_to_mtc_network(
17351738
on = "id"
17361739
)
17371740

1741+
roadway_network.links_mtc_df = gpd.GeoDataFrame(roadway_network.links_mtc_df, geometry=roadway_network.links_mtc_df.geometry)
1742+
roadway_network.nodes_mtc_df = gpd.GeoDataFrame(roadway_network.nodes_mtc_df, geometry=roadway_network.nodes_mtc_df.geometry)
17381743
roadway_network.links_mtc_df.crs = roadway_network.crs
17391744
roadway_network.nodes_mtc_df.crs = roadway_network.crs
17401745
WranglerLogger.info("Setting Coordinate Reference System to {}".format(output_proj))
@@ -1747,9 +1752,13 @@ def roadway_standard_to_mtc_network(
17471752
roadway_network.nodes_mtc_df["Y"] = roadway_network.nodes_mtc_df.geometry.apply(
17481753
lambda g: g.y
17491754
)
1755+
# roadway_network.nodes_mtc_df["pnr"]=roadway_network.nodes_mtc_df["pnr"].fillna(0)
1756+
WranglerLogger.info("Setting PNR value and converting it to string")
1757+
roadway_network.nodes_mtc_df["pnr"] = np.where(roadway_network.nodes_mtc_df['pnr'].isin([0,'',' ']), '0.0', '1.0')
17501758

17511759
# CUBE expect node id to be N
17521760
roadway_network.nodes_mtc_df.rename(columns={"model_node_id": "N"}, inplace=True)
1761+
# roadway_network.nodes_mtc_df['model_node_id']=roadway_network.nodes_mtc_df['N']
17531762

17541763
return roadway_network
17551764

@@ -1860,17 +1869,19 @@ def route_properties_gtfs_to_cube(
18601869

18611870
trip_df["agency_id"].fillna("", inplace = True)
18621871

1872+
trip_df['dir_shp_index'] = trip_df.groupby(["TM2_operator", "route_id", "tod_name"]).cumcount()
1873+
18631874
trip_df["NAME"] = trip_df.apply(
18641875
lambda x: str(x.TM2_operator)
18651876
+ "_"
18661877
+ str(x.route_id)
18671878
+ "_"
1868-
+ x.tod_name
1879+
+ str(x.tod_name)
18691880
+ "_"
18701881
+ "d"
1871-
+ str(int(x.direction_id))
1882+
+ str(int(x.dir_shp_index))
18721883
+ "_s"
1873-
+ x.shape_id,
1884+
+ str(x.shape_id),
18741885
axis=1,
18751886
)
18761887

@@ -1929,11 +1940,14 @@ def cube_format(transit_network, row):
19291940
s += "\n OPERATOR={},".format(int(row.TM2_operator) if ~math.isnan(row.TM2_operator) else 99)
19301941
s += '\n SHORTNAME=\"%s",' % (row.route_short_name,)
19311942
s += '\n VEHICLETYPE={},'.format(row.vehtype_num)
1932-
if row.TM2_line_haul_name in ["Light rail", "Heavy rail", "Commuter rail", "Ferry service"]:
1943+
if row.TM2_line_haul_name in ["Light rail", "Heavy rail", "Commuter rail", "Ferry service", "Cable Car"]:
19331944
add_nntime = True
19341945
else:
19351946
add_nntime = False
1936-
s += "\n N={}".format(transit_network.shape_gtfs_to_cube(row, add_nntime))
1947+
nodes, runtime = transit_network.shape_gtfs_to_cube(row, add_nntime)
1948+
if add_nntime:
1949+
s += '\n RUNTIME={},'.format(runtime)
1950+
s += "\n N={}".format(nodes)
19371951

19381952
# TODO: need NNTIME, ACCESS_C
19391953

@@ -1952,6 +1966,19 @@ def write_as_cube_lin(
19521966
outpath: File location for output cube line file.
19531967
19541968
"""
1969+
1970+
transit_network.feed.trips['trip_id'] = transit_network.feed.trips['trip_id'].astype(int)
1971+
transit_network.feed.trips['shape_id'] = transit_network.feed.trips['shape_id'].astype(int)
1972+
1973+
transit_network.feed.stop_times['trip_id'] = transit_network.feed.stop_times['trip_id'].astype(int)
1974+
transit_network.feed.stop_times['stop_id'] = transit_network.feed.stop_times['stop_id'].astype(float).astype(int)
1975+
1976+
transit_network.feed.shapes['shape_id'] = transit_network.feed.shapes['shape_id'].astype(int)
1977+
1978+
transit_network.feed.stops['stop_id'] = transit_network.feed.stops['stop_id'].astype(float).astype(int)
1979+
1980+
transit_network.feed.frequencies['trip_id'] = transit_network.feed.frequencies['trip_id'].astype(int)
1981+
19551982
if not outpath:
19561983
outpath = os.path.join(parameters.scratch_location,"outtransit.lin")
19571984
trip_cube_df = route_properties_gtfs_to_cube(transit_network, parameters, outpath)
@@ -2151,7 +2178,9 @@ def _is_express_bus(x):
21512178
if (x.route_short_name.startswith("J")) | (x.route_short_name.startswith("Lynx")):
21522179
return 1
21532180
if x.agency_name == "SolTrans":
2154-
if (x.route_short_name in ["80", "92", "78"]) | (x.route_long_name in ["80", "92", "78"]):
2181+
if ((x.route_short_name in ["80", "92", "78","Green","Blue","Red"]) |
2182+
(x.route_long_name in ["80", "92", "78","Green","Blue","Red"])
2183+
):
21552184
return 1
21562185
if x.agency_name == "Vine (Napa County)":
21572186
if x.route_short_name in ["29"]:

lasso/parameters.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
import pyproj
23
from .logger import WranglerLogger
34

45

@@ -740,7 +741,7 @@ def __init__(self, **kwargs):
740741
#MTC
741742
'name',
742743
"distance",
743-
#"roadway",
744+
"roadway",
744745
#"name",
745746
#MC
746747
#"shape_id",
@@ -769,6 +770,7 @@ def __init__(self, **kwargs):
769770
"managed",
770771
"bus_only",
771772
"rail_only",
773+
"pnr",
772774
#MTC
773775
"assignable",
774776
"cntype",
@@ -795,6 +797,8 @@ def __init__(self, **kwargs):
795797
#bi-county
796798
"nmt2010",
797799
"nmt2020",
800+
"BRT",
801+
"has_transit"
798802
]
799803

800804
self.output_link_shp = os.path.join(self.scratch_location, "links.shp")
@@ -820,7 +824,7 @@ def __init__(self, **kwargs):
820824

821825
self.fare_matrix_output_variables = ["faresystem", "origin_farezone", "destination_farezone", "price"]
822826

823-
self.zones = 4756
827+
self.zones = 6593
824828
"""
825829
Create all the possible headway variable combinations based on the cube time periods setting
826830
"""
@@ -906,6 +910,8 @@ def __init__(self, **kwargs):
906910
#bi-county
907911
"nmt2010",
908912
"nmt2020",
913+
"BRT",
914+
"has_transit"
909915
]
910916

911917
self.float_col = [
@@ -921,6 +927,7 @@ def __init__(self, **kwargs):
921927
self.string_col = [
922928
"osm_node_id",
923929
"name",
930+
"pnr",
924931
"roadway",
925932
"shstGeometryId",
926933
"access_AM",
@@ -937,4 +944,10 @@ def __init__(self, **kwargs):
937944

938945
self.drive_buffer = 6
939946

947+
#self.network_build_crs = CRS("EPSG:2875")
948+
#self.project_card_crs = CRS("EPSG:4326")
949+
#self.transformer = pyproj.Transformer.from_crs(
950+
# self.network_build_crs, self.project_card_crs, always_xy=True
951+
#)
952+
940953
self.__dict__.update(kwargs)

lasso/project.py

+23-5
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ def create_project(
165165
roadway_node_changes: Optional[DataFrame] = None,
166166
transit_changes: Optional[CubeTransit] = None,
167167
base_roadway_network: Optional[RoadwayNetwork] = None,
168+
base_transit_network: Optional[StandardTransit] = None,
168169
base_cube_transit_network: Optional[CubeTransit] = None,
169170
build_cube_transit_network: Optional[CubeTransit] = None,
170171
project_name: Optional[str] = None,
@@ -403,6 +404,8 @@ def create_project(
403404
gtfs_feed_dir=base_transit_dir,
404405
parameters=parameters
405406
)
407+
elif base_transit_network:
408+
base_transit_network = base_transit_network
406409
else:
407410
msg = "No base transit network."
408411
WranglerLogger.info(msg)
@@ -695,16 +698,23 @@ def emme_id_to_wrangler_id(emme_link_change_df, emme_node_change_df, emme_transi
695698

696699
# get new emme nodes
697700
new_emme_node_id_list = [
698-
n for n in emme_node_change_df['emme_id'] if n not in emme_node_id_crosswalk_df['emme_node_id']
701+
n for n in emme_node_change_df['emme_id'].to_list() if n not in emme_node_id_crosswalk_df['emme_node_id'].to_list()
699702
]
700703
WranglerLogger.info('New emme node id list {}'.format(new_emme_node_id_list))
701704
new_wrangler_node = emme_node_id_crosswalk_df['model_node_id'].max()
702705

703706
# add crosswalk for new emme nodes
704707
for new_emme_node in new_emme_node_id_list:
705-
new_wrangler_node = new_wrangler_node + 1
706-
emme_node_id_dict.update({new_emme_node : new_wrangler_node})
707-
708+
if new_emme_node in emme_node_id_dict.keys():
709+
msg = "new node id {} has already been added to the crosswalk".format(new_emme_node)
710+
WranglerLogger.error(msg)
711+
raise ValueError(msg)
712+
else:
713+
new_wrangler_node = new_wrangler_node + 1
714+
emme_node_id_dict.update({new_emme_node : new_wrangler_node})
715+
new_emme_node_id_crosswalk_df = pd.DataFrame(emme_node_id_dict.items(), columns=['emme_node_id', 'model_node_id'])
716+
new_emme_node_id_crosswalk_df.to_csv(emme_node_id_crosswalk_file, index=False)
717+
708718
# for nodes update model_node_id
709719
emme_node_change_df['model_node_id'] = emme_node_change_df['emme_id'].map(emme_node_id_dict).fillna(0)
710720

@@ -873,7 +883,7 @@ def determine_roadway_network_changes_compatibility(
873883
roadway_link_changes.rename(columns=dbf_to_net_dict, inplace=True)
874884

875885
for c in roadway_node_changes.columns:
876-
if (c not in log_to_net_df["log"].tolist() + log_to_net_df["net"].tolist()) & (c not in ["A", "B"]):
886+
if (c not in log_to_net_df["log"].tolist() + log_to_net_df["net"].tolist()) & (c not in ["A", "B", "X", "Y"]):
877887
roadway_node_changes.rename(columns={c : c.lower()}, inplace=True)
878888
roadway_node_changes.rename(columns=log_to_net_dict, inplace=True)
879889
roadway_node_changes.rename(columns=dbf_to_net_dict, inplace=True)
@@ -1083,6 +1093,8 @@ def _process_node_additions(node_add_df):
10831093

10841094
node_add_df = node_add_df.drop(["operation_final"], axis=1)
10851095

1096+
node_add_df = node_add_df.apply(_reproject_coordinates, axis=1)
1097+
10861098
for x in node_add_df.columns:
10871099
node_add_df[x] = node_add_df[x].astype(self.base_roadway_network.nodes_df[x].dtype)
10881100

@@ -1093,6 +1105,12 @@ def _process_node_additions(node_add_df):
10931105

10941106
return add_nodes_dict_list
10951107

1108+
def _reproject_coordinates(row):
1109+
reprojected_x, reprojected_y = self.parameters.transformer.transform(row['X'], row['Y'])
1110+
row['X'] = reprojected_x
1111+
row['Y'] = reprojected_y
1112+
return row
1113+
10961114
def _process_single_link_change(change_row, changeable_col):
10971115
""""""
10981116

0 commit comments

Comments
 (0)