diff --git a/Wrangler/Factor.py b/Wrangler/Factor.py index 4b1be15..4479448 100644 --- a/Wrangler/Factor.py +++ b/Wrangler/Factor.py @@ -1,13 +1,15 @@ -__all__ = ['Factor'] +__all__ = ["Factor"] + class Factor(dict): """ Transit config (or "factor") All attributes are stored in the dictionary """ + def __init__(self): dict.__init__(self) - self.comment='' + self.comment = "" def __repr__(self): s = "FACTOR " diff --git a/Wrangler/Faresystem.py b/Wrangler/Faresystem.py index 50c009d..b343436 100644 --- a/Wrangler/Faresystem.py +++ b/Wrangler/Faresystem.py @@ -2,6 +2,7 @@ from .Logger import WranglerLogger + class Faresystem(collections.OrderedDict): """ Faresystem definition. A faresystem is a Cube Public Transport construct representing a single faresystem. @@ -12,12 +13,14 @@ class Faresystem(collections.OrderedDict): def __init__(self): collections.OrderedDict.__init__(self) - self.fare_zone_mat = {} # origin fare zone (int) => dest fare zone (int) => fare (float) + self.fare_zone_mat = ( + {} + ) # origin fare zone (int) => dest fare zone (int) => fare (float) def __repr__(self): s = "FARESYSTEM " - fields = ['%s=%s' % (k,v) for k,v in self.items()] + fields = ["%s=%s" % (k, v) for k, v in self.items()] s += ", ".join(fields) return s @@ -28,7 +31,7 @@ def getFareMatrixId(self): """ if "FAREMATRIX" in self.keys(): faremat = self["FAREMATRIX"] - return faremat[faremat.rfind(".")+1:] + return faremat[faremat.rfind(".") + 1 :] return None def getId(self): @@ -50,11 +53,17 @@ def getFareZoneMatrixLines(self): Returns farezone to farezone string for writing. """ s = "" - if len(self.fare_zone_mat) == 0: return s + if len(self.fare_zone_mat) == 0: + return s for farezone_i in sorted(self.fare_zone_mat.keys()): for farezone_j in sorted(self.fare_zone_mat[farezone_i].keys()): - s += "{} {} {} {:.4f}\n".format(self.getId(), farezone_i, farezone_j, self.fare_zone_mat[farezone_i][farezone_j]) + s += "{} {} {} {:.4f}\n".format( + self.getId(), + farezone_i, + farezone_j, + self.fare_zone_mat[farezone_i][farezone_j], + ) return s @staticmethod @@ -64,24 +73,27 @@ def readFareZoneMatrixFile(farezonematrix_file, faresystems_dict): and updates the given dictionary of faresystems. """ WranglerLogger.debug("Reading {}".format(farezonematrix_file)) - f = open(farezonematrix_file, 'r') + f = open(farezonematrix_file, "r") while True: line = f.readline().strip() - if not line: break + if not line: + break - row = re.split("[\s+\,]", line) # split on whitespace or comma + row = re.split("[\s+\,]", line) # split on whitespace or comma - farematid = row[0] - farezone_i = int(row[1]) - farezone_j = int(row[2]) + farematid = row[0] + farezone_i = int(row[1]) + farezone_j = int(row[2]) for fare_str in row[3:]: fare_val = float(fare_str) # not efficient but it's ok for faresystem_id in faresystems_dict.keys(): if faresystems_dict[faresystem_id].getFareMatrixId() == farematid: - faresystems_dict[faresystem_id].setFarezoneODPair(farezone_i, farezone_j, fare_val) + faresystems_dict[faresystem_id].setFarezoneODPair( + farezone_i, farezone_j, fare_val + ) # print("faresystem {} farematid {} i={} j={} fare={}".format( # faresystem_id, farematid, farezone_i, farezone_j, fare_val)) farezone_j += 1 - f.close() \ No newline at end of file + f.close() diff --git a/Wrangler/HighwayNetwork.py b/Wrangler/HighwayNetwork.py index ea9eb5a..496f2da 100644 --- a/Wrangler/HighwayNetwork.py +++ b/Wrangler/HighwayNetwork.py @@ -1,17 +1,19 @@ import collections, csv, os, re, shutil, subprocess, time -from socket import gethostname, getfqdn +from socket import gethostname, getfqdn from .HwySpecsRTP import HwySpecsRTP from .Logger import WranglerLogger from .Network import Network from .NetworkException import NetworkException -__all__ = ['HighwayNetwork'] +__all__ = ["HighwayNetwork"] + class HighwayNetwork(Network): """ Representation of a roadway network. """ + cube_hostnames = None @staticmethod @@ -20,27 +22,45 @@ def getCubeHostnames(): Cube hostnames in Y:\COMMPATH\HostnamesWithCube.txt """ # got them already - if HighwayNetwork.cube_hostnames: return HighwayNetwork.cube_hostnames + if HighwayNetwork.cube_hostnames: + return HighwayNetwork.cube_hostnames - fqdn = getfqdn().lower() # fully qualified domain name + fqdn = getfqdn().lower() # fully qualified domain name # at mtc, assume cube license is available if fqdn.endswith("mtc.ca.gov"): - HighwayNetwork.cube_hostnames = [ gethostname().lower() ] + HighwayNetwork.cube_hostnames = [gethostname().lower()] return HighwayNetwork.cube_hostnames # read them HighwayNetwork.cube_hostnames = [] f = open(r"Y:\COMMPATH\HostnamesWithCube.txt") for line in f: - if line[0] == "#": continue - HighwayNetwork.cube_hostnames.append(line.split()[0]) # use the first token of non-comment lines + if line[0] == "#": + continue + HighwayNetwork.cube_hostnames.append( + line.split()[0] + ) # use the first token of non-comment lines f.close() return HighwayNetwork.cube_hostnames - def __init__(self, modelType, modelVersion, basenetworkpath, networkBaseDir=None, networkProjectSubdir=None, - networkSeedSubdir=None, networkPlanSubdir=None, isTiered=False, tag=None, - hwyspecsdir=None, hwyspecs=None, tempdir=None, networkName=None, tierNetworkName=None): + def __init__( + self, + modelType, + modelVersion, + basenetworkpath, + networkBaseDir=None, + networkProjectSubdir=None, + networkSeedSubdir=None, + networkPlanSubdir=None, + isTiered=False, + tag=None, + hwyspecsdir=None, + hwyspecs=None, + tempdir=None, + networkName=None, + tierNetworkName=None, + ): """ *basenetworkpath* should be a starting point for this network, and include a ``FREEFLOW.net``, as well as ``turns[am,pm,op].pen`` files. @@ -51,37 +71,54 @@ def __init__(self, modelType, modelVersion, basenetworkpath, networkBaseDir=None (an alternative to `FREEFLOW.net`.) *tag*: when not *isTiered*, a tag can optionally be used for cloning the base network - + *hwyspecs*, if passed in, should be an instance of :py:class:`HwySpecsRTP`. It is only used for logging. """ - Network.__init__(self, modelType, modelVersion, networkBaseDir, networkProjectSubdir, networkSeedSubdir, - networkPlanSubdir, networkName) - + Network.__init__( + self, + modelType, + modelVersion, + networkBaseDir, + networkProjectSubdir, + networkSeedSubdir, + networkPlanSubdir, + networkName, + ) + if isTiered: - (head,tail) = os.path.split(basenetworkpath) - self.applyBasenetwork(head,tail,None, tierNetworkName) + (head, tail) = os.path.split(basenetworkpath) + self.applyBasenetwork(head, tail, None, tierNetworkName) else: self.applyingBasenetwork = True - self.cloneAndApplyProject(networkdir=basenetworkpath,tempdir=tempdir, projtype='seed', tag=tag) + self.cloneAndApplyProject( + networkdir=basenetworkpath, tempdir=tempdir, projtype="seed", tag=tag + ) # keep a reference of the hwyspecsrtp for logging self.hwyspecsdir = hwyspecsdir self.hwyspecs = hwyspecs - + def applyBasenetwork(self, parentdir, networkdir, gitdir, tierNetworkName): - + # copy the base network file to my workspace - tierNetwork = os.path.join(parentdir,networkdir,tierNetworkName if tierNetworkName else "FREEFLOW.net") + tierNetwork = os.path.join( + parentdir, + networkdir, + tierNetworkName if tierNetworkName else "FREEFLOW.net", + ) WranglerLogger.debug("Using tier network %s" % tierNetwork) - shutil.copyfile(tierNetwork,"FREEFLOW.BLD") - for filename in ["turnsam.pen", "turnspm.pen", "turnsop.pen", "tolls.csv"]: + shutil.copyfile(tierNetwork, "FREEFLOW.BLD") + for filename in ["turnsam.pen", "turnspm.pen", "turnsop.pen", "tolls.csv"]: try: - shutil.copyfile(os.path.join(parentdir,networkdir,filename), filename) + shutil.copyfile(os.path.join(parentdir, networkdir, filename), filename) except: - WranglerLogger.warn("Couldn't find file {} -- Touching an empty file".format(filename)) + WranglerLogger.warn( + "Couldn't find file {} -- Touching an empty file".format(filename) + ) # touch a blank file - with open(filename, 'a'): os.utime(filename, None) + with open(filename, "a"): + os.utime(filename, None) # done self.applyingBasenetwork = False @@ -91,18 +128,28 @@ def saveNetworkFiles(self, suffix, to_suffix): Since roadway networks are not stored in memory but in files, this is useful for when the network builder is doing something tricky. """ - for filename in ["FREEFLOW.BLD", "turnsam.pen", "turnspm.pen", "turnsop.pen", "tolls.csv"]: + for filename in [ + "FREEFLOW.BLD", + "turnsam.pen", + "turnspm.pen", + "turnsop.pen", + "tolls.csv", + ]: if to_suffix: shutil.copy2(src=filename, dst="{}{}".format(filename, suffix)) - WranglerLogger.debug("Copying {:20} to {}{}".format(filename, filename, suffix)) + WranglerLogger.debug( + "Copying {:20} to {}{}".format(filename, filename, suffix) + ) else: shutil.copy2(src="{}{}".format(filename, suffix), dst=filename) - WranglerLogger.debug("Copying {:20} to {}".format(filename+suffix, filename)) + WranglerLogger.debug( + "Copying {:20} to {}".format(filename + suffix, filename) + ) def applyProject(self, parentdir, networkdir, gitdir, projectsubdir=None, **kwargs): """ Applies a roadway project by calling ``runtpp`` on the ``apply.s`` script. - By convention, the input to ``apply.s`` is ``FREEFLOW.BLD`` and the output is + By convention, the input to ``apply.s`` is ``FREEFLOW.BLD`` and the output is ``FREEFLOW.BLDOUT`` which is copied to ``FREEFLOW.BLD`` at the end of ``apply.s`` See :py:meth:`Wrangler.Network.applyProject` for argument details. @@ -110,51 +157,67 @@ def applyProject(self, parentdir, networkdir, gitdir, projectsubdir=None, **kwar # special case: base network if self.applyingBasenetwork: self.applyBasenetwork(parentdir, networkdir, gitdir, tierNetworkName=None) - self.logProject(gitdir=gitdir, - projectname=(networkdir + "\\" + projectsubdir if projectsubdir else networkdir), - projectdesc="Base network") + self.logProject( + gitdir=gitdir, + projectname=( + networkdir + "\\" + projectsubdir if projectsubdir else networkdir + ), + projectdesc="Base network", + ) return - + if projectsubdir: - applyDir = os.path.join(parentdir, networkdir, projectsubdir) - applyScript = "apply.s" - descfilename = os.path.join(parentdir, networkdir, projectsubdir, "desc.txt") - turnsfilename = os.path.join(parentdir, networkdir, projectsubdir, "turns.pen") - tollsfilename = os.path.join(parentdir, networkdir, projectsubdir, "tolls.csv") + applyDir = os.path.join(parentdir, networkdir, projectsubdir) + applyScript = "apply.s" + descfilename = os.path.join( + parentdir, networkdir, projectsubdir, "desc.txt" + ) + turnsfilename = os.path.join( + parentdir, networkdir, projectsubdir, "turns.pen" + ) + tollsfilename = os.path.join( + parentdir, networkdir, projectsubdir, "tolls.csv" + ) else: - applyDir = os.path.join(parentdir, networkdir) - applyScript = "apply.s" - descfilename = os.path.join(parentdir, networkdir, "desc.txt") + applyDir = os.path.join(parentdir, networkdir) + applyScript = "apply.s" + descfilename = os.path.join(parentdir, networkdir, "desc.txt") turnsfilename = os.path.join(parentdir, networkdir, "turns.pen") tollsfilename = os.path.join(parentdir, networkdir, "tolls.csv") # read the description desc = None try: - desc = open(descfilename,'r').read() + desc = open(descfilename, "r").read() except: pass - + # move the FREEFLOW.BLD into place - shutil.move("FREEFLOW.BLD", os.path.join(applyDir,"FREEFLOW.BLD")) + shutil.move("FREEFLOW.BLD", os.path.join(applyDir, "FREEFLOW.BLD")) # WranglerLogger.debug("HighwayNetwork.applyProject() received kwargs:{}".format(kwargs)) # retry in case of a license error NUM_RETRIES = 5 - for attempt in range(1,NUM_RETRIES+1): + for attempt in range(1, NUM_RETRIES + 1): # dispatch it, cube license hostname = gethostname().lower() if hostname not in HighwayNetwork.getCubeHostnames(): print("Dispatching cube script to taraval from %s".format(hostname)) - f = open(os.path.join(applyDir,'runtpp_dispatch.tmp'), 'w') + f = open(os.path.join(applyDir, "runtpp_dispatch.tmp"), "w") f.write("runtpp " + applyScript + "\n") f.close() - (cuberet, cubeStdout, cubeStderr) = self._runAndLog("Y:/champ/util/bin/dispatch.bat runtpp_dispatch.tmp taraval", run_dir=applyDir, logStdoutAndStderr=True, env=kwargs) + (cuberet, cubeStdout, cubeStderr) = self._runAndLog( + "Y:/champ/util/bin/dispatch.bat runtpp_dispatch.tmp taraval", + run_dir=applyDir, + logStdoutAndStderr=True, + env=kwargs, + ) else: - (cuberet, cubeStdout, cubeStderr) = self._runAndLog(cmd="runtpp "+applyScript, run_dir=applyDir, env=kwargs) - + (cuberet, cubeStdout, cubeStderr) = self._runAndLog( + cmd="runtpp " + applyScript, run_dir=applyDir, env=kwargs + ) nodemerge = re.compile("NODEMERGE: \d+") linkmerge = re.compile("LINKMERGE: \d+-\d+") @@ -162,17 +225,22 @@ def applyProject(self, parentdir, networkdir, gitdir, projectsubdir=None, **kwar license_error = False for line in cubeStdout: line = line.rstrip() - if re.match(nodemerge,line): continue - if re.match(linkmerge,line): continue - if line=="RUNTPP: Licensing error": license_error = True + if re.match(nodemerge, line): + continue + if re.match(linkmerge, line): + continue + if line == "RUNTPP: Licensing error": + license_error = True WranglerLogger.debug(line) - + # retry on license error if license_error: WranglerLogger.warn("Received license error") if attempt == NUM_RETRIES: WranglerLogger.fatal("Out of retry attempts") - raise NetworkException("HighwayNetwork applyProject failed from Licensing error") + raise NetworkException( + "HighwayNetwork applyProject failed from Licensing error" + ) # retry WranglerLogger.debug("Retrying {} ...".format(attempt)) @@ -182,32 +250,42 @@ def applyProject(self, parentdir, networkdir, gitdir, projectsubdir=None, **kwar if cuberet != 0 and cuberet != 1 and len(cubeStdout) > 1: # cuberet may be wrong -- check last stdout - WranglerLogger.debug("checking cubeStdout[-1]: {}".format(cubeStdout[-1])) + WranglerLogger.debug( + "checking cubeStdout[-1]: {}".format(cubeStdout[-1]) + ) # WranglerLogger.debug("match: {}".format(re.match(cube_success,cubeStdout[-1]))) - if re.match(cube_success,cubeStdout[-1]): - WranglerLogger.debug("Overriding cuberet {} with 0 due to last cubeStdout line".format(cuberet)) + if re.match(cube_success, cubeStdout[-1]): + WranglerLogger.debug( + "Overriding cuberet {} with 0 due to last cubeStdout line".format( + cuberet + ) + ) cuberet = 0 if cuberet != 0 and cuberet != 1: WranglerLogger.debug("cubeStdout: {}".format(cubeStdout)) - WranglerLogger.fatal("FAIL! Project: {} cuberet={}".format(applyScript, cuberet)) - raise NetworkException("HighwayNetwork applyProject failed; see log file") + WranglerLogger.fatal( + "FAIL! Project: {} cuberet={}".format(applyScript, cuberet) + ) + raise NetworkException( + "HighwayNetwork applyProject failed; see log file" + ) else: # success break # move it back - shutil.move(os.path.join(applyDir,"FREEFLOW.BLD"), "FREEFLOW.BLD") + shutil.move(os.path.join(applyDir, "FREEFLOW.BLD"), "FREEFLOW.BLD") # append new turn penalty file to mine if os.path.exists(turnsfilename): for filename in ["turnsam.pen", "turnspm.pen", "turnsop.pen"]: - newturnpens = open(turnsfilename, 'r').read() - turnfile = open(filename, 'a') + newturnpens = open(turnsfilename, "r").read() + turnfile = open(filename, "a") turnfile.write(newturnpens) turnfile.close() - WranglerLogger.debug("Appending turn penalties from "+turnsfilename) + WranglerLogger.debug("Appending turn penalties from " + turnsfilename) # merge tolls.csv if os.path.exists(tollsfilename): @@ -216,32 +294,44 @@ def applyProject(self, parentdir, networkdir, gitdir, projectsubdir=None, **kwar WranglerLogger.debug("") WranglerLogger.debug("") - year = None - county = None - if (networkdir==self.hwyspecsdir and - self.hwyspecs and - projectsubdir in self.hwyspecs.projectdict): - year = self.hwyspecs.projectdict[projectsubdir]["MOD YEAR"] - county = self.hwyspecs.projectdict[projectsubdir]["County"] - desc = (self.hwyspecs.projectdict[projectsubdir]["Facility"] + ", " + - self.hwyspecs.projectdict[projectsubdir]["Action"] + ", " + - self.hwyspecs.projectdict[projectsubdir]["Span"]) - - return self.logProject(gitdir=gitdir, - projectname=(networkdir + "\\" + projectsubdir if projectsubdir else networkdir), - year=year, projectdesc=desc, county=county) + year = None + county = None + if ( + networkdir == self.hwyspecsdir + and self.hwyspecs + and projectsubdir in self.hwyspecs.projectdict + ): + year = self.hwyspecs.projectdict[projectsubdir]["MOD YEAR"] + county = self.hwyspecs.projectdict[projectsubdir]["County"] + desc = ( + self.hwyspecs.projectdict[projectsubdir]["Facility"] + + ", " + + self.hwyspecs.projectdict[projectsubdir]["Action"] + + ", " + + self.hwyspecs.projectdict[projectsubdir]["Span"] + ) + + return self.logProject( + gitdir=gitdir, + projectname=( + networkdir + "\\" + projectsubdir if projectsubdir else networkdir + ), + year=year, + projectdesc=desc, + county=county, + ) def mergeTolls(self, tollsfile, newtollsfile): """ Merge the given tolls file with the existing. """ - WranglerLogger.debug("mergeTolls({},{}) called".format(tollsfile, newtollsfile)) + WranglerLogger.debug("mergeTolls({},{}) called".format(tollsfile, newtollsfile)) # read the original file -- fac_index is the key tolls_config = collections.OrderedDict() - tolls = open(tollsfile, 'r') + tolls = open(tollsfile, "r") tolls_reader = csv.reader(tolls, skipinitialspace=True) - fieldnames = next(tolls_reader) + fieldnames = next(tolls_reader) # not using csv.DictReader because in python2, it doesn't read an ordered dict :( for row in tolls_reader: row_dict = collections.OrderedDict(zip(fieldnames, row)) @@ -250,18 +340,25 @@ def mergeTolls(self, tollsfile, newtollsfile): tolls.close() # read the new file -- replace if fac_index matches - newtolls = open(newtollsfile, 'r') - new_reader = csv.reader(newtolls, skipinitialspace=True) + newtolls = open(newtollsfile, "r") + new_reader = csv.reader(newtolls, skipinitialspace=True) new_fieldnames = next(new_reader) # they need to match, or fieldnames can be longer if fieldnames == new_fieldnames: # excellent pass - elif len(new_fieldnames) < len(fieldnames) and fieldnames[:len(new_fieldnames)] == new_fieldnames: + elif ( + len(new_fieldnames) < len(fieldnames) + and fieldnames[: len(new_fieldnames)] == new_fieldnames + ): # ok -- some columns at end can be blank pass else: - raise NetworkException("Toll file {} has different fieldnames ({}) than expected ({})".format(newtollsfile, new_fieldnames, fieldnames)) + raise NetworkException( + "Toll file {} has different fieldnames ({}) than expected ({})".format( + newtollsfile, new_fieldnames, fieldnames + ) + ) for row in new_reader: row_dict = collections.OrderedDict(zip(fieldnames, row)) @@ -269,127 +366,187 @@ def mergeTolls(self, tollsfile, newtollsfile): newtolls.close() # write it out - tolls = open(tollsfile, mode='w', newline='') # newline arg passed because of https://docs.python.org/3/library/csv.html#id3 + tolls = open( + tollsfile, mode="w", newline="" + ) # newline arg passed because of https://docs.python.org/3/library/csv.html#id3 tolls_writer = csv.writer(tolls) tolls_writer.writerow(fieldnames) for fac_index, row in tolls_config.items(): tolls_writer.writerow(row.values()) tolls.close() - def validateTurnPens(self, CubeNetFile, turnPenReportFile=None, suggestCorrectLink=True): + def validateTurnPens( + self, CubeNetFile, turnPenReportFile=None, suggestCorrectLink=True + ): import Cube - turnpens_files = ['turnsam.pen','turnsop.pen','turnspm.pen'] - pen_regex = r'^\s*(?P\d+)\s+(?P\d+)\s+(?P\d+)\s+\d+\s+(?P-[\d+])' + + turnpens_files = ["turnsam.pen", "turnsop.pen", "turnspm.pen"] + pen_regex = r"^\s*(?P\d+)\s+(?P\d+)\s+(?P\d+)\s+\d+\s+(?P-[\d+])" if turnPenReportFile: - outfile = open(turnPenReportFile,'w') - outfile.write('file,old_from,old_through,old_to,on_street,at_street,new_from,new_through,new_to,note\n') - - (nodes_dict, links_dict) = Cube.import_cube_nodes_links_from_csvs(CubeNetFile, - extra_link_vars=['LANE_AM', 'LANE_OP','LANE_PM', - 'BUSLANE_AM', 'BUSLANE_OP', 'BUSLANE_PM'], - extra_node_vars=[], - links_csv=os.path.join(os.getcwd(),"cubenet_validate_links.csv"), - nodes_csv=os.path.join(os.getcwd(),"cubenet_validate_nodes.csv"), - exportIfExists=True) + outfile = open(turnPenReportFile, "w") + outfile.write( + "file,old_from,old_through,old_to,on_street,at_street,new_from,new_through,new_to,note\n" + ) + + (nodes_dict, links_dict) = Cube.import_cube_nodes_links_from_csvs( + CubeNetFile, + extra_link_vars=[ + "LANE_AM", + "LANE_OP", + "LANE_PM", + "BUSLANE_AM", + "BUSLANE_OP", + "BUSLANE_PM", + ], + extra_node_vars=[], + links_csv=os.path.join(os.getcwd(), "cubenet_validate_links.csv"), + nodes_csv=os.path.join(os.getcwd(), "cubenet_validate_nodes.csv"), + exportIfExists=True, + ) found_matches = {} - + for file_name in turnpens_files: - f = open(file_name,'r') + f = open(file_name, "r") for line in f: - text = line.split(';')[0] + text = line.split(";")[0] m = re.match(pen_regex, text) if m: new_fr = None new_th = None new_to = None - from_street = 'missing' - to_street = 'missing' - fr_node = int(m.groupdict()['frnode']) - th_node = int(m.groupdict()['thnode']) - to_node = int(m.groupdict()['tonode']) - pen = int(m.groupdict()['pen']) - if not (fr_node,th_node) in links_dict: - WranglerLogger.debug("HighwayNetwork.validateTurnPens: (%d, %d) not in the roadway network for %s (%d, %d, %d)" % (fr_node,th_node,file_name,fr_node,th_node,to_node)) - + from_street = "missing" + to_street = "missing" + fr_node = int(m.groupdict()["frnode"]) + th_node = int(m.groupdict()["thnode"]) + to_node = int(m.groupdict()["tonode"]) + pen = int(m.groupdict()["pen"]) + if not (fr_node, th_node) in links_dict: + WranglerLogger.debug( + "HighwayNetwork.validateTurnPens: (%d, %d) not in the roadway network for %s (%d, %d, %d)" + % (fr_node, th_node, file_name, fr_node, th_node, to_node) + ) + if suggestCorrectLink: new_fr = -1 new_th = th_node match_links_fr = [] match_links_th = [] # if we already found a match for this, don't keep looking. - if (fr_node,th_node) in found_matches.keys(): - match = found_matches[(fr_node,th_node)] + if (fr_node, th_node) in found_matches.keys(): + match = found_matches[(fr_node, th_node)] new_fr = match[0][1] else: - #catch the links matching fr_node on the from end - for (a,b) in links_dict.keys(): + # catch the links matching fr_node on the from end + for (a, b) in links_dict.keys(): if a == fr_node: - match_links_fr.append((a,b)) + match_links_fr.append((a, b)) # and links matching th_node on the to end if b == th_node: - match_links_th.append((a,b)) + match_links_th.append((a, b)) # now take matched links and look for match_links_fr node b to match match_links_th node a - for (a1,b1) in match_links_fr: - for (a2,b2) in match_links_th: + for (a1, b1) in match_links_fr: + for (a2, b2) in match_links_th: if b1 == a2: - #WranglerLogger.info("For link1 (%d, %d) and link2 (%d, %d): %d == %d" % (a1,b1,a2,b2,b1,a2)) - found_matches[(fr_node,th_node)] = [(a1,b1),(a2,b2)] + # WranglerLogger.info("For link1 (%d, %d) and link2 (%d, %d): %d == %d" % (a1,b1,a2,b2,b1,a2)) + found_matches[(fr_node, th_node)] = [ + (a1, b1), + (a2, b2), + ] new_fr = a2 # index 1 is streetname - from_street = links_dict[(a2,b2)][1] + from_street = links_dict[(a2, b2)][1] break else: new_fr = fr_node - from_street = links_dict[(fr_node,th_node)][1] - - - if not (th_node,to_node) in links_dict: - WranglerLogger.debug("HighwayNetwork.validateTurnPens: (%d, %d) not in the roadway network for %s (%d, %d, %d)" % (th_node,to_node,file_name,fr_node,th_node,to_node)) - #if turnPenReportFile: outfile.write("%s,%d,%d,outbound link missing from, %d, %d, %d\n" %(file_name,th_node,to_node,fr_node,th_node,to_node)) + from_street = links_dict[(fr_node, th_node)][1] + + if not (th_node, to_node) in links_dict: + WranglerLogger.debug( + "HighwayNetwork.validateTurnPens: (%d, %d) not in the roadway network for %s (%d, %d, %d)" + % (th_node, to_node, file_name, fr_node, th_node, to_node) + ) + # if turnPenReportFile: outfile.write("%s,%d,%d,outbound link missing from, %d, %d, %d\n" %(file_name,th_node,to_node,fr_node,th_node,to_node)) if suggestCorrectLink: new_th = th_node new_to = -1 match_links_th = [] match_links_to = [] # if we already found a match for this, don't keep looking. - if (th_node,to_node) in found_matches.keys(): - match = found_matches[(th_node,to_node)] + if (th_node, to_node) in found_matches.keys(): + match = found_matches[(th_node, to_node)] new_to = match[0][1] else: - #catch the links matching fr_node on the from end - for (a,b) in links_dict.keys(): + # catch the links matching fr_node on the from end + for (a, b) in links_dict.keys(): if a == th_node: - match_links_th.append((a,b)) + match_links_th.append((a, b)) # and links matching th_node on the to end if b == to_node: - match_links_to.append((a,b)) + match_links_to.append((a, b)) # now take matched links and look for match_links_fr node b to match match_links_th node a - for (a1,b1) in match_links_th: - for (a2,b2) in match_links_to: + for (a1, b1) in match_links_th: + for (a2, b2) in match_links_to: if b1 == a2: - #WranglerLogger.info("For link1 (%d, %d) and link2 (%d, %d): %d == %d" % (a1,b1,a2,b2,b1,a2)) - found_matches[(th_node,to_node)] = [(a1,b1),(a2,b2)] + # WranglerLogger.info("For link1 (%d, %d) and link2 (%d, %d): %d == %d" % (a1,b1,a2,b2,b1,a2)) + found_matches[(th_node, to_node)] = [ + (a1, b1), + (a2, b2), + ] new_to = a2 - to_street = links_dict[(a2,b2)][1] + to_street = links_dict[(a2, b2)][1] break else: new_to = to_node - to_street = links_dict[(th_node,to_node)][1] - + to_street = links_dict[(th_node, to_node)][1] + if new_th != None: - #outfile.write('file,old_from,old_through,old_to,on_street,at_street,new_from,new_through,new_to,note\n') - print(file_name,fr_node,th_node,to_node,from_street,to_street,new_fr,new_th,new_to) - outfile.write('%s,%d,%d,%d,%s,%s,%d,%d,%d,note\n' % (file_name,fr_node,th_node,to_node,from_street,to_street,new_fr if new_fr else -1,new_th,new_to if new_to else -1)) - - def write(self, path='.', name='FREEFLOW.NET', writeEmptyFiles=True, suppressQuery=False, suppressValidation=False): + # outfile.write('file,old_from,old_through,old_to,on_street,at_street,new_from,new_through,new_to,note\n') + print( + file_name, + fr_node, + th_node, + to_node, + from_street, + to_street, + new_fr, + new_th, + new_to, + ) + outfile.write( + "%s,%d,%d,%d,%s,%s,%d,%d,%d,note\n" + % ( + file_name, + fr_node, + th_node, + to_node, + from_street, + to_street, + new_fr if new_fr else -1, + new_th, + new_to if new_to else -1, + ) + ) + + def write( + self, + path=".", + name="FREEFLOW.NET", + writeEmptyFiles=True, + suppressQuery=False, + suppressValidation=False, + ): if not os.path.exists(path): WranglerLogger.debug("\nPath [%s] doesn't exist; creating." % path) os.mkdir(path) else: - netfile = os.path.join(path,"FREEFLOW.net") + netfile = os.path.join(path, "FREEFLOW.net") if os.path.exists(netfile) and not suppressQuery: - print("File [{}] exists already. Overwrite contents? (y/n/s) ".format(netfile)) + print( + "File [{}] exists already. Overwrite contents? (y/n/s) ".format( + netfile + ) + ) response = raw_input("") WranglerLogger.debug("response = [%s]" % response) if response == "s" or response == "S": @@ -399,11 +556,12 @@ def write(self, path='.', name='FREEFLOW.NET', writeEmptyFiles=True, suppressQue if response != "Y" and response != "y": exit(0) - shutil.copyfile("FREEFLOW.BLD",os.path.join(path,name)) + shutil.copyfile("FREEFLOW.BLD", os.path.join(path, name)) WranglerLogger.info("Writing into %s\\%s" % (path, name)) WranglerLogger.info("") - for filename in ["turnsam.pen", "turnspm.pen", "turnsop.pen", "tolls.csv"]: + for filename in ["turnsam.pen", "turnspm.pen", "turnsop.pen", "tolls.csv"]: shutil.copyfile(filename, os.path.join(path, filename)) - - if not suppressValidation: self.validateTurnPens(netfile,'turnPenValidations.csv') \ No newline at end of file + + if not suppressValidation: + self.validateTurnPens(netfile, "turnPenValidations.csv") diff --git a/Wrangler/HwySpecsRTP.py b/Wrangler/HwySpecsRTP.py index a614fdf..5d3dac1 100644 --- a/Wrangler/HwySpecsRTP.py +++ b/Wrangler/HwySpecsRTP.py @@ -1,58 +1,78 @@ import logging + class HwySpecsRTP: - """ Simple class to read in the RTP specifications from a CSV file. - """ - - def __init__(self,specsFile): + """Simple class to read in the RTP specifications from a CSV file.""" + + def __init__(self, specsFile): """ Read and cache specifications. Will apply in order read in. """ - self.projects = [] # list of RTP reference numbers - self.projectdict = {} # RTP reference number => dictionary of attributes - - specs = open(specsFile,'r') - i=0 + self.projects = [] # list of RTP reference numbers + self.projectdict = {} # RTP reference number => dictionary of attributes + + specs = open(specsFile, "r") + i = 0 for line in specs: - i+=1 - if i==1: - head=line.strip().split(',') + i += 1 + if i == 1: + head = line.strip().split(",") else: - l = line.strip().split(',') - #print l + l = line.strip().split(",") + # print l RTPref = l[head.index("RTP Ref#")] self.projectdict[RTPref] = {} self.projects.append(RTPref) - - self.projectdict[RTPref]["Facility"]=l[head.index("Corridor")] - self.projectdict[RTPref]["Action"]=l[head.index("Action")] - self.projectdict[RTPref]["Span"]=l[head.index("Span")] - self.projectdict[RTPref]["County"]=l[head.index("County")] - self.projectdict[RTPref]["MOD YEAR"]=int(l[head.index("MOD YEAR")]) - self.projectdict[RTPref]["RTP FUNDING"]=l[head.index("RTP FUNDING")] + self.projectdict[RTPref]["Facility"] = l[head.index("Corridor")] + self.projectdict[RTPref]["Action"] = l[head.index("Action")] + self.projectdict[RTPref]["Span"] = l[head.index("Span")] + self.projectdict[RTPref]["County"] = l[head.index("County")] + self.projectdict[RTPref]["MOD YEAR"] = int(l[head.index("MOD YEAR")]) + self.projectdict[RTPref]["RTP FUNDING"] = l[head.index("RTP FUNDING")] - def listOfProjects(self,maxYear=2035,baseYear=2000): + def listOfProjects(self, maxYear=2035, baseYear=2000): """ Returns the project RTP Reference numbers that qualify (after *baseYear*, before and including *maxYear*) """ projectList = [] for pref in self.projects: - if self.projectdict[pref]["MOD YEAR"]<=maxYear and self.projectdict[pref]["MOD YEAR"]>baseYear: + if ( + self.projectdict[pref]["MOD YEAR"] <= maxYear + and self.projectdict[pref]["MOD YEAR"] > baseYear + ): projectList.append(pref) return projectList - - def printProjects(self,fileObj): + + def printProjects(self, fileObj): fileObj.write("YEAR RTP FACILITY COUNTY ACTION \n") fileObj.write("----------------------------------------------------\n") for p in self.projects: - fileObj.write( str(p["MOD YEAR"])+" "+p["RTP REF"]+" "+p["Facility"]+" "+p["Action"]+" "+p["County"]+"\n") - + fileObj.write( + str(p["MOD YEAR"]) + + " " + + p["RTP REF"] + + " " + + p["Facility"] + + " " + + p["Action"] + + " " + + p["County"] + + "\n" + ) + def logProjects(self, logger): logger.info("YEAR RTP FACILITY COUNTY ACTION ") logger.info("----------------------------------------------------") for p in self.projects: - logger.info( str(p["MOD YEAR"])+" "+p["RTP REF"]+" "+p["Facility"]+" "+p["Action"]+" "+p["County"]) - - - + logger.info( + str(p["MOD YEAR"]) + + " " + + p["RTP REF"] + + " " + + p["Facility"] + + " " + + p["Action"] + + " " + + p["County"] + ) diff --git a/Wrangler/Linki.py b/Wrangler/Linki.py index 4f28f00..060c512 100644 --- a/Wrangler/Linki.py +++ b/Wrangler/Linki.py @@ -1,28 +1,27 @@ class Linki(dict): - """ Linki Link. Has A-node, B-node, possibly a comment and a distance. - """ + """Linki Link. Has A-node, B-node, possibly a comment and a distance.""" + def __init__(self): dict.__init__(self) - self.A='' - self.B='' - self.comment='' - self.distance='' - self.xferTime='' - self.accessType='' - + self.A = "" + self.B = "" + self.comment = "" + self.distance = "" + self.xferTime = "" + self.accessType = "" + def __repr__(self): s = "%8s %8s" % (self.A, self.B) - + # access links have a type and a transfer time - if self.accessType != '': + if self.accessType != "": s += " %s" % self.accessType - if self.xferTime != '': - s += " %3s" % self.xferTime - elif self.distance != '': + if self.xferTime != "": + s += " %3s" % self.xferTime + elif self.distance != "": s += " %8s" % self.distance - - if self.comment != '': + + if self.comment != "": s += " %s" % (self.comment) return s - \ No newline at end of file diff --git a/Wrangler/Logger.py b/Wrangler/Logger.py index ca4c8e8..bba1d32 100644 --- a/Wrangler/Logger.py +++ b/Wrangler/Logger.py @@ -1,6 +1,6 @@ import logging -__all__ = ['WranglerLogger', 'setupLogging'] +__all__ = ["WranglerLogger", "setupLogging"] # for all the Wrangler logging needs! @@ -8,12 +8,12 @@ def setupLogging(infoLogFilename, debugLogFilename, logToConsole=True): - """ Sets up the logger. The infoLog is terse, just gives the bare minimum of details - so the network composition will be clear later. - The debuglog is very noisy, for debugging. - - Pass none to either. - Spews it all out to console too, if logToConsole is true. + """Sets up the logger. The infoLog is terse, just gives the bare minimum of details + so the network composition will be clear later. + The debuglog is very noisy, for debugging. + + Pass none to either. + Spews it all out to console too, if logToConsole is true. """ # clear handlers if any exist already WranglerLogger.handlers = [] @@ -22,21 +22,23 @@ def setupLogging(infoLogFilename, debugLogFilename, logToConsole=True): WranglerLogger.setLevel(logging.DEBUG) if infoLogFilename: - infologhandler = logging.StreamHandler(open(infoLogFilename, 'w')) + infologhandler = logging.StreamHandler(open(infoLogFilename, "w")) infologhandler.setLevel(logging.INFO) - infologhandler.setFormatter(logging.Formatter('%(message)s')) + infologhandler.setFormatter(logging.Formatter("%(message)s")) WranglerLogger.addHandler(infologhandler) - + if debugLogFilename: - debugloghandler = logging.StreamHandler(open(debugLogFilename,'w')) + debugloghandler = logging.StreamHandler(open(debugLogFilename, "w")) debugloghandler.setLevel(logging.DEBUG) - debugloghandler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s', '%Y-%m-%d %H:%M')) + debugloghandler.setFormatter( + logging.Formatter("%(asctime)s %(levelname)s %(message)s", "%Y-%m-%d %H:%M") + ) WranglerLogger.addHandler(debugloghandler) - + if logToConsole: consolehandler = logging.StreamHandler() consolehandler.setLevel(logging.DEBUG) - consolehandler.setFormatter(logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')) + consolehandler.setFormatter( + logging.Formatter("%(name)-12s: %(levelname)-8s %(message)s") + ) WranglerLogger.addHandler(consolehandler) - - diff --git a/Wrangler/Network.py b/Wrangler/Network.py index eb10c90..4ed871f 100644 --- a/Wrangler/Network.py +++ b/Wrangler/Network.py @@ -3,24 +3,33 @@ from .NetworkException import NetworkException from .Regexes import git_commit_pattern -__all__ = ['Network'] +__all__ = ["Network"] + class Network(object): - MODEL_TYPE_CHAMP = "CHAMP" # SFCTA travel model - MODEL_TYPE_TM1 = "TravelModelOne" # MTC/ABAG Travel Model One - MODEL_TYPE_TM2 = "TravelModelTwo" # MTC/ABAG Travel Model Two + MODEL_TYPE_CHAMP = "CHAMP" # SFCTA travel model + MODEL_TYPE_TM1 = "TravelModelOne" # MTC/ABAG Travel Model One + MODEL_TYPE_TM2 = "TravelModelTwo" # MTC/ABAG Travel Model Two - WRANGLER_VERSION = 2.0 - NETWORK_BASE_DIR = r"Y:\networks" - NETWORK_PROJECT_SUBDIR = "" - NETWORK_PLAN_SUBDIR = "" - NETWORK_SEED_SUBDIR = "" + WRANGLER_VERSION = 2.0 + NETWORK_BASE_DIR = r"Y:\networks" + NETWORK_PROJECT_SUBDIR = "" + NETWORK_PLAN_SUBDIR = "" + NETWORK_SEED_SUBDIR = "" # static variable allNetworks = {} - def __init__(self, modelType, modelVersion, networkBaseDir=None, networkProjectSubdir=None, - networkSeedSubdir=None, networkPlanSubdir=None, networkName=None): + def __init__( + self, + modelType, + modelVersion, + networkBaseDir=None, + networkProjectSubdir=None, + networkSeedSubdir=None, + networkPlanSubdir=None, + networkName=None, + ): """ *modelType* should be MODEL_TYPE_CHAMP, MODEL_TYPE_TM1, or MODEL_TYPE_TM2 *modelVersion* should be numeric and is used for compatibility checks. @@ -28,20 +37,31 @@ def __init__(self, modelType, modelVersion, networkBaseDir=None, networkProjectS Currently this should be 4.3 or newer for CHAMP. Pass *networkName* to be added to the Networks dictionary """ - if modelType not in [Network.MODEL_TYPE_CHAMP, Network.MODEL_TYPE_TM1, Network.MODEL_TYPE_TM2]: + if modelType not in [ + Network.MODEL_TYPE_CHAMP, + Network.MODEL_TYPE_TM1, + Network.MODEL_TYPE_TM2, + ]: raise NetworkException("Do not understand modelType {}".format(modelType)) if type(modelVersion) != type(0.0): - raise NetworkException("Do not understand modelVersion {}".format(modelVersion)) + raise NetworkException( + "Do not understand modelVersion {}".format(modelVersion) + ) self.modelType = modelType self.modelVersion = modelVersion self.wranglerVersion = self.WRANGLER_VERSION self.appliedProjects = {} - if networkBaseDir: Network.NETWORK_BASE_DIR = networkBaseDir - if networkProjectSubdir: Network.NETWORK_PROJECT_SUBDIR = networkProjectSubdir - if networkSeedSubdir: Network.NETWORK_SEED_SUBDIR = networkSeedSubdir - if networkPlanSubdir: Network.NETWORK_PLAN_SUBDIR = networkPlanSubdir - if networkName: Network.allNetworks[networkName] = self + if networkBaseDir: + Network.NETWORK_BASE_DIR = networkBaseDir + if networkProjectSubdir: + Network.NETWORK_PROJECT_SUBDIR = networkProjectSubdir + if networkSeedSubdir: + Network.NETWORK_SEED_SUBDIR = networkSeedSubdir + if networkPlanSubdir: + Network.NETWORK_PLAN_SUBDIR = networkPlanSubdir + if networkName: + Network.allNetworks[networkName] = self def _runAndLog(self, cmd, run_dir=".", logStdoutAndStderr=False, env=None): """ @@ -56,62 +76,89 @@ def _runAndLog(self, cmd, run_dir=".", logStdoutAndStderr=False, env=None): # WranglerLogger.debug("Using environment {}".format(myenv)) try: - proc = subprocess.Popen( cmd, cwd = run_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, env=myenv ) + proc = subprocess.Popen( + cmd, + cwd=run_dir, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=True, + env=myenv, + ) retStdout = [] for line in proc.stdout: - if type(line)==bytes: line = line.decode() # convert to string, not byetes - line = line.strip('\r\n') - if logStdoutAndStderr: WranglerLogger.debug("stdout: " + line) + if type(line) == bytes: + line = line.decode() # convert to string, not byetes + line = line.strip("\r\n") + if logStdoutAndStderr: + WranglerLogger.debug("stdout: " + line) retStdout.append(line) retStderr = [] for line in proc.stderr: - if type(line)==bytes: line = line.decode() # convert to string, not byetes - line = line.strip('\r\n') - if logStdoutAndStderr: WranglerLogger.debug("stderr: " + line) + if type(line) == bytes: + line = line.decode() # convert to string, not byetes + line = line.strip("\r\n") + if logStdoutAndStderr: + WranglerLogger.debug("stderr: " + line) retStderr.append(line) - retcode = proc.wait() - WranglerLogger.debug("Received {} from [{}] run in [{}]".format(retcode, cmd, run_dir)) - + retcode = proc.wait() + WranglerLogger.debug( + "Received {} from [{}] run in [{}]".format(retcode, cmd, run_dir) + ) + except Exception as inst: - WranglerLogger.error('Exception caught') - WranglerLogger.error(type(inst)) # the exception instance - WranglerLogger.error(inst.args) # arguments stored in .args - WranglerLogger.error(inst) # __str__ allows args to be printed directly, + WranglerLogger.error("Exception caught") + WranglerLogger.error(type(inst)) # the exception instance + WranglerLogger.error(inst.args) # arguments stored in .args + WranglerLogger.error(inst) # __str__ allows args to be printed directly, return (retcode, retStdout, retStderr) - def getReqs(self, networkdir, projectsubdir=None, tag=None, projtype=None, tempdir=None): + def getReqs( + self, networkdir, projectsubdir=None, tag=None, projtype=None, tempdir=None + ): """ Checks project for pre-requisites, co-requisites, and conflicts See :py:meth:`Wrangler.Network.applyProject` for argument details. """ - (parentdir, networkdir, gitdir, projectsubdir) = self.getClonedProjectArgs(networkdir, projectsubdir, projtype, tempdir) - prereqs = self.getAttr('prereqs', parentdir, networkdir, gitdir, projectsubdir) - coreqs = self.getAttr('coreqs', parentdir, networkdir, gitdir, projectsubdir) - conflicts = self.getAttr('conflicts',parentdir, networkdir, gitdir, projectsubdir) + (parentdir, networkdir, gitdir, projectsubdir) = self.getClonedProjectArgs( + networkdir, projectsubdir, projtype, tempdir + ) + prereqs = self.getAttr("prereqs", parentdir, networkdir, gitdir, projectsubdir) + coreqs = self.getAttr("coreqs", parentdir, networkdir, gitdir, projectsubdir) + conflicts = self.getAttr( + "conflicts", parentdir, networkdir, gitdir, projectsubdir + ) return (prereqs, coreqs, conflicts) - def getClonedProjectArgs(self, networkdir, projectsubdir=None, projtype=None, tempdir=None): + def getClonedProjectArgs( + self, networkdir, projectsubdir=None, projtype=None, tempdir=None + ): """ Gets project arguments to clone and apply projects See :py:meth:`Wrangler.Network.applyProject` for argument details. """ if tempdir: - if projtype=='plan': - joinedBaseDir = os.path.join(Network.NETWORK_BASE_DIR,Network.NETWORK_PLAN_SUBDIR) + if projtype == "plan": + joinedBaseDir = os.path.join( + Network.NETWORK_BASE_DIR, Network.NETWORK_PLAN_SUBDIR + ) joinedTempDir = os.path.join(tempdir, Network.NETWORK_PLAN_SUBDIR) - elif projtype=='project': - joinedBaseDir = os.path.join(Network.NETWORK_BASE_DIR,Network.NETWORK_PROJECT_SUBDIR) + elif projtype == "project": + joinedBaseDir = os.path.join( + Network.NETWORK_BASE_DIR, Network.NETWORK_PROJECT_SUBDIR + ) joinedTempDir = os.path.join(tempdir, Network.NETWORK_PROJECT_SUBDIR) - elif projtype=='seed': - joinedBaseDir = os.path.join(Network.NETWORK_BASE_DIR,Network.NETWORK_SEED_SUBDIR) + elif projtype == "seed": + joinedBaseDir = os.path.join( + Network.NETWORK_BASE_DIR, Network.NETWORK_SEED_SUBDIR + ) joinedTempDir = os.path.join(tempdir, Network.NETWORK_SEED_SUBDIR) else: joinedBaseDir = Network.NETWORK_BASE_DIR joinedTempDir = tempdir - + gitdir = os.path.join(joinedTempDir, networkdir) else: # need if for projtype... and joinedTempDir @@ -125,11 +172,11 @@ def getClonedProjectArgs(self, networkdir, projectsubdir=None, projtype=None, te except: gitdir = os.path.join(gitdir, projectsubdir) commitstr = self.getCommit(gitdir) - + return (joinedTempDir, networkdir, gitdir, projectsubdir) - + def getAttr(self, attr_name, parentdir, networkdir, gitdir, projectsubdir=None): - """ + """ Returns attribute for this project based on attr_name attr_name: the name of the attribute to get parentdir: the directory where the project is found @@ -138,10 +185,22 @@ def getAttr(self, attr_name, parentdir, networkdir, gitdir, projectsubdir=None): projectsubdir: the subdir if it exists, None otherwise """ - if attr_name not in ['year', 'desc', 'modelType', 'modelVersion', 'wranglerVersion', 'prereqs', 'coreqs', 'conflicts', 'networks']: - WranglerLogger.fatal('%s is not a valid attribute type for a network project' % (attr_name)) + if attr_name not in [ + "year", + "desc", + "modelType", + "modelVersion", + "wranglerVersion", + "prereqs", + "coreqs", + "conflicts", + "networks", + ]: + WranglerLogger.fatal( + "%s is not a valid attribute type for a network project" % (attr_name) + ) return - + if projectsubdir: projectname = projectsubdir sys.path.append(os.path.join(os.getcwd(), parentdir, networkdir)) @@ -173,7 +232,7 @@ def getAttr(self, attr_name, parentdir, networkdir, gitdir, projectsubdir=None): projectdir = eval(evalstr) # WranglerLogger.debug("projectdir = {}".format(projectdir)) - attr_value = (eval("%s.%s()" % (projectname ,attr_name))) + attr_value = eval("%s.%s()" % (projectname, attr_name)) # todo: if try champVersion if modelType is CHAMP and modelVersion is sought return attr_value @@ -183,16 +242,16 @@ def getModelVersion(self, parentdir, networkdir, gitdir, projectsubdir=None): See :py:meth:`Wrangler.Network.applyProject` for argument details. """ - return getAttr('modelVersion',parentdir, networkdir,gitdir, projectsubdir) + return getAttr("modelVersion", parentdir, networkdir, gitdir, projectsubdir) def getWranglerVersion(self, parentdir, networkdir, gitdir, projectsubdir=None): - """ + """ Returns wranglerVersion range for this project See :py:meth:`Wrangler.Network.applyProject` for argument details. """ - return getAttr('wranglerVersion',parentdir, networkdir,gitdir, projectsubdir) - + return getAttr("wranglerVersion", parentdir, networkdir, gitdir, projectsubdir) + def checkVersion(self, version, parentdir, networkdir, gitdir, projectsubdir=None): """ Verifies that this project is compatible with the modelVersion or wranglerVersion, raises an exception @@ -200,35 +259,64 @@ def checkVersion(self, version, parentdir, networkdir, gitdir, projectsubdir=Non See :py:meth:`Wrangler.Network.applyProject` for argument details. """ - if version not in ['modelVersion','wranglerVersion']: - Wrangler.WranglerLogger.fatal("%s is not a valid version. Must be 'modelVersion' or 'wranglerVersion'" % str(version)) + if version not in ["modelVersion", "wranglerVersion"]: + Wrangler.WranglerLogger.fatal( + "%s is not a valid version. Must be 'modelVersion' or 'wranglerVersion'" + % str(version) + ) - versions = {'modelVersion':self.modelVersion, 'wranglerVersion':self.wranglerVersion} + versions = { + "modelVersion": self.modelVersion, + "wranglerVersion": self.wranglerVersion, + } # if we're checking model version, check model type as well if version == "modelVersion": # legacy: assume CHAMP project_model_type = Network.MODEL_TYPE_CHAMP try: - project_model_type = self.getAttr("modelType", parentdir=parentdir, networkdir=networkdir, gitdir=gitdir, projectsubdir=projectsubdir) - WranglerLogger.debug("Found project_model_type {}".format(project_model_type)) + project_model_type = self.getAttr( + "modelType", + parentdir=parentdir, + networkdir=networkdir, + gitdir=gitdir, + projectsubdir=projectsubdir, + ) + WranglerLogger.debug( + "Found project_model_type {}".format(project_model_type) + ) except ImportError as ie: # consider this fatal sys.exit(2) except: - WranglerLogger.debug("Failed to find project model type. Exception: {}".format(sys.exc_info()[0])) + WranglerLogger.debug( + "Failed to find project model type. Exception: {}".format( + sys.exc_info()[0] + ) + ) pass if project_model_type != self.modelType: - raise NetworkException("Project model type ({}) not compatible with this model type {}".format(project_model_type, self.modelType)) - - projVersion = self.getAttr(version, parentdir=parentdir, networkdir=networkdir, - gitdir=gitdir, projectsubdir=projectsubdir) - WranglerLogger.debug("Checking %s compatibility of project (%s) with requirement (%s)" % - (version, projVersion, versions[version])) + raise NetworkException( + "Project model type ({}) not compatible with this model type {}".format( + project_model_type, self.modelType + ) + ) + + projVersion = self.getAttr( + version, + parentdir=parentdir, + networkdir=networkdir, + gitdir=gitdir, + projectsubdir=projectsubdir, + ) + WranglerLogger.debug( + "Checking %s compatibility of project (%s) with requirement (%s)" + % (version, projVersion, versions[version]) + ) minVersion = projVersion[0] maxVersion = projVersion[1] - + if maxVersion == None: if versions[version] >= minVersion: return @@ -236,8 +324,15 @@ def checkVersion(self, version, parentdir, networkdir, gitdir, projectsubdir=Non if versions[version] >= minVersion and versions[version] <= maxVersion: return - raise NetworkException("Project version range (%d, %d) not compatible with this %s version %d" - % (minVersion,maxVersion,version.replace("Version","").upper(),versions[version])) + raise NetworkException( + "Project version range (%d, %d) not compatible with this %s version %d" + % ( + minVersion, + maxVersion, + version.replace("Version", "").upper(), + versions[version], + ) + ) def getNetTypes(self, parentdir, networkdir, projectsubdir=None): """ @@ -257,18 +352,20 @@ def getNetTypes(self, parentdir, networkdir, projectsubdir=None): evalstr = "import %s" % projectname exec(evalstr) except Exception as e: - #WranglerLogger.debug("error importing module") - s_projectname = "s"+str(projectname) + # WranglerLogger.debug("error importing module") + s_projectname = "s" + str(projectname) evalstr = "%s = __import__('%s')" % (s_projectname, projectname) exec(evalstr) evalstr = "dir(%s)" % (projectname if not s_projectname else s_projectname) projectdir = eval(evalstr) - + # WranglerLogger.debug("projectdir = " + str(projectdir)) - netTypes = (eval("%s.networks()" % (projectname if not s_projectname else s_projectname))) + netTypes = eval( + "%s.networks()" % (projectname if not s_projectname else s_projectname) + ) return netTypes - + def applyProject(self, parentdir, networkdir, gitdir, projectsubdir=None, **kwargs): """ Implemented by subclasses. Args are as follows: @@ -280,49 +377,69 @@ def applyProject(self, parentdir, networkdir, gitdir, projectsubdir=None, **kwar * *projectsubdir* is an optional subdir of *networkdir*; If the ``apply.s`` or ``__init__.py`` is in a subdir, this is how it's specified * *kwargs* are additional keyword args to pass into the apply() - + Returns the SHA1 hash ID of the git commit of the project applied """ pass - def cloneProject(self, networkdir, projectsubdir=None, tag=None, projtype=None, tempdir=None, **kwargs): + def cloneProject( + self, + networkdir, + projectsubdir=None, + tag=None, + projtype=None, + tempdir=None, + **kwargs + ): """ * *networkdir* corresponds to the dir relative to ``Y:\\networks`` * *projectsubdir* is a subdir within that, or None if there's no subdir * *tag* is "1.0" or "1-latest", or None for just the latest version * *tempdir* is the parent dir to put the git clone dir; pass None for python to just choose * *kwargs* are additional args for the apply - + Returns the SHA1 hash ID of the git commit of the project applied """ - if tempdir: - if projtype=='plan': - joinedBaseDir = os.path.join(Network.NETWORK_BASE_DIR,Network.NETWORK_PLAN_SUBDIR) + if projtype == "plan": + joinedBaseDir = os.path.join( + Network.NETWORK_BASE_DIR, Network.NETWORK_PLAN_SUBDIR + ) joinedTempDir = os.path.join(tempdir, Network.NETWORK_PLAN_SUBDIR) - elif projtype=='project': - joinedBaseDir = os.path.join(Network.NETWORK_BASE_DIR,Network.NETWORK_PROJECT_SUBDIR) + elif projtype == "project": + joinedBaseDir = os.path.join( + Network.NETWORK_BASE_DIR, Network.NETWORK_PROJECT_SUBDIR + ) joinedTempDir = os.path.join(tempdir, Network.NETWORK_PROJECT_SUBDIR) - elif projtype=='seed': - joinedBaseDir = os.path.join(Network.NETWORK_BASE_DIR,Network.NETWORK_SEED_SUBDIR) + elif projtype == "seed": + joinedBaseDir = os.path.join( + Network.NETWORK_BASE_DIR, Network.NETWORK_SEED_SUBDIR + ) joinedTempDir = os.path.join(tempdir, Network.NETWORK_SEED_SUBDIR) else: joinedBaseDir = Network.NETWORK_BASE_DIR joinedTempDir = tempdir - + gitdir = os.path.join(joinedTempDir, networkdir) - + if not os.path.exists(joinedTempDir): os.makedirs(joinedTempDir) - - # if the tempdir exists and it's already here and the projectsubdir is present, - # then we already checked it out - elif projectsubdir and os.path.exists(os.path.join(joinedTempDir,networkdir,projectsubdir)): - WranglerLogger.debug("Skipping checkout of %s, %s already exists" % - (networkdir, os.path.join(joinedTempDir,networkdir,projectsubdir))) - # verify we didn't require conflicting tags + # if the tempdir exists and it's already here and the projectsubdir is present, + # then we already checked it out + elif projectsubdir and os.path.exists( + os.path.join(joinedTempDir, networkdir, projectsubdir) + ): + WranglerLogger.debug( + "Skipping checkout of %s, %s already exists" + % ( + networkdir, + os.path.join(joinedTempDir, networkdir, projectsubdir), + ) + ) + + # verify we didn't require conflicting tags try: commitstr = self.getCommit(gitdir) except: @@ -334,22 +451,46 @@ def cloneProject(self, networkdir, projectsubdir=None, tag=None, projtype=None, # TODO: just checkout to the new tag raise NetworkException("Conflicting tag requirements - FIXME!") - self.checkVersion(version='modelVersion',parentdir=joinedTempDir, networkdir=networkdir, - gitdir=gitdir, projectsubdir=projectsubdir) - self.checkVersion(version='wranglerVersion',parentdir=joinedTempDir, networkdir=networkdir, - gitdir=gitdir, projectsubdir=projectsubdir) + self.checkVersion( + version="modelVersion", + parentdir=joinedTempDir, + networkdir=networkdir, + gitdir=gitdir, + projectsubdir=projectsubdir, + ) + self.checkVersion( + version="wranglerVersion", + parentdir=joinedTempDir, + networkdir=networkdir, + gitdir=gitdir, + projectsubdir=projectsubdir, + ) commitstr = self.getCommit(gitdir) return commitstr - - elif not projectsubdir and os.path.exists(os.path.join(joinedTempDir,networkdir)): - WranglerLogger.debug("Skipping checkout of %s, %s already exists" % - (networkdir, os.path.join(joinedTempDir,networkdir))) - self.checkVersion(version='modelVersion',parentdir=joinedTempDir, networkdir=networkdir, - gitdir=gitdir, projectsubdir=projectsubdir) - self.checkVersion(version='wranglerVersion',parentdir=joinedTempDir, networkdir=networkdir, - gitdir=gitdir, projectsubdir=projectsubdir) + elif not projectsubdir and os.path.exists( + os.path.join(joinedTempDir, networkdir) + ): + WranglerLogger.debug( + "Skipping checkout of %s, %s already exists" + % (networkdir, os.path.join(joinedTempDir, networkdir)) + ) + + self.checkVersion( + version="modelVersion", + parentdir=joinedTempDir, + networkdir=networkdir, + gitdir=gitdir, + projectsubdir=projectsubdir, + ) + self.checkVersion( + version="wranglerVersion", + parentdir=joinedTempDir, + networkdir=networkdir, + gitdir=gitdir, + projectsubdir=projectsubdir, + ) # TODO: we should verify we didn't require conflicting tags? commitstr = self.getCommit(gitdir) @@ -362,23 +503,35 @@ def cloneProject(self, networkdir, projectsubdir=None, tag=None, projtype=None, WranglerLogger.debug("Using tempdir %s" % tempdir) gitdir = os.path.join(tempdir, networkdir) - WranglerLogger.debug("Checking out networkdir %s into tempdir %s %s" % - (networkdir, joinedTempDir,"for "+projectsubdir if projectsubdir else "")) + WranglerLogger.debug( + "Checking out networkdir %s into tempdir %s %s" + % ( + networkdir, + joinedTempDir, + "for " + projectsubdir if projectsubdir else "", + ) + ) # if on windows and joinedBaseDir is on a mapped network drive, it needs to be prefaced with "file://" # https://stackoverflow.com/questions/37422428/git-internal-error-refs-remotes-origin-master-is-not-a-valid-packed-reference - if os.name=='nt': + if os.name == "nt": import win32file - (drive,tail) = os.path.splitdrive(joinedBaseDir) + + (drive, tail) = os.path.splitdrive(joinedBaseDir) WranglerLogger.debug("Checking if windows drive [%s] is remote" % drive) - if win32file.GetDriveType(drive)==win32file.DRIVE_REMOTE: + if win32file.GetDriveType(drive) == win32file.DRIVE_REMOTE: joinedBaseDir = "file://" + joinedBaseDir WranglerLogger.debug("Using base dir [%s]" % joinedBaseDir) - if os.path.exists(os.path.join(joinedBaseDir,networkdir,'.git')): - cmd = r'git clone -b master --quiet "%s" "%s"' % (os.path.join(joinedBaseDir, networkdir), networkdir) + if os.path.exists(os.path.join(joinedBaseDir, networkdir, ".git")): + cmd = r'git clone -b master --quiet "%s" "%s"' % ( + os.path.join(joinedBaseDir, networkdir), + networkdir, + ) else: - cmd = r'git clone -b master --quiet "%s"' % os.path.join(joinedBaseDir, networkdir) + cmd = r'git clone -b master --quiet "%s"' % os.path.join( + joinedBaseDir, networkdir + ) (retcode, retstdout, retstderr) = self._runAndLog(cmd, joinedTempDir) if retcode != 0: @@ -387,11 +540,13 @@ def cloneProject(self, networkdir, projectsubdir=None, tag=None, projtype=None, # if there was a subdir involved, try checking if the subdir is the git dir gitdir = os.path.join(gitdir, projectsubdir) - newtempdir = os.path.join(joinedTempDir,networkdir) + newtempdir = os.path.join(joinedTempDir, networkdir) if not os.path.exists(newtempdir): os.makedirs(newtempdir) - cmd = r'git clone -b master --quiet "%s"' % os.path.join(joinedBaseDir, networkdir, projectsubdir) + cmd = r'git clone -b master --quiet "%s"' % os.path.join( + joinedBaseDir, networkdir, projectsubdir + ) (retcode, retstdout, retstderr) = self._runAndLog(cmd, newtempdir) if tag != None: @@ -400,28 +555,53 @@ def cloneProject(self, networkdir, projectsubdir=None, tag=None, projtype=None, if retcode != 0: raise NetworkException("Git checkout failed; see log file") - self.checkVersion(version='modelVersion',parentdir=joinedTempDir, networkdir=networkdir, - gitdir=gitdir, projectsubdir=projectsubdir) - self.checkVersion(version='wranglerVersion',parentdir=joinedTempDir, networkdir=networkdir, - gitdir=gitdir, projectsubdir=projectsubdir) + self.checkVersion( + version="modelVersion", + parentdir=joinedTempDir, + networkdir=networkdir, + gitdir=gitdir, + projectsubdir=projectsubdir, + ) + self.checkVersion( + version="wranglerVersion", + parentdir=joinedTempDir, + networkdir=networkdir, + gitdir=gitdir, + projectsubdir=projectsubdir, + ) commitstr = self.getCommit(gitdir) return commitstr - def cloneAndApplyProject(self, networkdir, projectsubdir=None, tag=None, projtype=None, tempdir=None, **kwargs): + def cloneAndApplyProject( + self, + networkdir, + projectsubdir=None, + tag=None, + projtype=None, + tempdir=None, + **kwargs + ): """ * *networkdir* corresponds to the dir relative to ``Y:\\networks`` * *projectsubdir* is a subdir within that, or None if there's no subdir * *tag* is "1.0" or "1-latest", or None for just the latest version * *tempdir* is the parent dir to put the git clone dir; pass None for python to just choose * *kwargs* are additional args for the apply - + Returns the SHA1 hash ID of the git commit of the project applied """ self.cloneProject(networkdir, projectsubdir, tag, projtype, tempdir, **kwargs) - (joinedTempDir, networkdir, gitdir, projectsubdir) = self.getClonedProjectArgs(networkdir, projectsubdir, projtype, tempdir) - return self.applyProject(parentdir=joinedTempDir, networkdir=networkdir, - gitdir=gitdir, projectsubdir=projectsubdir, **kwargs) + (joinedTempDir, networkdir, gitdir, projectsubdir) = self.getClonedProjectArgs( + networkdir, projectsubdir, projtype, tempdir + ) + return self.applyProject( + parentdir=joinedTempDir, + networkdir=networkdir, + gitdir=gitdir, + projectsubdir=projectsubdir, + **kwargs + ) def getCommit(self, gitdir): """ @@ -435,12 +615,14 @@ def getCommit(self, gitdir): cmd = r"git log -1" (retcode, retstdout, retstderr) = self._runAndLog(cmd, gitdir) - if len(retstdout)<3: + if len(retstdout) < 3: raise NetworkException("Git log failed; see log file") - + m = re.match(git_commit_pattern, retstdout[0]) if not m: - raise NetworkException("Didn't understand git log output: [" + retstdout[0] + "]") + raise NetworkException( + "Didn't understand git log output: [" + retstdout[0] + "]" + ) return m.group(1) @@ -450,7 +632,7 @@ def getTags(self, gitdir, commitstr): """ cmd = r"git tag --contains " + commitstr (retcode, retstdout, retstderr) = self._runAndLog(cmd, gitdir) - if len(retstdout)==0: + if len(retstdout) == 0: return None return retstdout @@ -460,28 +642,37 @@ def logProject(self, gitdir, projectname, year=None, projectdesc=None, county=No Returns the SHA1 hash ID of the git commit of the project applied """ commitstr = self.getCommit(gitdir) - tag = self.getTags(gitdir, commitstr) + tag = self.getTags(gitdir, commitstr) if year: yearstr = "%4d" % year else: yearstr = " " - WranglerLogger.info("%-4s | %-5s | %-40s | %-40s | %-10s | %s" % - (yearstr, - tag if tag else "notag", - commitstr if commitstr else "", - projectname.lstrip() if projectname else "", - county.lstrip() if county else "", - projectdesc.lstrip() if projectdesc else "" - ) - ) + WranglerLogger.info( + "%-4s | %-5s | %-40s | %-40s | %-10s | %s" + % ( + yearstr, + tag if tag else "notag", + commitstr if commitstr else "", + projectname.lstrip() if projectname else "", + county.lstrip() if county else "", + projectdesc.lstrip() if projectdesc else "", + ) + ) self.appliedProjects[projectname] = tag if tag else commitstr - + return commitstr - - def write(self, path='.', name='network', writeEmptyFiles=True, suppressQuery=False, suppressValidation=False): + + def write( + self, + path=".", + name="network", + writeEmptyFiles=True, + suppressQuery=False, + suppressValidation=False, + ): """ Implemented by subclass """ - pass \ No newline at end of file + pass diff --git a/Wrangler/NetworkException.py b/Wrangler/NetworkException.py index a03d22b..5687ff6 100644 --- a/Wrangler/NetworkException.py +++ b/Wrangler/NetworkException.py @@ -1,7 +1,9 @@ -__all__ = ['NetworkException'] +__all__ = ["NetworkException"] -class NetworkException(Exception): + +class NetworkException(Exception): """ This class is used to communicate Wrangler errors. """ + pass diff --git a/Wrangler/Node.py b/Wrangler/Node.py index 772d817..95103e1 100644 --- a/Wrangler/Node.py +++ b/Wrangler/Node.py @@ -1,32 +1,33 @@ -import os,sys +import os, sys from .Logger import WranglerLogger -__all__ = ['Node'] +__all__ = ["Node"] + class Node(object): """ Transit node. This can only exist as part of a transit line. - + * *num* is the string representation of the node number with stop-status (e.g. '-24322') * *stop* is True or False - + All other attributes stored as a dictionary. e.g:: thisnode["DELAY"]="0.5" """ - + # static variables for nodes.xls - descriptions = {} - descriptions_read = False + descriptions = {} + descriptions_read = False def __init__(self, n): self.attr = {} - if isinstance(n,int): + if isinstance(n, int): self.num = str(n) else: - self.num = n - self.stop=(self.num.find('-')<0 and True or False) + self.num = n + self.stop = self.num.find("-") < 0 and True or False self.comment = None def setStop(self, isStop=True): @@ -58,7 +59,9 @@ def __eq__(self, other): elif isinstance(other, str): return int(self.num) == int(other) else: - WranglerLogger.error("Node.__eq__ called with other type {}".format(type(other))) + WranglerLogger.error( + "Node.__eq__ called with other type {}".format(type(other)) + ) def replaceNum(self, num): """ @@ -74,19 +77,23 @@ def isStop(self): """ Returns True if this node is a stop, False if not. """ - if int(self.num)>0: return True + if int(self.num) > 0: + return True return False def boardsDisallowed(self): """ Returns True if this node is a stop and boardings are disallowed (ACCESS=2) """ - if not self.isStop(): return False - - if "ACCESS" not in self.attr: return False - - if int(self.attr["ACCESS"]) == 2: return True - + if not self.isStop(): + return False + + if "ACCESS" not in self.attr: + return False + + if int(self.attr["ACCESS"]) == 2: + return True + return False def lineFileRepr(self, prependNEquals=False, lastNode=False): @@ -94,62 +101,76 @@ def lineFileRepr(self, prependNEquals=False, lastNode=False): String representation for line file """ - if prependNEquals: s=" N=" - else: s=" " + if prependNEquals: + s = " N=" + else: + s = " " # node number - if self.stop: s+= " " + if self.stop: + s += " " s += self.num # attributes - for k,v in sorted(self.attr.items()): - if k=="DELAY" and float(v)==0: continue # NOP - s +=", %s=%s" % (k,v) + for k, v in sorted(self.attr.items()): + if k == "DELAY" and float(v) == 0: + continue # NOP + s += ", %s=%s" % (k, v) # comma - if not lastNode: s+= "," + if not lastNode: + s += "," # comment - if self.comment: s+=' %s' % (self.comment,) + if self.comment: + s += " %s" % (self.comment,) # eol s += "\n" return s # Dictionary methods - def __getitem__(self,key): return self.attr[key] - def __setitem__(self,key,value): self.attr[key]=value - def __cmp__(self,other): return cmp(int(self.num),other) + def __getitem__(self, key): + return self.attr[key] + + def __setitem__(self, key, value): + self.attr[key] = value + + def __cmp__(self, other): + return cmp(int(self.num), other) def description(self): """ Returns the description of this node (a string), or None if unknown. """ Node.getDescriptions() - + if abs(int(self.num)) in Node.descriptions: return Node.descriptions[abs(int(self.num))] - + return None @staticmethod def getDescriptions(): # if we've already done this, do nothing - if Node.descriptions_read: return - + if Node.descriptions_read: + return + try: import xlrd - workbook = xlrd.open_workbook(filename=os.environ["CHAMP_node_names"], - encoding_override='ascii') - sheet = workbook.sheet_by_name("equiv") + + workbook = xlrd.open_workbook( + filename=os.environ["CHAMP_node_names"], encoding_override="ascii" + ) + sheet = workbook.sheet_by_name("equiv") row = 0 - while (row < sheet.nrows): - Node.descriptions[int(sheet.cell_value(row,0))] = \ - sheet.cell_value(row,1).encode('utf-8') - row+=1 - + while row < sheet.nrows: + Node.descriptions[int(sheet.cell_value(row, 0))] = sheet.cell_value( + row, 1 + ).encode("utf-8") + row += 1 + # print "Read descriptions: " + str(Node.descriptions) - except ImportError: + except ImportError: print("Could not import xlrd module, Node descriptions unknown") except: print("Unexpected error reading Nodes.xls:", sys.exc_info()[0]) print(sys.exc_info()) - + Node.descriptions_read = True - \ No newline at end of file diff --git a/Wrangler/PNRLink.py b/Wrangler/PNRLink.py index 77844cc..5bf3da6 100644 --- a/Wrangler/PNRLink.py +++ b/Wrangler/PNRLink.py @@ -1,29 +1,31 @@ import re from .Regexes import nodepair_pattern -__all__ = ['PNRLink'] +__all__ = ["PNRLink"] + class PNRLink(dict): - """ PNR Support Link. - 'node' property is the node-pair for this link (e.g. 24133-34133) - 'comment' is any end-of-line comment for this link including the leading semicolon - All other attributes are stored in a dictionary (e.g. thislink['MODE']='1,2') + """PNR Support Link. + 'node' property is the node-pair for this link (e.g. 24133-34133) + 'comment' is any end-of-line comment for this link including the leading semicolon + All other attributes are stored in a dictionary (e.g. thislink['MODE']='1,2') """ + UNNUMBERED = "unnumbered" - + def __init__(self): dict.__init__(self) - self.id='' - self.comment='' + self.id = "" + self.comment = "" - self.pnr='' - self.station='' + self.pnr = "" + self.station = "" def __repr__(self): s = "PNR NODE=%s " % (self.id,) # Deal w/all link attributes - fields = ['%s=%s' % (k,v) for k,v in self.items()] + fields = ["%s=%s" % (k, v) for k, v in self.items()] s += " ".join(fields) s += self.comment @@ -41,9 +43,9 @@ def parseID(self): """ if self.id: m = re.match(nodepair_pattern, self.id) - + # it's either just the station - if m == None: # it's a nodenum + if m == None: # it's a nodenum self.station = self.id self.pnr = self.UNNUMBERED # or it's pnr,station @@ -53,4 +55,3 @@ def parseID(self): else: pass - \ No newline at end of file diff --git a/Wrangler/PTSystem.py b/Wrangler/PTSystem.py index c7d0f4a..a449a5e 100644 --- a/Wrangler/PTSystem.py +++ b/Wrangler/PTSystem.py @@ -2,7 +2,8 @@ from .NetworkException import NetworkException -__all__ = ['PTSystem'] +__all__ = ["PTSystem"] + class PTSystem: """ @@ -11,51 +12,68 @@ class PTSystem: """ def __init__(self): - self.waitCurveDefs = collections.OrderedDict() # key is number, value is also ordered dict - self.crowdCurveDefs = collections.OrderedDict() # key is number, value is also ordered dict - self.operators = collections.OrderedDict() # key is number, value is also ordered dict - self.modes = collections.OrderedDict() # key is number, value is also ordered dict - self.vehicleTypes = collections.OrderedDict() # key is number, value is also ordered dict + self.waitCurveDefs = ( + collections.OrderedDict() + ) # key is number, value is also ordered dict + self.crowdCurveDefs = ( + collections.OrderedDict() + ) # key is number, value is also ordered dict + self.operators = ( + collections.OrderedDict() + ) # key is number, value is also ordered dict + self.modes = ( + collections.OrderedDict() + ) # key is number, value is also ordered dict + self.vehicleTypes = ( + collections.OrderedDict() + ) # key is number, value is also ordered dict def isEmpty(self): - if len(self.operators ) > 0: return False - if len(self.modes ) > 0: return False - if len(self.vehicleTypes) > 0: return False + if len(self.operators) > 0: + return False + if len(self.modes) > 0: + return False + if len(self.vehicleTypes) > 0: + return False return True def __repr__(self): - """ Returns string representation. - """ + """Returns string representation.""" s = "" for pt_num, pt_dict in self.modes.items(): s += "MODE" - for k,v in pt_dict.items(): s+= " {}={}".format(k,v) - s+= "\n" + for k, v in pt_dict.items(): + s += " {}={}".format(k, v) + s += "\n" s += "\n" for pt_num, pt_dict in self.operators.items(): s += "OPERATOR" - for k,v in pt_dict.items(): s+= " {}={}".format(k,v) - s+= "\n" + for k, v in pt_dict.items(): + s += " {}={}".format(k, v) + s += "\n" s += "\n" for pt_num, pt_dict in self.vehicleTypes.items(): s += "VEHICLETYPE" - for k,v in pt_dict.items(): s+= " {}={}".format(k,v) - s+= "\n" + for k, v in pt_dict.items(): + s += " {}={}".format(k, v) + s += "\n" s += "\n" for pt_num, pt_dict in self.waitCurveDefs.items(): s += "WAITCRVDEF" - for k,v in pt_dict.items(): s+= " {}={}".format(k,v) - s+= "\n" + for k, v in pt_dict.items(): + s += " {}={}".format(k, v) + s += "\n" s += "\n" for pt_num, pt_dict in self.crowdCurveDefs.items(): s += "CROWDCRVDEF" - for k,v in pt_dict.items(): s+= " {}={}".format(k,v) - s+= "\n" + for k, v in pt_dict.items(): + s += " {}={}".format(k, v) + s += "\n" s += "\n" return s @@ -64,27 +82,41 @@ def merge(self, pts): """ Merges another pts with self. """ - for key,val_dict in pts.waitCurveDefs.items(): - if key in self.waitCurveDefs: # collision - raise NetworkException("PTSystem: Trying to merge WAITCRVDEF with same key: {}".format(key)) + for key, val_dict in pts.waitCurveDefs.items(): + if key in self.waitCurveDefs: # collision + raise NetworkException( + "PTSystem: Trying to merge WAITCRVDEF with same key: {}".format(key) + ) self.waitCurveDefs[key] = val_dict - for key,val_dict in pts.crowdCurveDefs.items(): - if key in self.crowdCurveDefs: # collision - raise NetworkException("PTSystem: Trying to merge CROWDCRVDEF with same key: {}".format(key)) + for key, val_dict in pts.crowdCurveDefs.items(): + if key in self.crowdCurveDefs: # collision + raise NetworkException( + "PTSystem: Trying to merge CROWDCRVDEF with same key: {}".format( + key + ) + ) self.crowdCurveDefs[key] = val_dict - for key,val_dict in pts.operators.items(): - if key in self.operators: # collision - raise NetworkException("PTSystem: Trying to merge OPERATOR with same key: {}".format(key)) + for key, val_dict in pts.operators.items(): + if key in self.operators: # collision + raise NetworkException( + "PTSystem: Trying to merge OPERATOR with same key: {}".format(key) + ) self.operators[key] = val_dict - for key,val_dict in pts.modes.items(): - if key in self.modes: # collision - raise NetworkException("PTSystem: Trying to merge MODE with same key: {}".format(key)) + for key, val_dict in pts.modes.items(): + if key in self.modes: # collision + raise NetworkException( + "PTSystem: Trying to merge MODE with same key: {}".format(key) + ) self.modes[key] = val_dict - for key,val_dict in pts.vehicleTypes.items(): - if key in self.vehicleTypes: # collision - raise NetworkException("PTSystem: Trying to merge VEHICLETYPE with same key: {}".format(key)) + for key, val_dict in pts.vehicleTypes.items(): + if key in self.vehicleTypes: # collision + raise NetworkException( + "PTSystem: Trying to merge VEHICLETYPE with same key: {}".format( + key + ) + ) self.vehicleTypes[key] = val_dict diff --git a/Wrangler/PlanSpecs.py b/Wrangler/PlanSpecs.py index c9606c3..2bc7e68 100644 --- a/Wrangler/PlanSpecs.py +++ b/Wrangler/PlanSpecs.py @@ -3,126 +3,186 @@ from .Network import Network from .Logger import WranglerLogger + class PlanSpecs: - """ Simple class to read in the plan specifications from a CSV file. - """ - - def __init__(self,modelType,modelVersion,basedir,networkdir,plansubdir,projectsubdir=None,tag=None,tempdir=None, **kwargs): + """Simple class to read in the plan specifications from a CSV file.""" + + def __init__( + self, + modelType, + modelVersion, + basedir, + networkdir, + plansubdir, + projectsubdir=None, + tag=None, + tempdir=None, + **kwargs + ): """ Read specs file, check out projects and check the network type and project year """ - self.plan_name = networkdir - self.projects = [] # list of projects included in the plan - self.projectdict = {} # plan name => dictionary of attributes - #network is necessary to check out projects - self.network = Network(modelType=modelType, - modelVersion=modelVersion, - networkBaseDir=basedir, - networkPlanSubdir=plansubdir, - networkProjectSubdir=projectsubdir) - self.tag_override = {} - self.modelyear = None + self.plan_name = networkdir + self.projects = [] # list of projects included in the plan + self.projectdict = {} # plan name => dictionary of attributes + # network is necessary to check out projects + self.network = Network( + modelType=modelType, + modelVersion=modelVersion, + networkBaseDir=basedir, + networkPlanSubdir=plansubdir, + networkProjectSubdir=projectsubdir, + ) + self.tag_override = {} + self.modelyear = None if kwargs: - if 'modelyear' in kwargs.keys(): - self.modelyear = kwargs['modelyear'] - #These next two don't do anything now. - if 'tag_override' in kwargs.keys(): - WranglerLogger.debug('found tag_override: %s' % (kwargs['tag_override'])) - self.tag_override = kwargs['tag_override'] + if "modelyear" in kwargs.keys(): + self.modelyear = kwargs["modelyear"] + # These next two don't do anything now. + if "tag_override" in kwargs.keys(): + WranglerLogger.debug( + "found tag_override: %s" % (kwargs["tag_override"]) + ) + self.tag_override = kwargs["tag_override"] if not isinstance(self.tag_override, dict): - raise NetworkException('when passing tag_override, must be dict type') - - specs = open(os.path.join(tempdir,plansubdir,networkdir,'planSpecs.csv'),'r') - i=0 + raise NetworkException( + "when passing tag_override, must be dict type" + ) + + specs = open( + os.path.join(tempdir, plansubdir, networkdir, "planSpecs.csv"), "r" + ) + i = 0 for line in specs: - i+=1 - if i==1: - header=line.strip().split(',') + i += 1 + if i == 1: + header = line.strip().split(",") else: - l = line.strip().split(',') + l = line.strip().split(",") project_name = l[header.index("projectname")] projType = l[header.index("type")] try: project_tag = self.tag_override[project_name] - WranglerLogger.debug('applying project-specific tag %s to project %s within plan %s' %(project_tag, project_name, self.plan_name)) + WranglerLogger.debug( + "applying project-specific tag %s to project %s within plan %s" + % (project_tag, project_name, self.plan_name) + ) except: project_tag = tag - WranglerLogger.debug('applying general tag %s to project %s within plan %s' %(project_tag, project_name, self.plan_name)) + WranglerLogger.debug( + "applying general tag %s to project %s within plan %s" + % (project_tag, project_name, self.plan_name) + ) self.projectdict[project_name] = {} self.projects.append(project_name) - self.projectdict[project_name]["name"]=project_name - self.projectdict[project_name]["projtype"]=projType + self.projectdict[project_name]["name"] = project_name + self.projectdict[project_name]["projtype"] = projType try: - self.projectdict[project_name]["modelyear"] = kwargs['modelyear'] - #WranglerLogger.debug('project %s using MODELYEAR %s' % (project_name, kwargs['modelyear'])) + self.projectdict[project_name]["modelyear"] = kwargs["modelyear"] + # WranglerLogger.debug('project %s using MODELYEAR %s' % (project_name, kwargs['modelyear'])) except Exception as e: - self.projectdict[project_name]["modelyear"] = None #WranglerLogger.debug( - #WranglerLogger.debug('project %s: MODELYEAR error: %s' % (project_name, e)) + self.projectdict[project_name][ + "modelyear" + ] = None # WranglerLogger.debug( + # WranglerLogger.debug('project %s: MODELYEAR error: %s' % (project_name, e)) try: - self.projectdict[project_name]['tag'] = kwargs['tag_override'][project_name] - #WranglerLogger.debug('project %s using TAG %s' % (project_name, kwargs['tag_override'][project_name])) + self.projectdict[project_name]["tag"] = kwargs["tag_override"][ + project_name + ] + # WranglerLogger.debug('project %s using TAG %s' % (project_name, kwargs['tag_override'][project_name])) except Exception as e: - self.projectdict[project_name]['tag'] = None - #WranglerLogger.debug('project %s: TAG error: %s' % (project_name, e)) - + self.projectdict[project_name]["tag"] = None + # WranglerLogger.debug('project %s: TAG error: %s' % (project_name, e)) + # if project = "dir1/dir2" assume dir1 is git, dir2 is the projectsubdir - (head,tail) = os.path.split(project_name) + (head, tail) = os.path.split(project_name) if head: - applied_SHA1 = self.network.cloneProject(networkdir=head, projectsubdir=tail, tag=project_tag, - projtype=projType, tempdir=tempdir) - (parentdir, networkdir, gitdir, projectsubdir) = self.network.getClonedProjectArgs(head, tail, projType, tempdir) - self.projectdict[project_name]["nettypes"]=self.network.getNetTypes(tempdir,head,tail) + applied_SHA1 = self.network.cloneProject( + networkdir=head, + projectsubdir=tail, + tag=project_tag, + projtype=projType, + tempdir=tempdir, + ) + ( + parentdir, + networkdir, + gitdir, + projectsubdir, + ) = self.network.getClonedProjectArgs(head, tail, projType, tempdir) + self.projectdict[project_name][ + "nettypes" + ] = self.network.getNetTypes(tempdir, head, tail) else: - applied_SHA1 = self.network.cloneProject(networkdir=project_name, tag=project_tag, - projtype=projType, tempdir=tempdir) - (parentdir, networkdir, gitdir, projectsubdir) = self.network.getClonedProjectArgs(project_name, None, projType, tempdir) - self.projectdict[project_name]["nettypes"]=self.network.getNetTypes(tempdir,project_name) - self.projectdict[project_name]["year"]= self.network.getAttr('year',parentdir, networkdir, gitdir, projectsubdir) + applied_SHA1 = self.network.cloneProject( + networkdir=project_name, + tag=project_tag, + projtype=projType, + tempdir=tempdir, + ) + ( + parentdir, + networkdir, + gitdir, + projectsubdir, + ) = self.network.getClonedProjectArgs( + project_name, None, projType, tempdir + ) + self.projectdict[project_name][ + "nettypes" + ] = self.network.getNetTypes(tempdir, project_name) + self.projectdict[project_name]["year"] = self.network.getAttr( + "year", parentdir, networkdir, gitdir, projectsubdir + ) - def projectAsDict(self,project_name): + def projectAsDict(self, project_name): projDict = {} - projDict['name'] = project_name - projDict['kwargs'] = {} - if 'projtype' in self.projectdict[project_name].keys(): - projDict['type'] = self.projectdict[project_name]['projtype'] - if 'modelyear' in self.projectdict[project_name].keys(): - projDict['kwargs']['modelyear'] = self.projectdict[project_name]['modelyear'] - if 'tag' in self.projectdict[project_name].keys(): - projDict['tag'] = self.projectdict[project_name]['tag'] -## if 'kwargs' in self.projectdict[project_name].keys(): -## projDict['kwargs'] = self.projectdict[project_name]['kwargs'] + projDict["name"] = project_name + projDict["kwargs"] = {} + if "projtype" in self.projectdict[project_name].keys(): + projDict["type"] = self.projectdict[project_name]["projtype"] + if "modelyear" in self.projectdict[project_name].keys(): + projDict["kwargs"]["modelyear"] = self.projectdict[project_name][ + "modelyear" + ] + if "tag" in self.projectdict[project_name].keys(): + projDict["tag"] = self.projectdict[project_name]["tag"] + ## if 'kwargs' in self.projectdict[project_name].keys(): + ## projDict['kwargs'] = self.projectdict[project_name]['kwargs'] return projDict - def listOfProjects(self,netType='hwy'): + def listOfProjects(self, netType="hwy"): """ Returns a list of project names. """ projectlist = [] for proj in self.projects: - if netType in self.projectdict[proj]['nettypes']: + if netType in self.projectdict[proj]["nettypes"]: projectlist.append(self.projectAsDict(proj)) -## if not self.modelyear or self.modelyear >= self.projectdict[proj]["year"]: -## projectlist.append(self.projectAsDict(proj)) -## else: -## WranglerLogger.warn("not applying %s, projectyear %d >= modelyear %d" % (proj, self.projectdict[proj]["year"], self.modelyear)) + ## if not self.modelyear or self.modelyear >= self.projectdict[proj]["year"]: + ## projectlist.append(self.projectAsDict(proj)) + ## else: + ## WranglerLogger.warn("not applying %s, projectyear %d >= modelyear %d" % (proj, self.projectdict[proj]["year"], self.modelyear)) return projectlist - - def printProjects(self,fileObj): + + def printProjects(self, fileObj): pass -## fileObj.write("YEAR PROJECT HWY MUNI RAIL BUS \n") -## fileObj.write("----------------------------------------------------\n") -## for p in self.projects: -## fileObj.write( str(p['year'])+" "+p['name']+" "+p['hwy']+" "+p['muni']+" "+p['rail']+" "+p['bus']+"\n") - + + ## fileObj.write("YEAR PROJECT HWY MUNI RAIL BUS \n") + ## fileObj.write("----------------------------------------------------\n") + ## for p in self.projects: + ## fileObj.write( str(p['year'])+" "+p['name']+" "+p['hwy']+" "+p['muni']+" "+p['rail']+" "+p['bus']+"\n") + def logProjects(self, logger): pass + + ## logger.info("YEAR PROJECT HWY MUNI RAIL BUS \n") ## logger.info("----------------------------------------------------") ## for p in self.projects: ## logger.info( sstr(p['year'])+" "+p['name']+" "+p['hwy']+" "+p['muni']+" "+p['rail']+" "+p['bus']) - diff --git a/Wrangler/Regexes.py b/Wrangler/Regexes.py index a5fb8f3..1d341a2 100644 --- a/Wrangler/Regexes.py +++ b/Wrangler/Regexes.py @@ -1,6 +1,6 @@ import re -__all__ = [ 'nodepair_pattern', 'git_commit_pattern'] +__all__ = ["nodepair_pattern", "git_commit_pattern"] -nodepair_pattern = re.compile('(\d+)[-,\s]+(\d+)') -git_commit_pattern = re.compile('commit ([0-9a-f]{40}$)') +nodepair_pattern = re.compile("(\d+)[-,\s]+(\d+)") +git_commit_pattern = re.compile("commit ([0-9a-f]{40}$)") diff --git a/Wrangler/Supplink.py b/Wrangler/Supplink.py index 48f801c..b6ca947 100644 --- a/Wrangler/Supplink.py +++ b/Wrangler/Supplink.py @@ -1,41 +1,45 @@ from .NetworkException import NetworkException -__all__ = ['Supplink'] +__all__ = ["Supplink"] + class Supplink(dict): - """ PNR Support Link. - 'node' property is the node-pair for this link (e.g. 24133-34133) - 'comment' is any end-of-line comment for this link including the leading semicolon - All other attributes are stored in a dictionary (e.g. thislink['MODE']='1,2') + """PNR Support Link. + 'node' property is the node-pair for this link (e.g. 24133-34133) + 'comment' is any end-of-line comment for this link including the leading semicolon + All other attributes are stored in a dictionary (e.g. thislink['MODE']='1,2') """ - MODES = {1:"WALK_ACCESS", - 2:"WALK_EGRESS", - 3:"DRIVE_ACCESS", - 4:"DRIVE_EGRESS", - 5:"TRANSIT_TRANSFER", - 6:"DRIVE_FUNNEL", - 7:"WALK_FUNNEL"} - + + MODES = { + 1: "WALK_ACCESS", + 2: "WALK_EGRESS", + 3: "DRIVE_ACCESS", + 4: "DRIVE_EGRESS", + 5: "TRANSIT_TRANSFER", + 6: "DRIVE_FUNNEL", + 7: "WALK_FUNNEL", + } + def __init__(self): dict.__init__(self) - MODES_INV = dict((v,k) for k,v in Supplink.MODES.items()) + MODES_INV = dict((v, k) for k, v in Supplink.MODES.items()) + + self.id = "" # string, e.g. "1-7719" + self.comment = None - self.id='' # string, e.g. "1-7719" - self.comment=None - # components of ID, ints self.Anode = None self.Bnode = None - self.mode = None + self.mode = None def __repr__(self): - s = "SUPPLINK N=%5d-%5d " % (self.Anode,self.Bnode) + s = "SUPPLINK N=%5d-%5d " % (self.Anode, self.Bnode) # Deal w/all link attributes fields = [] for k in sorted(self.keys()): - fields.append('%s=%s' % (k,self[k])) + fields.append("%s=%s" % (k, self[k])) s += " ".join(fields) if self.comment: @@ -45,79 +49,86 @@ def __repr__(self): def setId(self, id): self.id = id - - nodeList=self.id.split('-') + + nodeList = self.id.split("-") self.Anode = int(nodeList[0]) self.Bnode = int(nodeList[1]) - + def setMode(self, newmode=None): """ If newmode is passed, then uses that. Otherwise, figure out the mode from the text in the dictionary. """ - if newmode==None and self.mode: return - + if newmode == None and self.mode: + return + # find it in my dictionary - for k,v in self.items(): + for k, v in self.items(): if k.lower() == "mode": if newmode: self.mode = newmode self[k] = str(self.mode) else: self.mode = int(v) - + # it wasn't in the dictionary if newmode and not self.mode: self.mode = newmode self["MODE"] = str(self.mode) - + if not self.mode: raise NetworkException("Supplink mode not set: " + str(self)) def isWalkAccess(self): self.setMode() - return (Supplink.MODES[self.mode]=="WALK_ACCESS") - + return Supplink.MODES[self.mode] == "WALK_ACCESS" + def isWalkEgress(self): self.setMode() - return (Supplink.MODES[self.mode]=="WALK_EGRESS") - + return Supplink.MODES[self.mode] == "WALK_EGRESS" + def isDriveAccess(self): self.setMode() - return (Supplink.MODES[self.mode]=="DRIVE_ACCESS") + return Supplink.MODES[self.mode] == "DRIVE_ACCESS" def isDriveEgress(self): self.setMode() - return (Supplink.MODES[self.mode]=="DRIVE_EGRESS") + return Supplink.MODES[self.mode] == "DRIVE_EGRESS" def isTransitTransfer(self): self.setMode() - return (Supplink.MODES[self.mode]=="TRANSIT_TRANSFER") - + return Supplink.MODES[self.mode] == "TRANSIT_TRANSFER" + def isWalkFunnel(self): self.setMode() - return (Supplink.MODES[self.mode]=="WALK_FUNNEL") + return Supplink.MODES[self.mode] == "WALK_FUNNEL" def isDriveFunnel(self): self.setMode() - return (Supplink.MODES[self.mode]=="DRIVE_FUNNEL") - + return Supplink.MODES[self.mode] == "DRIVE_FUNNEL" + def isOneWay(self): - for k,v in self.items(): - if k.upper() == "ONEWAY": return v.upper() in ["Y", "YES", "1", "T", "TRUE"] + for k, v in self.items(): + if k.upper() == "ONEWAY": + return v.upper() in ["Y", "YES", "1", "T", "TRUE"] # Cube says default is False return False - + def reverse(self): # not one-way; nothing to do - if not self.isOneWay(): return - + if not self.isOneWay(): + return + temp = self.Anode self.Anode = self.Bnode self.Bnode = temp - + self.id = "%d-%d" % (self.Anode, self.Bnode) - if self.isWalkAccess(): self.setMode(Supplink.MODES_INV["WALK_EGRESS"]) - elif self.isWalkEgress(): self.setMode(Supplink.MODES_INV["WALK_ACCESS"]) - elif self.isDriveAccess(): self.setMode(Supplink.MODES_INV["DRIVE_EGRESS"]) - elif self.isDriveEgress(): self.setMode(Supplink.MODES_INV["DRIVE_ACCESS"]) \ No newline at end of file + if self.isWalkAccess(): + self.setMode(Supplink.MODES_INV["WALK_EGRESS"]) + elif self.isWalkEgress(): + self.setMode(Supplink.MODES_INV["WALK_ACCESS"]) + elif self.isDriveAccess(): + self.setMode(Supplink.MODES_INV["DRIVE_EGRESS"]) + elif self.isDriveEgress(): + self.setMode(Supplink.MODES_INV["DRIVE_ACCESS"]) diff --git a/Wrangler/TransitAssignmentData.py b/Wrangler/TransitAssignmentData.py index 2857e7b..033704b 100644 --- a/Wrangler/TransitAssignmentData.py +++ b/Wrangler/TransitAssignmentData.py @@ -2,7 +2,7 @@ # Original revision: Lisa Zorn 2010-8-5 # based on old "combineTransitDBFs.py" # -import csv,os,logging,string,sys,traceback,xlrd +import csv, os, logging, string, sys, traceback, xlrd from dataTable import DataTable, dbfTableReader, FieldType from .TransitCapacity import TransitCapacity from .TransitLine import TransitLine @@ -13,89 +13,117 @@ print("Importing ", __file__) -__all__ = ['TransitAssignmentData', 'TransitAssignmentDataException'] +__all__ = ["TransitAssignmentData", "TransitAssignmentDataException"] + + +class TransitAssignmentDataException(Exception): + pass -class TransitAssignmentDataException(Exception): pass class TransitAssignmentData: - - TIMEPERIOD_TO_VEHTYPIDX = { "AM":2, "MD": 4, "PM":3, "EV":4, "EA":4 } - - - def __init__(self, directory=".", timeperiod="AM", modelType=Network.MODEL_TYPE_CHAMP, - champtype="champ4", muniTEP=True, ignoreModes=[], - system=[], profileNode=False,tpfactor="quickboards",grouping=None, - transitCapacity=None, - lineLevelAggregateFilename=None, linkLevelAggregateFilename=None): + + TIMEPERIOD_TO_VEHTYPIDX = {"AM": 2, "MD": 4, "PM": 3, "EV": 4, "EA": 4} + + def __init__( + self, + directory=".", + timeperiod="AM", + modelType=Network.MODEL_TYPE_CHAMP, + champtype="champ4", + muniTEP=True, + ignoreModes=[], + system=[], + profileNode=False, + tpfactor="quickboards", + grouping=None, + transitCapacity=None, + lineLevelAggregateFilename=None, + linkLevelAggregateFilename=None, + ): """ - * *directory* is the location of the transit assignment files - * *modelType* should be MODEL_TYPE_CHAMP, MODEL_TYPE_TM1, or MODEL_TYPE_TM2 - * *timeperiod* is a string in ["AM", "MD", "PM", "EV", "EA" ] - * *champtype* is a string in ["champ4", "champ3", "champ3-sfonly"] - * *muniTEP* is only important for Muni files, but it matters because vehicle type is different - * pass *ignoreModes* to ignore some, such as [11,12,13,14,15,16,17] to ignore access/egress/xfer - * pass *system* to restrict looking only at given systems, e.g. ["SF MUNI", "BART" ] - * pass *profileNode* to only look at links to or from that node - * *tpfactor* determines the time period peak hour factor. Must be one of ```quickboards``` - or ```constant``` or ```constant_with_peaked_muni```. - * Uses *transitLineToVehicle* and *transitVehicleToCapacity* to map transit lines to vehicle types, - and vehicle types to capacities. - * If *lineLevelAggregateFilename* or *linkLevelAggregateFilename* are passed in, then - it is assumed that the many transit assignment dbfs have already been aggregated (likely - by this very class!) and we should just read those instead of doing the work again. + * *directory* is the location of the transit assignment files + * *modelType* should be MODEL_TYPE_CHAMP, MODEL_TYPE_TM1, or MODEL_TYPE_TM2 + * *timeperiod* is a string in ["AM", "MD", "PM", "EV", "EA" ] + * *champtype* is a string in ["champ4", "champ3", "champ3-sfonly"] + * *muniTEP* is only important for Muni files, but it matters because vehicle type is different + * pass *ignoreModes* to ignore some, such as [11,12,13,14,15,16,17] to ignore access/egress/xfer + * pass *system* to restrict looking only at given systems, e.g. ["SF MUNI", "BART" ] + * pass *profileNode* to only look at links to or from that node + * *tpfactor* determines the time period peak hour factor. Must be one of ```quickboards``` + or ```constant``` or ```constant_with_peaked_muni```. + * Uses *transitLineToVehicle* and *transitVehicleToCapacity* to map transit lines to vehicle types, + and vehicle types to capacities. + * If *lineLevelAggregateFilename* or *linkLevelAggregateFilename* are passed in, then + it is assumed that the many transit assignment dbfs have already been aggregated (likely + by this very class!) and we should just read those instead of doing the work again. """ - - + # from quickboards - if tpfactor=="quickboards": - self.TIMEPERIOD_FACTOR = { "AM":0.44, "MD":0.18, "PM":0.37, "EV":0.22, "EA":0.58 } - elif tpfactor=="constant": - self.TIMEPERIOD_FACTOR ={} + if tpfactor == "quickboards": + self.TIMEPERIOD_FACTOR = { + "AM": 0.44, + "MD": 0.18, + "PM": 0.37, + "EV": 0.22, + "EA": 0.58, + } + elif tpfactor == "constant": + self.TIMEPERIOD_FACTOR = {} for tp in ["AM", "MD", "PM", "EV", "EA"]: - self.TIMEPERIOD_FACTOR[tp] = 1.0/TransitLine.HOURS_PER_TIMEPERIOD[modelType][tp] - elif tpfactor=="constant_with_peaked_muni": + self.TIMEPERIOD_FACTOR[tp] = ( + 1.0 / TransitLine.HOURS_PER_TIMEPERIOD[modelType][tp] + ) + elif tpfactor == "constant_with_peaked_muni": # defaults - self.TIMEPERIOD_FACTOR ={} + self.TIMEPERIOD_FACTOR = {} for tp in ["AM", "MD", "PM", "EV", "EA"]: - self.TIMEPERIOD_FACTOR[tp] = 1.0/TransitLine.HOURS_PER_TIMEPERIOD[modelType][tp] + self.TIMEPERIOD_FACTOR[tp] = ( + 1.0 / TransitLine.HOURS_PER_TIMEPERIOD[modelType][tp] + ) # muni peaking - muni_peaking = {"AM":0.45, # 0.39 / 0.85 (Muni peaking factor from 2010 APC / Muni's capacity ratio) - "MD":1/TransitLine.HOURS_PER_TIMEPERIOD[modelType]["MD"], - "PM":0.45, - "EV":0.2, - "EA":1/TransitLine.HOURS_PER_TIMEPERIOD[modelType]["EA"]} + muni_peaking = { + "AM": 0.45, # 0.39 / 0.85 (Muni peaking factor from 2010 APC / Muni's capacity ratio) + "MD": 1 / TransitLine.HOURS_PER_TIMEPERIOD[modelType]["MD"], + "PM": 0.45, + "EV": 0.2, + "EA": 1 / TransitLine.HOURS_PER_TIMEPERIOD[modelType]["EA"], + } if modelType == Network.MODEL_TYPE_CHAMP: - self.TIMEPERIOD_FACTOR[11] = muni_peaking # muni bus - self.TIMEPERIOD_FACTOR[12] = muni_peaking # muni express bus - self.TIMEPERIOD_FACTOR[13] = muni_peaking # muni BRT - self.TIMEPERIOD_FACTOR[14] = muni_peaking # muni cable car - self.TIMEPERIOD_FACTOR[15] = muni_peaking # muni LRT + self.TIMEPERIOD_FACTOR[11] = muni_peaking # muni bus + self.TIMEPERIOD_FACTOR[12] = muni_peaking # muni express bus + self.TIMEPERIOD_FACTOR[13] = muni_peaking # muni BRT + self.TIMEPERIOD_FACTOR[14] = muni_peaking # muni cable car + self.TIMEPERIOD_FACTOR[15] = muni_peaking # muni LRT elif modelType == Network.MODEL_TYPE_TM1: - self.TIMEPERIOD_FACTOR[20] = muni_peaking # muni cable car - self.TIMEPERIOD_FACTOR[21] = muni_peaking # muni local bus - self.TIMEPERIOD_FACTOR[110] = muni_peaking # muni LRT + self.TIMEPERIOD_FACTOR[20] = muni_peaking # muni cable car + self.TIMEPERIOD_FACTOR[21] = muni_peaking # muni local bus + self.TIMEPERIOD_FACTOR[110] = muni_peaking # muni LRT else: - raise TransitAssignmentDataException("Invalid time period factor "+str(tpfactor)) + raise TransitAssignmentDataException( + "Invalid time period factor " + str(tpfactor) + ) - self.assigndir = directory + self.assigndir = directory self.timeperiod = timeperiod - self.modelType = modelType - self.champtype = champtype - self.ignoreModes= ignoreModes - self.system = system - self.profileNode= profileNode - self.aggregateAll = True # aggregate for A,B? + self.modelType = modelType + self.champtype = champtype + self.ignoreModes = ignoreModes + self.system = system + self.profileNode = profileNode + self.aggregateAll = True # aggregate for A,B? if transitCapacity: - self.capacity = transitCapacity + self.capacity = transitCapacity else: - self.capacity = TransitCapacity() - self.csvColnames= None # uninitialized + self.capacity = TransitCapacity() + self.csvColnames = None # uninitialized if self.timeperiod not in ["AM", "MD", "PM", "EV", "EA"]: - raise TransitAssignmentDataException("Invalid timeperiod "+str(timeperiod)) - if self.champtype not in ["champ3","champ4","champ3-sfonly"]: - raise TransitAssignmentDataException("Invalid champtypte "+str(champtype)) + raise TransitAssignmentDataException( + "Invalid timeperiod " + str(timeperiod) + ) + if self.champtype not in ["champ3", "champ4", "champ3-sfonly"]: + raise TransitAssignmentDataException("Invalid champtypte " + str(champtype)) # supplementary workbooks if grouping and (grouping.upper() == "RAPID"): @@ -103,16 +131,19 @@ def __init__(self, directory=".", timeperiod="AM", modelType=Network.MODEL_TYPE_ else: self.lineToGroup = self.readTransitLineGrouping(mapfile=grouping) - # Already aggregated up? if lineLevelAggregateFilename: - self.readAggregateDbfs(asgnFileName=lineLevelAggregateFilename, - aggregateFileName=linkLevelAggregateFilename) + self.readAggregateDbfs( + asgnFileName=lineLevelAggregateFilename, + aggregateFileName=linkLevelAggregateFilename, + ) return # To determine what files we'll open - if not 'ALLTRIPMODES' in os.environ: - raise NetworkException("No ALLTRIPMODES in environment for TransitAssignmentData to decide on input files") + if not "ALLTRIPMODES" in os.environ: + raise NetworkException( + "No ALLTRIPMODES in environment for TransitAssignmentData to decide on input files" + ) self.MODES = os.environ["ALLTRIPMODES"].split(" ") WranglerLogger.debug("TransitAssignmentData MODES = " + str(self.MODES)) @@ -122,42 +153,73 @@ def readTransitLineGrouping(self, mapfile=None): """ Read the transit line groupings file which assigns a grouping to lines """ - if not mapfile: return {} - + if not mapfile: + return {} + lineToGroup = {} try: - workbook = xlrd.open_workbook(filename=mapfile,encoding_override='ascii') + workbook = xlrd.open_workbook(filename=mapfile, encoding_override="ascii") except: - print("couldn't find that workbook %s, yo! No Groupings used!".format(mapefile)) + print( + "couldn't find that workbook %s, yo! No Groupings used!".format( + mapefile + ) + ) return lineToGroup - sheet = workbook.sheet_by_name("Lookup") + sheet = workbook.sheet_by_name("Lookup") row = 1 - while (row < sheet.nrows): - group = sheet.cellvalue(row,1).encode('utf-8') - self.lineToGroup[lookupsheet.cell_value(row,0).encode('utf-8')]=lookupsheet.cell_value(row,1).encode('utf-8') - row+=1 + while row < sheet.nrows: + group = sheet.cellvalue(row, 1).encode("utf-8") + self.lineToGroup[ + lookupsheet.cell_value(row, 0).encode("utf-8") + ] = lookupsheet.cell_value(row, 1).encode("utf-8") + row += 1 return lineToGroup - + def assignMuniRapid(self): lineToGroup = {} - RapidList=["E","J","K","L","M","N", - "1","1DRM","1PRS","1STN","1CRN","1SHT", - "5","5SHT","5EV","5L", - "9","9SHT","9L","9EVE","9X", - "14L","14X", - "22", - "28L", - "30","30SHT","30WSQ","30X", - "38L", - "47", - "49","49L", - "71","71L"] + RapidList = [ + "E", + "J", + "K", + "L", + "M", + "N", + "1", + "1DRM", + "1PRS", + "1STN", + "1CRN", + "1SHT", + "5", + "5SHT", + "5EV", + "5L", + "9", + "9SHT", + "9L", + "9EVE", + "9X", + "14L", + "14X", + "22", + "28L", + "30", + "30SHT", + "30WSQ", + "30X", + "38L", + "47", + "49", + "49L", + "71", + "71L", + ] for genericLine in RapidList: - for dir in ["I","O"]: - lineToGroup["MUN"+genericLine+dir]="RAPID" + for dir in ["I", "O"]: + lineToGroup["MUN" + genericLine + dir] = "RAPID" return lineToGroup - def initializeFields(self, headerRow=None): """ Initializes the *trnAsgnFields*, *trnAsgnCopyFields*, *trnAsgnAdditiveFields*, @@ -166,232 +228,340 @@ def initializeFields(self, headerRow=None): if headerRow: self.csvColnames = headerRow else: - self.csvColnames = ["A","B","TIME","MODE", #"FREQ", - "PLOT", #"COLOR", - "STOP_A","STOP_B","DIST", - "NAME", #"SEQ", - "OWNER", - "AB_VOL","AB_BRDA","AB_XITA","AB_BRDB","AB_XITB", - "BA_VOL","BA_BRDA","BA_XITA","BA_BRDB","BA_XITB"] - - self.colnameToCsvIndex = dict((self.csvColnames[idx],idx) for idx in range(len(self.csvColnames))) + self.csvColnames = [ + "A", + "B", + "TIME", + "MODE", # "FREQ", + "PLOT", # "COLOR", + "STOP_A", + "STOP_B", + "DIST", + "NAME", # "SEQ", + "OWNER", + "AB_VOL", + "AB_BRDA", + "AB_XITA", + "AB_BRDB", + "AB_XITB", + "BA_VOL", + "BA_BRDA", + "BA_XITA", + "BA_BRDB", + "BA_XITB", + ] + + self.colnameToCsvIndex = dict( + (self.csvColnames[idx], idx) for idx in range(len(self.csvColnames)) + ) # copy these directly - self.trnAsgnFields = {"A": 'u4', - "B": 'u4', - "TIME": 'u4', - "MODE": 'u1', - "PLOT": 'u1', - "STOP_A": 'b', - "STOP_B": 'b', - "DIST": 'u4', - "NAME": 'a13', - "OWNER": 'a10', - } + self.trnAsgnFields = { + "A": "u4", + "B": "u4", + "TIME": "u4", + "MODE": "u1", + "PLOT": "u1", + "STOP_A": "b", + "STOP_B": "b", + "DIST": "u4", + "NAME": "a13", + "OWNER": "a10", + } self.trnAsgnCopyFields = list(self.trnAsgnFields.keys()) # these are in the dbf not the csv (grrrr) - self.trnAsgnFields["FREQ"] = 'f4' - self.trnAsgnFields["SEQ"] = 'u1' - self.trnAsgnFields["COLOR"] = 'u1' + self.trnAsgnFields["FREQ"] = "f4" + self.trnAsgnFields["SEQ"] = "u1" + self.trnAsgnFields["COLOR"] = "u1" # Lets also ad these for easy joining - self.trnAsgnFields["AB"] ='a15' - self.trnAsgnFields["ABNAMESEQ"] ='a30' + self.trnAsgnFields["AB"] = "a15" + self.trnAsgnFields["ABNAMESEQ"] = "a30" # Straight lookup based on the line name - self.trnAsgnFields["GROUP"] ='a20' - self.trnAsgnFields["FULLNAME"] ='a40' - self.trnAsgnFields["SYSTEM"] ='a25' - self.trnAsgnFields["VEHTYPE"] ='a40' - self.trnAsgnFields["VEHCAP"] ='u2' + self.trnAsgnFields["GROUP"] = "a20" + self.trnAsgnFields["FULLNAME"] = "a40" + self.trnAsgnFields["SYSTEM"] = "a25" + self.trnAsgnFields["VEHTYPE"] = "a40" + self.trnAsgnFields["VEHCAP"] = "u2" # Calculated in the first pass - self.trnAsgnFields["PERIODCAP"] ='f4' + self.trnAsgnFields["PERIODCAP"] = "f4" # Additive fields are all U4 - #Here, I need to read in if volume is float, if is, flag if it is, write accordingly - self.trnAsgnAdditiveFields = ["AB_VOL","AB_BRDA","AB_XITA","AB_BRDB","AB_XITB", - "BA_VOL","BA_BRDA","BA_XITA","BA_BRDB","BA_XITB"] + # Here, I need to read in if volume is float, if is, flag if it is, write accordingly + self.trnAsgnAdditiveFields = [ + "AB_VOL", + "AB_BRDA", + "AB_XITA", + "AB_BRDB", + "AB_XITB", + "BA_VOL", + "BA_BRDA", + "BA_XITA", + "BA_BRDB", + "BA_XITB", + ] for field in self.trnAsgnAdditiveFields: - self.trnAsgnFields[field]='f4' - + self.trnAsgnFields[field] = "f4" # Calculated at the end - self.trnAsgnFields["LOAD"] ='f4' + self.trnAsgnFields["LOAD"] = "f4" # aggregate fields - self.aggregateFields = {"A": 'u4', - "B": 'u4', - "AB": 'a15', - "FREQ": 'f4', # combined freq - "DIST": 'u4', # should be the same so first - "VEHCAP":'u2', # sum - "PERIODCAP":'f4', # sum - "LOAD": 'f4', # combined - "MAXLOAD":'f4', # max load of any line on the link - } + self.aggregateFields = { + "A": "u4", + "B": "u4", + "AB": "a15", + "FREQ": "f4", # combined freq + "DIST": "u4", # should be the same so first + "VEHCAP": "u2", # sum + "PERIODCAP": "f4", # sum + "LOAD": "f4", # combined + "MAXLOAD": "f4", # max load of any line on the link + } for field in self.trnAsgnAdditiveFields: - self.aggregateFields[field]='f4' + self.aggregateFields[field] = "f4" def readTransitAssignmentCsvs(self): """ Read the transit assignment dbfs, the direct output of Cube's transit assignment. """ - self.trnAsgnTable = False + self.trnAsgnTable = False self.aggregateTable = False warnline = {} ABNameSeq_List = [] # (A,B,NAME,SEQ) from the dbf/csvs - + # open the input assignment files for mode in self.MODES: if self.modelType == Network.MODEL_TYPE_CHAMP: if mode == "WMWVIS": - filename = os.path.join(self.assigndir, "VISWMW" + self.timeperiod + ".csv") - elif mode[1]=="T": - filename = os.path.join(self.assigndir, "NS" + mode + self.timeperiod + ".csv") + filename = os.path.join( + self.assigndir, "VISWMW" + self.timeperiod + ".csv" + ) + elif mode[1] == "T": + filename = os.path.join( + self.assigndir, "NS" + mode + self.timeperiod + ".csv" + ) else: - filename = os.path.join(self.assigndir, "SF" + mode + self.timeperiod + ".csv") + filename = os.path.join( + self.assigndir, "SF" + mode + self.timeperiod + ".csv" + ) elif self.modelType == Network.MODEL_TYPE_TM1: - filename = os.path.join(self.assigndir, "trnlink{}_{}.csv".format(self.timeperiod.lower(), mode)) - + filename = os.path.join( + self.assigndir, + "trnlink{}_{}.csv".format(self.timeperiod.lower(), mode), + ) + # Read the DBF file into datatable - WranglerLogger.info("Reading "+filename) + WranglerLogger.info("Reading " + filename) # Create our table data structure once if mode == self.MODES[0]: # figure out how many records numrecs = 0 totalrows = 0 - - filereader = csv.reader(open(filename, 'r'), delimiter=',', quoting=csv.QUOTE_NONE) + + filereader = csv.reader( + open(filename, "r"), delimiter=",", quoting=csv.QUOTE_NONE + ) for row in filereader: - + # header row? - if row[0]=="A": + if row[0] == "A": self.initializeFields(row) continue - elif totalrows==0 and not self.csvColnames: + elif totalrows == 0 and not self.csvColnames: self.initializeFields() - totalrows += 1 - if self.profileNode and \ - (int(row[self.colnameToCsvIndex["A"]]) != self.profileNode and - int(row[self.colnameToCsvIndex["B"]]) != self.profileNode): continue - - if int(row[self.colnameToCsvIndex["MODE"]]) in self.ignoreModes: continue - + if self.profileNode and ( + int(row[self.colnameToCsvIndex["A"]]) != self.profileNode + and int(row[self.colnameToCsvIndex["B"]]) != self.profileNode + ): + continue + + if int(row[self.colnameToCsvIndex["MODE"]]) in self.ignoreModes: + continue + linename = row[self.colnameToCsvIndex["NAME"]].strip() # exclude this system? - (system, vehicletype) = self.capacity.getSystemAndVehicleType(linename, self.timeperiod) + (system, vehicletype) = self.capacity.getSystemAndVehicleType( + linename, self.timeperiod + ) - if len(self.system)>0 and system not in self.system: continue + if len(self.system) > 0 and system not in self.system: + continue numrecs += 1 - WranglerLogger.info("Keeping %d records out of %d" % (numrecs, totalrows)) - - self.trnAsgnTable = DataTable(numRecords=numrecs, - fieldNames=self.trnAsgnFields.keys(), - numpyFieldTypes=list(self.trnAsgnFields.values())) + WranglerLogger.info( + "Keeping %d records out of %d" % (numrecs, totalrows) + ) + + self.trnAsgnTable = DataTable( + numRecords=numrecs, + fieldNames=self.trnAsgnFields.keys(), + numpyFieldTypes=list(self.trnAsgnFields.values()), + ) ABNameSeqSet = set() WranglerLogger.debug("Created dataTable") - + # Go through the records - newrownum = 0 # row number in the trnAsgnTable,ABNameSeq_List -- rows we're keeping + newrownum = ( + 0 # row number in the trnAsgnTable,ABNameSeq_List -- rows we're keeping + ) oldrownum = 0 # row number in the csv,dbf -- all input rows - - filereader = csv.reader(open(filename, 'r'), delimiter=',', quoting=csv.QUOTE_NONE) + + filereader = csv.reader( + open(filename, "r"), delimiter=",", quoting=csv.QUOTE_NONE + ) # for the first csv only, also read the dbf for the freq and seq fields if mode == self.MODES[0]: if self.modelType == Network.MODEL_TYPE_CHAMP: - indbf = dbfTableReader(os.path.join(self.assigndir, "SFWBW" + self.timeperiod + ".dbf")) + indbf = dbfTableReader( + os.path.join(self.assigndir, "SFWBW" + self.timeperiod + ".dbf") + ) elif self.modelType == Network.MODEL_TYPE_TM1: - indbf = dbfTableReader(os.path.join(self.assigndir, "trnlink{}_{}.dbf".format(self.timeperiod.lower(), mode))) + indbf = dbfTableReader( + os.path.join( + self.assigndir, + "trnlink{}_{}.dbf".format(self.timeperiod.lower(), mode), + ) + ) else: indbf = None - + for row in filereader: # header row? - if row[0]=="A": continue - + if row[0] == "A": + continue + if self.profileNode: - if (int(row[self.colnameToCsvIndex["A"]]) != self.profileNode and - int(row[self.colnameToCsvIndex["B"]]) != self.profileNode): continue + if ( + int(row[self.colnameToCsvIndex["A"]]) != self.profileNode + and int(row[self.colnameToCsvIndex["B"]]) != self.profileNode + ): + continue elif int(row[self.colnameToCsvIndex["AB_VOL"]]) > 0: - WranglerLogger.info("Link %s %s for mode %s has AB_VOL %s" % - (row[self.colnameToCsvIndex["A"]], - row[self.colnameToCsvIndex["B"]], mode, - row[self.colnameToCsvIndex["AB_VOL"]])) - + WranglerLogger.info( + "Link %s %s for mode %s has AB_VOL %s" + % ( + row[self.colnameToCsvIndex["A"]], + row[self.colnameToCsvIndex["B"]], + mode, + row[self.colnameToCsvIndex["AB_VOL"]], + ) + ) + if int(row[self.colnameToCsvIndex["MODE"]]) in self.ignoreModes: oldrownum += 1 continue - + linename = row[self.colnameToCsvIndex["NAME"]].strip() - + # exclude this system? - (system, vehicletype) = self.capacity.getSystemAndVehicleType(linename, self.timeperiod) - if len(self.system)>0 and system not in self.system: continue - + (system, vehicletype) = self.capacity.getSystemAndVehicleType( + linename, self.timeperiod + ) + if len(self.system) > 0 and system not in self.system: + continue + # Initial table fill: Special stuff for the first time through if mode == self.MODES[0]: # ------------ these fields just get used directly for field in self.trnAsgnCopyFields: - + try: # integer fields - if self.trnAsgnFields[field][0] in ['u','b']: - if row[self.colnameToCsvIndex[field]]=="": + if self.trnAsgnFields[field][0] in ["u", "b"]: + if row[self.colnameToCsvIndex[field]] == "": self.trnAsgnTable[newrownum][field] = 0 - elif field in ['TIME','DIST']: + elif field in ["TIME", "DIST"]: # backwards compatibility - dbfs were 100ths of a mile/min - self.trnAsgnTable[newrownum][field] = float(row[self.colnameToCsvIndex[field]])*100.0 + self.trnAsgnTable[newrownum][field] = ( + float(row[self.colnameToCsvIndex[field]]) + * 100.0 + ) else: - self.trnAsgnTable[newrownum][field] = int(row[self.colnameToCsvIndex[field]]) + self.trnAsgnTable[newrownum][field] = int( + row[self.colnameToCsvIndex[field]] + ) # float fields - elif self.trnAsgnFields[field][0] == 'f': - if row[self.colnameToCsvIndex[field]]=="": + elif self.trnAsgnFields[field][0] == "f": + if row[self.colnameToCsvIndex[field]] == "": self.trnAsgnTable[newrownum][field] = 0.0 else: - self.trnAsgnTable[newrownum][field] = float(row[self.colnameToCsvIndex[field]]) + self.trnAsgnTable[newrownum][field] = float( + row[self.colnameToCsvIndex[field]] + ) # text fields else: - self.trnAsgnTable[newrownum][field] = row[self.colnameToCsvIndex[field]] - + self.trnAsgnTable[newrownum][field] = row[ + self.colnameToCsvIndex[field] + ] + except: - WranglerLogger.fatal("Error interpreting field %s: [%s]" % (field, str(self.colnameToCsvIndex[field]))) + WranglerLogger.fatal( + "Error interpreting field %s: [%s]" + % (field, str(self.colnameToCsvIndex[field])) + ) WranglerLogger.fatal("row=%s" % str(row)) WranglerLogger.fatal(sys.exc_info()[0]) WranglerLogger.fatal(sys.exc_info()[1]) WranglerLogger.fatal(traceback.format_exc()) - print("Error interpreting field %s: [%s]" % (field, str(self.colnameToCsvIndex[field]))) - sys.exit(2) + print( + "Error interpreting field %s: [%s]" + % (field, str(self.colnameToCsvIndex[field])) + ) + sys.exit(2) # ------------ these fields come from the dbf because they're missing in the csv (sigh) dbfRow = indbf.__getitem__(oldrownum) - if int(row[self.colnameToCsvIndex["A"]])<100000: - if dbfRow["A"]!=int(row[self.colnameToCsvIndex["A"]]): - raise NetworkException("Assertion error for A on row %d: %s != %s" % (oldrownum, str(dbfRow["A"]), str(row[self.colnameToCsvIndex["A"]]))) - if int(row[self.colnameToCsvIndex["B"]])<100000: - if dbfRow["B"]!=int(row[self.colnameToCsvIndex["B"]]): - raise NetworkException("Assertion error for B on row %d: %s != %s" % (oldrownum, str(dbfRow["B"]), str(row[self.colnameToCsvIndex["B"]]))) + if int(row[self.colnameToCsvIndex["A"]]) < 100000: + if dbfRow["A"] != int(row[self.colnameToCsvIndex["A"]]): + raise NetworkException( + "Assertion error for A on row %d: %s != %s" + % ( + oldrownum, + str(dbfRow["A"]), + str(row[self.colnameToCsvIndex["A"]]), + ) + ) + if int(row[self.colnameToCsvIndex["B"]]) < 100000: + if dbfRow["B"] != int(row[self.colnameToCsvIndex["B"]]): + raise NetworkException( + "Assertion error for B on row %d: %s != %s" + % ( + oldrownum, + str(dbfRow["B"]), + str(row[self.colnameToCsvIndex["B"]]), + ) + ) self.trnAsgnTable[newrownum]["FREQ"] = dbfRow["FREQ"] - self.trnAsgnTable[newrownum]["SEQ"] = dbfRow["SEQ"] + self.trnAsgnTable[newrownum]["SEQ"] = dbfRow["SEQ"] trySeq = dbfRow["SEQ"] # ------------ special one-time computed fields - - # ABNameSeq is more complicated because we want it to be unique - AB = row[self.colnameToCsvIndex["A"]] + " " + row[self.colnameToCsvIndex["B"]] + + # ABNameSeq is more complicated because we want it to be unique + AB = ( + row[self.colnameToCsvIndex["A"]] + + " " + + row[self.colnameToCsvIndex["B"]] + ) self.trnAsgnTable[newrownum]["AB"] = AB - + ABNameSeq = AB + " " + linename - if trySeq>0: + if trySeq > 0: tryABNameSeq = ABNameSeq + " " + str(trySeq) - + # This line seems to be a problem... A/B/NAME/SEQ are not unique if tryABNameSeq in ABNameSeqSet: - WranglerLogger.warn("Non-Unique A/B/Name/Seq: " + tryABNameSeq + "; faking SEQ!") + WranglerLogger.warn( + "Non-Unique A/B/Name/Seq: " + + tryABNameSeq + + "; faking SEQ!" + ) # Find one that works while tryABNameSeq in ABNameSeqSet: trySeq += 1 @@ -400,82 +570,121 @@ def readTransitAssignmentCsvs(self): self.trnAsgnTable[newrownum]["ABNAMESEQ"] = ABNameSeq ABNameSeqSet.add(ABNameSeq) # WranglerLogger.debug("AABNAMESEQ={} type={}".format(ABNameSeq, type(ABNameSeq))) - - ABNameSeq_List.append((int(row[self.colnameToCsvIndex["A"]]), - int(row[self.colnameToCsvIndex["B"]]), - row[self.colnameToCsvIndex["NAME"]], - trySeq)) - + + ABNameSeq_List.append( + ( + int(row[self.colnameToCsvIndex["A"]]), + int(row[self.colnameToCsvIndex["B"]]), + row[self.colnameToCsvIndex["NAME"]], + trySeq, + ) + ) + # ------------ straight lookup FULLNAME, VEHTYPE, VEHCAP; easy calc for PERIODCAP - self.trnAsgnTable[newrownum]["SYSTEM"] = system + self.trnAsgnTable[newrownum]["SYSTEM"] = system self.trnAsgnTable[newrownum]["VEHTYPE"] = vehicletype - self.trnAsgnTable[newrownum]["FULLNAME"] = self.capacity.getFullname(linename, self.timeperiod) - + self.trnAsgnTable[newrownum][ + "FULLNAME" + ] = self.capacity.getFullname(linename, self.timeperiod) + try: - (vtype, vehcap) = self.capacity.getVehicleTypeAndCapacity(linename, self.timeperiod) + (vtype, vehcap) = self.capacity.getVehicleTypeAndCapacity( + linename, self.timeperiod + ) - self.trnAsgnTable[newrownum]["VEHCAP"] = vehcap - self.trnAsgnTable[newrownum]["PERIODCAP"] = TransitLine.HOURS_PER_TIMEPERIOD[self.modelType][self.timeperiod] * 60.0 * vehcap/self.trnAsgnTable[newrownum]["FREQ"] + self.trnAsgnTable[newrownum]["VEHCAP"] = vehcap + self.trnAsgnTable[newrownum]["PERIODCAP"] = ( + TransitLine.HOURS_PER_TIMEPERIOD[self.modelType][ + self.timeperiod + ] + * 60.0 + * vehcap + / self.trnAsgnTable[newrownum]["FREQ"] + ) except: - self.trnAsgnTable[newrownum]["VEHCAP"] = 0 - self.trnAsgnTable[newrownum]["PERIODCAP"] = 0 + self.trnAsgnTable[newrownum]["VEHCAP"] = 0 + self.trnAsgnTable[newrownum]["PERIODCAP"] = 0 # if we still don't have a system, warn - if self.trnAsgnTable[newrownum]["SYSTEM"] == "" and linename not in warnline: + if ( + self.trnAsgnTable[newrownum]["SYSTEM"] == "" + and linename not in warnline + ): WranglerLogger.warning("No default system: " + linename) - warnline[linename] =1 - - #---------add in any grouping that may want to use + warnline[linename] = 1 + + # ---------add in any grouping that may want to use if linename in self.lineToGroup: - self.trnAsgnTable[newrownum]["GROUP"] = self.lineToGroup[linename] + self.trnAsgnTable[newrownum]["GROUP"] = self.lineToGroup[ + linename + ] else: self.trnAsgnTable[newrownum]["GROUP"] = "" - + # initialize additive fields for field in self.trnAsgnAdditiveFields: - if row[self.colnameToCsvIndex[field]]=="": + if row[self.colnameToCsvIndex[field]] == "": self.trnAsgnTable[newrownum][field] = 0.0 else: - self.trnAsgnTable[newrownum][field] = float(row[self.colnameToCsvIndex[field]]) + self.trnAsgnTable[newrownum][field] = float( + row[self.colnameToCsvIndex[field]] + ) # end initial table fill - + # Add in the subsequent assignment files else: - + # print oldrownum, newrownum, ABNameSeq_List[newrownum] # print row[self.colnameToCsvIndex["NAME"]], ABNameSeq_List[oldrownum][2] - if ((int(row[self.colnameToCsvIndex["A"]]) != ABNameSeq_List[newrownum][0]) or - (int(row[self.colnameToCsvIndex["B"]]) != ABNameSeq_List[newrownum][1])): + if ( + int(row[self.colnameToCsvIndex["A"]]) + != ABNameSeq_List[newrownum][0] + ) or ( + int(row[self.colnameToCsvIndex["B"]]) + != ABNameSeq_List[newrownum][1] + ): WranglerLogger.debug(row) WranglerLogger.debug(ABNameSeq_List[newrownum]) - assert(int(row[self.colnameToCsvIndex["A"]]) == ABNameSeq_List[newrownum][0]) - assert(int(row[self.colnameToCsvIndex["B"]]) == ABNameSeq_List[newrownum][1]) + assert ( + int(row[self.colnameToCsvIndex["A"]]) + == ABNameSeq_List[newrownum][0] + ) + assert ( + int(row[self.colnameToCsvIndex["B"]]) + == ABNameSeq_List[newrownum][1] + ) # these don't nec match, can be *32 in ferry skim rather than the bart vehicle name, for example # assert( row[self.colnameToCsvIndex["NAME"]] == ABNameSeq_List[newrownum][2]) - - ABNameSeq = row[self.colnameToCsvIndex["A"]] + " " + \ - row[self.colnameToCsvIndex["B"]] + " " + \ - row[self.colnameToCsvIndex["NAME"]].rstrip() - if ABNameSeq_List[newrownum][3]>0: + + ABNameSeq = ( + row[self.colnameToCsvIndex["A"]] + + " " + + row[self.colnameToCsvIndex["B"]] + + " " + + row[self.colnameToCsvIndex["NAME"]].rstrip() + ) + if ABNameSeq_List[newrownum][3] > 0: ABNameSeq += " " + str(ABNameSeq_List[newrownum][3]) # convert to bytes - the index in the dataTable is a byte string - ABNameSeq = ABNameSeq.encode('utf-8') + ABNameSeq = ABNameSeq.encode("utf-8") for field in self.trnAsgnAdditiveFields: - if row[self.colnameToCsvIndex[field]] !="": - self.trnAsgnTable[ABNameSeq][field] += float(row[self.colnameToCsvIndex[field]]) - - newrownum += 1 + if row[self.colnameToCsvIndex[field]] != "": + self.trnAsgnTable[ABNameSeq][field] += float( + row[self.colnameToCsvIndex[field]] + ) + + newrownum += 1 oldrownum += 1 # we're done with this; free it up del filereader - if indbf: + if indbf: del indbf - + # Table is created and filled -- set the index - if mode == self.MODES[0]: + if mode == self.MODES[0]: try: self.trnAsgnTable.setIndex(fieldName="ABNAMESEQ") except: @@ -484,94 +693,110 @@ def readTransitAssignmentCsvs(self): for row in self.trnAsgnTable: ABNameSeqList.append(row["ABNAMESEQ"]) ABNameSeqList.sort() - for idx in range(len(ABNameSeqList)-1): - if ABNameSeqList[idx]==ABNameSeqList[idx+1]: - WranglerLogger.fatal("Duplicate ABNAMESEQ at idx %d : [%s]" % (idx,ABNameSeqList[idx])) + for idx in range(len(ABNameSeqList) - 1): + if ABNameSeqList[idx] == ABNameSeqList[idx + 1]: + WranglerLogger.fatal( + "Duplicate ABNAMESEQ at idx %d : [%s]" + % (idx, ABNameSeqList[idx]) + ) exit(1) # ok the table is all filled in -- fill in the LOAD for row in self.trnAsgnTable: - if row["VEHCAP"] == 0: continue + if row["VEHCAP"] == 0: + continue tpfactor = self.TIMEPERIOD_FACTOR[self.timeperiod] - + # mode-specific peaking factor will over-ride if row["MODE"] in self.TIMEPERIOD_FACTOR: tpfactor = self.TIMEPERIOD_FACTOR[row["MODE"]][self.timeperiod] - - row["LOAD"] = row["AB_VOL"] * tpfactor * row["FREQ"] / (60.0 * row["VEHCAP"]) + + row["LOAD"] = ( + row["AB_VOL"] * tpfactor * row["FREQ"] / (60.0 * row["VEHCAP"]) + ) # build the aggregate table for key="A B" if self.aggregateAll: self.buildAggregateTable() - + def buildAggregateTable(self): # first find how big it is - ABSet = set() + ABSet = set() for row in self.trnAsgnTable: ABSet.add(row["AB"]) - - self.aggregateTable = DataTable(numRecords=len(ABSet), - fieldNames=list(self.aggregateFields.keys()), - numpyFieldTypes=list(self.aggregateFields.values())) + + self.aggregateTable = DataTable( + numRecords=len(ABSet), + fieldNames=list(self.aggregateFields.keys()), + numpyFieldTypes=list(self.aggregateFields.values()), + ) ABtoRowIndex = {} rowsUsed = 0 for row in self.trnAsgnTable: if row["AB"] not in ABtoRowIndex: rowIndex = rowsUsed - self.aggregateTable[rowIndex]["AB"] = row["AB"] - self.aggregateTable[rowIndex]["A"] = row["A"] - self.aggregateTable[rowIndex]["B"] = row["B"] - self.aggregateTable[rowIndex]["DIST"] = row["DIST"] - self.aggregateTable[rowIndex]["FREQ"] = 0.0 - self.aggregateTable[rowIndex]["PERIODCAP"] = 0.0 - self.aggregateTable[rowIndex]["LOAD"] = 0.0 - self.aggregateTable[rowIndex]["MAXLOAD"] = 0.0 - for field in self.trnAsgnAdditiveFields: # sum + self.aggregateTable[rowIndex]["AB"] = row["AB"] + self.aggregateTable[rowIndex]["A"] = row["A"] + self.aggregateTable[rowIndex]["B"] = row["B"] + self.aggregateTable[rowIndex]["DIST"] = row["DIST"] + self.aggregateTable[rowIndex]["FREQ"] = 0.0 + self.aggregateTable[rowIndex]["PERIODCAP"] = 0.0 + self.aggregateTable[rowIndex]["LOAD"] = 0.0 + self.aggregateTable[rowIndex]["MAXLOAD"] = 0.0 + for field in self.trnAsgnAdditiveFields: # sum self.aggregateTable[rowIndex][field] = 0.0 ABtoRowIndex[row["AB"]] = rowsUsed rowsUsed += 1 else: rowIndex = ABtoRowIndex[row["AB"]] - - for field in self.trnAsgnAdditiveFields: # sum + + for field in self.trnAsgnAdditiveFields: # sum self.aggregateTable[rowIndex][field] += row[field] self.aggregateTable[rowIndex]["AB_VOL"] += row["AB_VOL"] self.aggregateTable[rowIndex]["BA_VOL"] += row["BA_VOL"] self.aggregateTable[rowIndex]["PERIODCAP"] += row["PERIODCAP"] - self.aggregateTable[rowIndex]["MAXLOAD"] = max(row["LOAD"], self.aggregateTable[rowIndex]["MAXLOAD"]) - self.aggregateTable[rowIndex]["FREQ"] += 1/row["FREQ"] # combining -- will take reciprocal later - + self.aggregateTable[rowIndex]["MAXLOAD"] = max( + row["LOAD"], self.aggregateTable[rowIndex]["MAXLOAD"] + ) + self.aggregateTable[rowIndex]["FREQ"] += ( + 1 / row["FREQ"] + ) # combining -- will take reciprocal later + self.aggregateTable.setIndex(fieldName="AB") - count=0 + count = 0 for row in self.aggregateTable: count += 1 - if row["FREQ"]>0: - row["FREQ"] = 1/row["FREQ"] - if row["PERIODCAP"]>0: + if row["FREQ"] > 0: + row["FREQ"] = 1 / row["FREQ"] + if row["PERIODCAP"] > 0: row["LOAD"] = float(row["AB_VOL"]) / row["PERIODCAP"] # print row["LOAD"] - WranglerLogger.debug("count "+str(count)+" lines in aggregate table") + WranglerLogger.debug("count " + str(count) + " lines in aggregate table") def calculateFleetCharacteristics(self): - """ Calculates the fleet characteristics - vehicle hours and vehicle miles - by vehicle type - """ + """Calculates the fleet characteristics - vehicle hours and vehicle miles - by vehicle type""" self.vehicleHours = defaultdict(float) self.vehicleMiles = defaultdict(float) for key in self.trnAsgnTable._index.keys(): record = self.trnAsgnTable[key] # don't process access, egress and transfer links - if record["MODE"]>9: continue - + if record["MODE"] > 9: + continue + # index by system, then by vehicle type indexstr = record["SYSTEM"] + "," + record["VEHTYPE"] - + # number of vehicles = duration * 60 min/hour / freq - numveh = TransitLine.HOURS_PER_TIMEPERIOD[self.modelType][self.timeperiod] * 60.0 / record["FREQ"] + numveh = ( + TransitLine.HOURS_PER_TIMEPERIOD[self.modelType][self.timeperiod] + * 60.0 + / record["FREQ"] + ) # vehicle hours = (# of vehicles) x time per link, or TIME * 1 hour/6000 hundredths of min - self.vehicleHours[indexstr] += numveh*(record["TIME"]/6000.0) + self.vehicleHours[indexstr] += numveh * (record["TIME"] / 6000.0) # vehicle miles = (# of vehicles) x dist per link, or DIST * 1 mile/100 hundredths of mile - self.vehicleMiles[indexstr] += numveh*(record["DIST"]/100.0) + self.vehicleMiles[indexstr] += numveh * (record["DIST"] / 100.0) def readAggregateDbfs(self, asgnFileName, aggregateFileName=None): """ @@ -590,18 +815,19 @@ def readAggregateDbfs(self, asgnFileName, aggregateFileName=None): # rstrip spaces off the end of the text fields for row in self.trnAsgnTable: for headerTuple in headerTuples: - if headerTuple[1] == 'C': row[headerTuple[0]] = string.rstrip(row[headerTuple[0]]) + if headerTuple[1] == "C": + row[headerTuple[0]] = string.rstrip(row[headerTuple[0]]) # this is the index! self.trnAsgnTable.setIndex(fieldName="ABNAMESEQ") # the link-level aggregate table if not aggregateFileName: - self.aggregateAll = False + self.aggregateAll = False self.aggregateTable = False return - self.aggregateAll = True + self.aggregateAll = True self.aggregateTable = dbfTableReader(aggregateFileName) # cleanup again @@ -612,160 +838,168 @@ def readAggregateDbfs(self, asgnFileName, aggregateFileName=None): # rstrip spaces off the end of the text fields for row in self.aggregateTable: for headerTuple in headerTuples: - if headerTuple[1] == 'C': row[headerTuple[0]] = string.rstrip(row[headerTuple[0]]) + if headerTuple[1] == "C": + row[headerTuple[0]] = string.rstrip(row[headerTuple[0]]) def writePnrDrivers(self, pnrFileName): """ Writes PNR Auto Trips to DBF with fields: - ZONE + ZONE PNR TO-DEMAND FR-DEMAND """ - self.pnrFields = {"ZONE": 'u4', - "PNR" : 'u4', - "TO" : 'f4', - "FROM": 'f4' - } - self.pnrTable = DataTable(fieldNames=self.pnrFields.keys(), - numpyFieldTypes=self.trnAsgnFields.values()) - - + self.pnrFields = {"ZONE": "u4", "PNR": "u4", "TO": "f4", "FROM": "f4"} + self.pnrTable = DataTable( + fieldNames=self.pnrFields.keys(), + numpyFieldTypes=self.trnAsgnFields.values(), + ) + def writeDbfs(self, asgnFileName, aggregateFileName=None): """ Writes the line-level (key=A,B,NAME,SEQ) dbf to *asgnFileName*, and write the link-level (key=A,B) aggregated dbf to *aggregateFileName*. """ - addtype = "F" - addlen = 9 - addnumdec = 2 + addtype = "F" + addlen = 9 + addnumdec = 2 # line-level - self.trnAsgnTable.header = \ - (FieldType("A", "N", 7, 0), - FieldType("B", "N", 7, 0), - FieldType("TIME", "N", 5, 0), - FieldType("MODE", "N", 3, 0), - FieldType("FREQ", "F", 6, 2), - FieldType("PLOT", "N", 1, 0), - FieldType("COLOR", "N", 2, 0), - FieldType("STOP_A", "N", 1, 0), - FieldType("STOP_B", "N", 1, 0), - FieldType("DIST", "N", 4, 0), - FieldType("NAME", "C", 13,0), - FieldType("SEQ", "N", 3, 0), - FieldType("OWNER", "C", 10,0), - FieldType("AB", "C", 15,0), - FieldType("ABNAMESEQ", "C", 30,0), - FieldType("FULLNAME", "C", 40,0), - FieldType("SYSTEM", "C", 25,0), - FieldType("GROUP", "C", 20,0), - FieldType("VEHTYPE", "C", 40,0), - FieldType("VEHCAP", "F", 8, 2), - FieldType("PERIODCAP", "F", 15, 2), - FieldType("LOAD", "F", 7, 3), - FieldType("AB_VOL", addtype, addlen, addnumdec), - FieldType("AB_BRDA", addtype, addlen, addnumdec), - FieldType("AB_XITA", addtype, addlen, addnumdec), - FieldType("AB_BRDB", addtype, addlen, addnumdec), - FieldType("AB_XITB", addtype, addlen, addnumdec), - FieldType("BA_VOL", addtype, addlen, addnumdec), - FieldType("BA_BRDA", addtype, addlen, addnumdec), - FieldType("BA_XITA", addtype, addlen, addnumdec), - FieldType("BA_BRDB", addtype, addlen, addnumdec), - FieldType("BA_XITB", addtype, addlen, addnumdec) - ) + self.trnAsgnTable.header = ( + FieldType("A", "N", 7, 0), + FieldType("B", "N", 7, 0), + FieldType("TIME", "N", 5, 0), + FieldType("MODE", "N", 3, 0), + FieldType("FREQ", "F", 6, 2), + FieldType("PLOT", "N", 1, 0), + FieldType("COLOR", "N", 2, 0), + FieldType("STOP_A", "N", 1, 0), + FieldType("STOP_B", "N", 1, 0), + FieldType("DIST", "N", 4, 0), + FieldType("NAME", "C", 13, 0), + FieldType("SEQ", "N", 3, 0), + FieldType("OWNER", "C", 10, 0), + FieldType("AB", "C", 15, 0), + FieldType("ABNAMESEQ", "C", 30, 0), + FieldType("FULLNAME", "C", 40, 0), + FieldType("SYSTEM", "C", 25, 0), + FieldType("GROUP", "C", 20, 0), + FieldType("VEHTYPE", "C", 40, 0), + FieldType("VEHCAP", "F", 8, 2), + FieldType("PERIODCAP", "F", 15, 2), + FieldType("LOAD", "F", 7, 3), + FieldType("AB_VOL", addtype, addlen, addnumdec), + FieldType("AB_BRDA", addtype, addlen, addnumdec), + FieldType("AB_XITA", addtype, addlen, addnumdec), + FieldType("AB_BRDB", addtype, addlen, addnumdec), + FieldType("AB_XITB", addtype, addlen, addnumdec), + FieldType("BA_VOL", addtype, addlen, addnumdec), + FieldType("BA_BRDA", addtype, addlen, addnumdec), + FieldType("BA_XITA", addtype, addlen, addnumdec), + FieldType("BA_BRDB", addtype, addlen, addnumdec), + FieldType("BA_XITB", addtype, addlen, addnumdec), + ) self.trnAsgnTable.writeAsDbf(asgnFileName) - if aggregateFileName==None: return + if aggregateFileName == None: + return if not self.aggregateTable: self.buildAggregateTable() - self.aggregateTable.header = \ - (FieldType("A", "N", 7, 0), - FieldType("B", "N", 7, 0), - FieldType("AB", "C", 15,0), - FieldType("FREQ", "F", 6, 2), - FieldType("DIST", "N", 4, 0), - FieldType("VEHCAP", "F", 8, 2), - FieldType("PERIODCAP", "F", 15, 2), - FieldType("LOAD", "F", 7, 3), - FieldType("MAXLOAD", "F", 7, 3), - FieldType("AB_VOL", addtype, addlen, addnumdec), - FieldType("AB_BRDA", addtype, addlen, addnumdec), - FieldType("AB_XITA", addtype, addlen, addnumdec), - FieldType("AB_BRDB", addtype, addlen, addnumdec), - FieldType("AB_XITB", addtype, addlen, addnumdec), - FieldType("BA_VOL", addtype, addlen, addnumdec), - FieldType("BA_BRDA", addtype, addlen, addnumdec), - FieldType("BA_XITA", addtype, addlen, addnumdec), - FieldType("BA_BRDB", addtype, addlen, addnumdec), - FieldType("BA_XITB", addtype, addlen, addnumdec) - ) + self.aggregateTable.header = ( + FieldType("A", "N", 7, 0), + FieldType("B", "N", 7, 0), + FieldType("AB", "C", 15, 0), + FieldType("FREQ", "F", 6, 2), + FieldType("DIST", "N", 4, 0), + FieldType("VEHCAP", "F", 8, 2), + FieldType("PERIODCAP", "F", 15, 2), + FieldType("LOAD", "F", 7, 3), + FieldType("MAXLOAD", "F", 7, 3), + FieldType("AB_VOL", addtype, addlen, addnumdec), + FieldType("AB_BRDA", addtype, addlen, addnumdec), + FieldType("AB_XITA", addtype, addlen, addnumdec), + FieldType("AB_BRDB", addtype, addlen, addnumdec), + FieldType("AB_XITB", addtype, addlen, addnumdec), + FieldType("BA_VOL", addtype, addlen, addnumdec), + FieldType("BA_BRDA", addtype, addlen, addnumdec), + FieldType("BA_XITA", addtype, addlen, addnumdec), + FieldType("BA_BRDB", addtype, addlen, addnumdec), + FieldType("BA_XITB", addtype, addlen, addnumdec), + ) self.aggregateTable.writeAsDbf(aggregateFileName) WranglerLogger.info("Wrote aggregate table as {}".format(aggregateFileName)) - + def numBoards(self, linename, nodenum, nodenum_next, seq): - """ linename is something like MUN30I; it includes the direction. - nodenum is the node in question, nodenum_next is the next node in the line file - seq is for the sequence of (nodenum,nodenum_next). e.g. seq starts at 1 for the first link and increments - TODO: what if the line is two-way? - Returns an int representing number of boards in the whole time period. - Throws an exception if linename isnt recognized or if nodenum is not part of the line. + """linename is something like MUN30I; it includes the direction. + nodenum is the node in question, nodenum_next is the next node in the line file + seq is for the sequence of (nodenum,nodenum_next). e.g. seq starts at 1 for the first link and increments + TODO: what if the line is two-way? + Returns an int representing number of boards in the whole time period. + Throws an exception if linename isnt recognized or if nodenum is not part of the line. """ key = "%d %d %s %d" % (nodenum, nodenum_next, linename.upper(), seq) if key in self.trnAsgnTable: return self.trnAsgnTable[key]["AB_BRDA"] - raise TransitAssignmentDataException("key [%s] not found in transit assignment data" % key) - + raise TransitAssignmentDataException( + "key [%s] not found in transit assignment data" % key + ) + def numExits(self, linename, nodenum_prev, nodenum, seq): - """ See numBoards - """ + """See numBoards""" key = "%d %d %s %d" % (nodenum_prev, nodenum, linename.upper(), seq) if key in self.trnAsgnTable: return self.trnAsgnTable[key]["AB_XITB"] - raise TransitAssignmentDataException("key [%s] not found in transit assignment data" % key) - - def loadFactor(self, linename, a,b, seq): - """ Returns a fraction: peak hour pax per vehicle / vehicle capacity - e.g. 1.0 is a packed vehicle - - NOTE this assumes a distribution pax over the time period. For now, we'll use - the simple peak hour factors that quickboards uses but this could be refined - in the future. + raise TransitAssignmentDataException( + "key [%s] not found in transit assignment data" % key + ) + + def loadFactor(self, linename, a, b, seq): + """Returns a fraction: peak hour pax per vehicle / vehicle capacity + e.g. 1.0 is a packed vehicle + + NOTE this assumes a distribution pax over the time period. For now, we'll use + the simple peak hour factors that quickboards uses but this could be refined + in the future. """ key = "%d %d %s %d" % (a, b, linename.upper(), seq) if key not in self.trnAsgnTable: - raise TransitAssignmentDataException("Key [%s] not found in transit assignment data" % key) + raise TransitAssignmentDataException( + "Key [%s] not found in transit assignment data" % key + ) return self.trnAsgnTable[key]["LOAD"] - - def linkVolume(self,linename,a,b,seq): + + def linkVolume(self, linename, a, b, seq): """Return number of people on a given link a b""" key = "%d %d %s %d" % (a, b, linename.upper(), seq) if key not in self.trnAsgnTable: - raise TransitAssignmentDataException("Key [%s] not found in transit assignment data" % key) + raise TransitAssignmentDataException( + "Key [%s] not found in transit assignment data" % key + ) return self.trnAsgnTable[key]["AB_VOL"] - - def linkTime(self,linename,a,b,seq): + + def linkTime(self, linename, a, b, seq): """Return time in minutes on a given link a b""" key = "%d %d %s %d" % (a, b, linename.upper(), seq) if key not in self.trnAsgnTable: - raise TransitAssignmentDataException("Key [%s] not found in transit assignment data" % key) + raise TransitAssignmentDataException( + "Key [%s] not found in transit assignment data" % key + ) return self.trnAsgnTable[key]["TIME"] - - def linkDistance(self,linename,a,b,seq): + def linkDistance(self, linename, a, b, seq): """Return distance in miles on a given link a b""" key = "%d %d %s %d" % (a, b, linename.upper(), seq) if key not in self.trnAsgnTable: - raise TransitAssignmentDataException("Key [%s] not found in transit assignment data" % key) + raise TransitAssignmentDataException( + "Key [%s] not found in transit assignment data" % key + ) return self.trnAsgnTable[key]["DIST"] - + # Not complete.... TODO if it makes sense.... class DailyTransitAssignmentData: - def __init__(self, tadAM, tadMD, tadPM, tadEV, tadEA): """ For aggregating into a single version! @@ -781,39 +1015,65 @@ def __init__(self, tadAM, tadMD, tadPM, tadEV, tadEA): # numpyFieldTypes=self.trnAsgnFields.values()) -if __name__ == '__main__': - logging.basicConfig(level=logging.DEBUG, - format="%(asctime)s - %(levelname)s - %(message)s", - datefmt='%Y-%b-%d %H:%M:%S',) +if __name__ == "__main__": + logging.basicConfig( + level=logging.DEBUG, + format="%(asctime)s - %(levelname)s - %(message)s", + datefmt="%Y-%b-%d %H:%M:%S", + ) if False: - tad1 = TransitAssignmentData(directory=r"X:\Projects\GHGReductionCE\2035", - timeperiod="AM", - tpfactor="constant") - print("Test1: vol for MUNKO, 13401-13402, 1) is ", tad1.linkVolume("MUNKO", 13401, 13402, 1)) - tad1.writeDbfs(asgnFileName=r"X:\lmz\AM_asgn.dbf", aggregateFileName=r"X:\lmz\AM_agg.dbf") + tad1 = TransitAssignmentData( + directory=r"X:\Projects\GHGReductionCE\2035", + timeperiod="AM", + tpfactor="constant", + ) + print( + "Test1: vol for MUNKO, 13401-13402, 1) is ", + tad1.linkVolume("MUNKO", 13401, 13402, 1), + ) + tad1.writeDbfs( + asgnFileName=r"X:\lmz\AM_asgn.dbf", aggregateFileName=r"X:\lmz\AM_agg.dbf" + ) tad1 = False if False: - tad2 = TransitAssignmentData(directory=r"X:\Projects\GHGReductionCE\2035", - timeperiod="AM", - tpfactor="constant") - print("Test2: vol for MUNKO, 13401-13402, 1) is ", tad2.linkVolume("MUNKO", 13401, 13402, 1)) - tad2.writeDbfs(asgnFileName=r"X:\lmz\AM_asgnF.dbf", aggregateFileName=r"X:\lmz\AM_aggF.dbf") + tad2 = TransitAssignmentData( + directory=r"X:\Projects\GHGReductionCE\2035", + timeperiod="AM", + tpfactor="constant", + ) + print( + "Test2: vol for MUNKO, 13401-13402, 1) is ", + tad2.linkVolume("MUNKO", 13401, 13402, 1), + ) + tad2.writeDbfs( + asgnFileName=r"X:\lmz\AM_asgnF.dbf", aggregateFileName=r"X:\lmz\AM_aggF.dbf" + ) tad2 = False if True: - tad3 = TransitAssignmentData(timeperiod="AM", - tpfactor="constant", - lineLevelAggregateFilename=r"X:\lmz\AM_asgn.dbf", - linkLevelAggregateFilename=r"X:\lmz\AM_agg.dbf") - print("Test3: vol for MUNKO, 13401-13402, 1) is ", tad3.linkVolume("MUNKO", 13401, 13402, 1)) + tad3 = TransitAssignmentData( + timeperiod="AM", + tpfactor="constant", + lineLevelAggregateFilename=r"X:\lmz\AM_asgn.dbf", + linkLevelAggregateFilename=r"X:\lmz\AM_agg.dbf", + ) + print( + "Test3: vol for MUNKO, 13401-13402, 1) is ", + tad3.linkVolume("MUNKO", 13401, 13402, 1), + ) tad3 = False if True: - tad4 = TransitAssignmentData(timeperiod="AM", - tpfactor="constant", - lineLevelAggregateFilename=r"X:\lmz\AM_asgnF.dbf", - linkLevelAggregateFilename=r"X:\lmz\AM_aggF.dbf") - print("Test4: vol for MUNKO, 13401-13402, 1) is ", tad4.linkVolume("MUNKO", 13401, 13402, 1)) + tad4 = TransitAssignmentData( + timeperiod="AM", + tpfactor="constant", + lineLevelAggregateFilename=r"X:\lmz\AM_asgnF.dbf", + linkLevelAggregateFilename=r"X:\lmz\AM_aggF.dbf", + ) + print( + "Test4: vol for MUNKO, 13401-13402, 1) is ", + tad4.linkVolume("MUNKO", 13401, 13402, 1), + ) tad4 = False diff --git a/Wrangler/TransitCapacity.py b/Wrangler/TransitCapacity.py index b69421d..865a1d4 100644 --- a/Wrangler/TransitCapacity.py +++ b/Wrangler/TransitCapacity.py @@ -1,8 +1,8 @@ -import copy,csv,os,re,string +import copy, csv, os, re, string from .Logger import WranglerLogger from .NetworkException import NetworkException -__all__ = ['TransitCapacity'] +__all__ = ["TransitCapacity"] class TransitCapacity: @@ -10,93 +10,133 @@ class TransitCapacity: Simple class for accessing and mutating (and reading and writing) Transit Capacity information """ - - TIMEPERIOD_TO_VEHTYPIDX = { "AM":2, "MD": 4, "PM":3, "EV":4, "EA":4 } + + TIMEPERIOD_TO_VEHTYPIDX = {"AM": 2, "MD": 4, "PM": 3, "EV": 4, "EA": 4} # for self.linenameToAttributes # linename -> [ system, full name, AM vehicletype, PM vehiceltype, OP vehicle type ] - ATTR_SYSTEM = 0 - ATTR_FULLNAME = 1 - ATTR_AMVEHTYPE = 2 - ATTR_PMVEHTYPE = 3 - ATTR_OPVEHTYPE = 4 + ATTR_SYSTEM = 0 + ATTR_FULLNAME = 1 + ATTR_AMVEHTYPE = 2 + ATTR_PMVEHTYPE = 3 + ATTR_OPVEHTYPE = 4 # for self.vehicleTypeToDelays - DELAY_SIMPLE = 0 - DELAY_CONST = 1 - DELAY_PERBOARD = 2 + DELAY_SIMPLE = 0 + DELAY_CONST = 1 + DELAY_PERBOARD = 2 DELAY_PERALIGHT = 3 - def __init__(self, directory=".", - transitLineToVehicle="transitLineToVehicle.csv", - transitVehicleToCapacity="transitVehicleToCapacity.csv", - transitPrefixToVehicle="transitPrefixToVehicle.csv"): + def __init__( + self, + directory=".", + transitLineToVehicle="transitLineToVehicle.csv", + transitVehicleToCapacity="transitVehicleToCapacity.csv", + transitPrefixToVehicle="transitPrefixToVehicle.csv", + ): """ - Uses *transitLineToVehicle* and *transitVehicleToCapacity* to map transit lines to vehicle types, + Uses *transitLineToVehicle* and *transitVehicleToCapacity* to map transit lines to vehicle types, and vehicle types to capacities. """ - self.vehicleTypeToCapacity = {} - self.vehicleTypeToDelays = {} - self.linenameToAttributes = {} - self.linenameToSimple = {} - self.prefixToVehicleType = {} + self.vehicleTypeToCapacity = {} + self.vehicleTypeToDelays = {} + self.linenameToAttributes = {} + self.linenameToSimple = {} + self.prefixToVehicleType = {} self.readTransitLineToVehicle(directory, filename=transitLineToVehicle) self.readTransitVehicleToCapacity(directory, filename=transitVehicleToCapacity) self.readTransitPrefixToVehicle(directory, filename=transitPrefixToVehicle) - def readTransitVehicleToCapacity(self, directory=".", filename="transitVehicleToCapacity.csv"): + def readTransitVehicleToCapacity( + self, directory=".", filename="transitVehicleToCapacity.csv" + ): """ Populate a self.vehicleTypeToCapacity from *filename*: vehicletype -> 100% capacity (a float) e.g. "LRV2" -> 238.0 - + Also populate a self.vehicleTypeToDelays: - vehicletype -> [ simple delay, + vehicletype -> [ simple delay, complex delay const, complex delay per board, complex delay per alight ] """ - f = open(os.path.join(directory,filename), 'r') + f = open(os.path.join(directory, filename), "r") lines = f.readlines() f.close() - + for line in lines: tokens = line.split(",") - if tokens[0]=="VehicleType": continue # header + if tokens[0] == "VehicleType": + continue # header vtype = tokens[0] self.vehicleTypeToCapacity[vtype] = float(tokens[1]) - + if len(tokens) > 4: self.vehicleTypeToDelays[vtype] = [0, 0, 0, 0] - self.vehicleTypeToDelays[vtype][TransitCapacity.DELAY_SIMPLE ] = float(tokens[4]) - self.vehicleTypeToDelays[vtype][TransitCapacity.DELAY_CONST ] = float(tokens[5]) - self.vehicleTypeToDelays[vtype][TransitCapacity.DELAY_PERBOARD ] = float(tokens[6]) - self.vehicleTypeToDelays[vtype][TransitCapacity.DELAY_PERALIGHT] = float(tokens[7]) - + self.vehicleTypeToDelays[vtype][TransitCapacity.DELAY_SIMPLE] = float( + tokens[4] + ) + self.vehicleTypeToDelays[vtype][TransitCapacity.DELAY_CONST] = float( + tokens[5] + ) + self.vehicleTypeToDelays[vtype][TransitCapacity.DELAY_PERBOARD] = float( + tokens[6] + ) + self.vehicleTypeToDelays[vtype][ + TransitCapacity.DELAY_PERALIGHT + ] = float(tokens[7]) + # print "vehicleTypeToCapacity = " + str(self.vehicleTypeToCapacity) # print "vehicleTypeToDelays = " + str(self.vehicleTypeToDelays) - def writeTransitVehicleToCapacity(self, directory=".", filename="transitVehicleToCapacity.csv"): + def writeTransitVehicleToCapacity( + self, directory=".", filename="transitVehicleToCapacity.csv" + ): """ Writes it out in the same format """ - f = open(os.path.join(directory,filename), 'w') - f.write("VehicleType,100%Capacity,85%Capacity,VehicleCategory,SimpleDelayPerStop,ConstDelayPerStop,DelayPerBoard,DelayPerAlight\n") + f = open(os.path.join(directory, filename), "w") + f.write( + "VehicleType,100%Capacity,85%Capacity,VehicleCategory,SimpleDelayPerStop,ConstDelayPerStop,DelayPerBoard,DelayPerAlight\n" + ) for vehicleType in sorted(self.vehicleTypeToCapacity.keys()): - f.write(vehicleType+",") - f.write("%d,%d" % (self.vehicleTypeToCapacity[vehicleType], - 0.85*self.vehicleTypeToCapacity[vehicleType])) + f.write(vehicleType + ",") + f.write( + "%d,%d" + % ( + self.vehicleTypeToCapacity[vehicleType], + 0.85 * self.vehicleTypeToCapacity[vehicleType], + ) + ) if vehicleType in self.vehicleTypeToDelays: - f.write(",%s" % vehicleType) # this is supposed to be vehicle category but we didn't keep that around since we don't use it - f.write(",%.3f,%.3f,%.3f,%.3f" % (self.vehicleTypeToDelays[vehicleType][TransitCapacity.DELAY_SIMPLE ], - self.vehicleTypeToDelays[vehicleType][TransitCapacity.DELAY_CONST ], - self.vehicleTypeToDelays[vehicleType][TransitCapacity.DELAY_PERBOARD ], - self.vehicleTypeToDelays[vehicleType][TransitCapacity.DELAY_PERALIGHT])) + f.write( + ",%s" % vehicleType + ) # this is supposed to be vehicle category but we didn't keep that around since we don't use it + f.write( + ",%.3f,%.3f,%.3f,%.3f" + % ( + self.vehicleTypeToDelays[vehicleType][ + TransitCapacity.DELAY_SIMPLE + ], + self.vehicleTypeToDelays[vehicleType][ + TransitCapacity.DELAY_CONST + ], + self.vehicleTypeToDelays[vehicleType][ + TransitCapacity.DELAY_PERBOARD + ], + self.vehicleTypeToDelays[vehicleType][ + TransitCapacity.DELAY_PERALIGHT + ], + ) + ) f.write("\n") f.close() - - def readTransitLineToVehicle(self, directory=".", filename="transitLineToVehicle.csv"): + + def readTransitLineToVehicle( + self, directory=".", filename="transitLineToVehicle.csv" + ): """ Populate self.linenameToAttributes from *filename*: linename -> [ system, full name, AM vehicletype, PM vehicletype, OP vehicle type ] @@ -105,75 +145,122 @@ def readTransitLineToVehicle(self, directory=".", filename="transitLineToVehicle linename -> [ stripped, simplename ] e.g. "MUN91I" -> [ "91I", "91" ] """ - l2vReader = csv.reader(open(os.path.join(directory,filename))) - for name,system,stripped,simplename,fullLineName,vehicleTypeAM,vehicleTypePM,vehicleTypeOP in l2vReader: - self.linenameToAttributes[name.upper()] = [system, fullLineName, vehicleTypeAM,vehicleTypePM,vehicleTypeOP] + l2vReader = csv.reader(open(os.path.join(directory, filename))) + for ( + name, + system, + stripped, + simplename, + fullLineName, + vehicleTypeAM, + vehicleTypePM, + vehicleTypeOP, + ) in l2vReader: + self.linenameToAttributes[name.upper()] = [ + system, + fullLineName, + vehicleTypeAM, + vehicleTypePM, + vehicleTypeOP, + ] self.linenameToSimple[name.upper()] = [stripped, simplename] # print "linenameToAttributes = " + str(self.linenameToAttributes) - def writeTransitLineToVehicle(self, directory=".", filename="transitLineToVehicle.csv"): + def writeTransitLineToVehicle( + self, directory=".", filename="transitLineToVehicle.csv" + ): """ Writes it out in the same format """ - f = open(os.path.join(directory,filename), 'w') - f.write("Name,System,Stripped,Line,FullLineName,AM VehicleType,PM VehicleType,OP Vehicle Type\n") + f = open(os.path.join(directory, filename), "w") + f.write( + "Name,System,Stripped,Line,FullLineName,AM VehicleType,PM VehicleType,OP Vehicle Type\n" + ) for linename in sorted(self.linenameToAttributes.keys()): f.write(linename + ",") - f.write(self.linenameToAttributes[linename][TransitCapacity.ATTR_SYSTEM] + ",") - f.write(self.linenameToSimple[linename][0] + ",") # stripped - f.write(self.linenameToSimple[linename][1] + ",") # simplename - f.write(self.linenameToAttributes[linename][TransitCapacity.ATTR_FULLNAME]+",") - f.write(self.linenameToAttributes[linename][TransitCapacity.ATTR_AMVEHTYPE]+",") - f.write(self.linenameToAttributes[linename][TransitCapacity.ATTR_PMVEHTYPE]+",") - f.write(self.linenameToAttributes[linename][TransitCapacity.ATTR_OPVEHTYPE]+"\n") + f.write( + self.linenameToAttributes[linename][TransitCapacity.ATTR_SYSTEM] + "," + ) + f.write(self.linenameToSimple[linename][0] + ",") # stripped + f.write(self.linenameToSimple[linename][1] + ",") # simplename + f.write( + self.linenameToAttributes[linename][TransitCapacity.ATTR_FULLNAME] + "," + ) + f.write( + self.linenameToAttributes[linename][TransitCapacity.ATTR_AMVEHTYPE] + + "," + ) + f.write( + self.linenameToAttributes[linename][TransitCapacity.ATTR_PMVEHTYPE] + + "," + ) + f.write( + self.linenameToAttributes[linename][TransitCapacity.ATTR_OPVEHTYPE] + + "\n" + ) f.close() - def readTransitPrefixToVehicle(self, directory=".", filename="transitPrefixToVehicle.csv"): + def readTransitPrefixToVehicle( + self, directory=".", filename="transitPrefixToVehicle.csv" + ): """ Populate self.prefixToVehicleType from *filename*: prefix -> [ system, vehicletype ] """ - p2vReader = csv.reader(open(os.path.join(directory,filename))) + p2vReader = csv.reader(open(os.path.join(directory, filename))) for prefix, system, vehicleType in p2vReader: self.prefixToVehicleType[prefix] = [system, vehicleType] - def writeTransitPrefixToVehicle(self, directory=".", filename="transitPrefixToVehicle.csv"): + def writeTransitPrefixToVehicle( + self, directory=".", filename="transitPrefixToVehicle.csv" + ): """ Writes it out in the same format """ - f = open(os.path.join(directory,filename), 'w') + f = open(os.path.join(directory, filename), "w") f.write("Prefix,System,VehicleType\n") for prefix in sorted(self.prefixToVehicleType.keys()): f.write(prefix + ",") - f.write(self.prefixToVehicleType[prefix][0] + ",") # system - f.write(self.prefixToVehicleType[prefix][1] + "\n") # vehicleType + f.write(self.prefixToVehicleType[prefix][0] + ",") # system + f.write(self.prefixToVehicleType[prefix][1] + "\n") # vehicleType f.close() - + def getSystemAndVehicleType(self, linename, timeperiod): """ Convenience function. Returns tuple: best guess of (system, vehicletype) """ linenameU = linename.upper() if linenameU in self.linenameToAttributes: - return (self.linenameToAttributes[linenameU][TransitCapacity.ATTR_SYSTEM], - self.linenameToAttributes[linenameU][TransitCapacity.TIMEPERIOD_TO_VEHTYPIDX[timeperiod]]) + return ( + self.linenameToAttributes[linenameU][TransitCapacity.ATTR_SYSTEM], + self.linenameToAttributes[linenameU][ + TransitCapacity.TIMEPERIOD_TO_VEHTYPIDX[timeperiod] + ], + ) if linename[:4] in self.prefixToVehicleType: - return ( self.prefixToVehicleType[linenameU[:4]][0], self.prefixToVehicleType[linenameU[:4]][1]) + return ( + self.prefixToVehicleType[linenameU[:4]][0], + self.prefixToVehicleType[linenameU[:4]][1], + ) if linename[:3] in self.prefixToVehicleType: - return ( self.prefixToVehicleType[linenameU[:3]][0], self.prefixToVehicleType[linenameU[:3]][1]) + return ( + self.prefixToVehicleType[linenameU[:3]][0], + self.prefixToVehicleType[linenameU[:3]][1], + ) return ("", "") - def getVehicleTypeAndCapacity(self, linename, timeperiod): - """ returns (vehicletype, vehiclecapacity) - """ + """returns (vehicletype, vehiclecapacity)""" (system, vehicleType) = self.getSystemAndVehicleType(linename, timeperiod) - + if vehicleType not in self.vehicleTypeToCapacity: - raise NetworkException("Vehicle type [%s] of system [%s] characteristics unknown; line name = [%s]" % (vehicleType, system, linename.upper())) + raise NetworkException( + "Vehicle type [%s] of system [%s] characteristics unknown; line name = [%s]" + % (vehicleType, system, linename.upper()) + ) capacity = self.vehicleTypeToCapacity[vehicleType] return (vehicleType, capacity) @@ -194,7 +281,10 @@ def getSimpleDwell(self, linename, timeperiod): """ (system, vehicleType) = self.getSystemAndVehicleType(linename, timeperiod) if vehicleType not in self.vehicleTypeToDelays: - raise NetworkException("Vehicle type [%s] of system [%s] simple dwell unknown; line name = [%s]" % (vehicleType, system, linename.upper())) + raise NetworkException( + "Vehicle type [%s] of system [%s] simple dwell unknown; line name = [%s]" + % (vehicleType, system, linename.upper()) + ) return self.vehicleTypeToDelays[vehicleType][TransitCapacity.DELAY_SIMPLE] @@ -204,11 +294,16 @@ def getComplexDwells(self, linename, timeperiod): """ (system, vehicleType) = self.getSystemAndVehicleType(linename, timeperiod) if vehicleType not in self.vehicleTypeToDelays: - raise NetworkException("Vehicle type [%s] of system [%s] complex dwell unknown; line name = [%s]" % (vehicleType, system, linename.upper())) + raise NetworkException( + "Vehicle type [%s] of system [%s] complex dwell unknown; line name = [%s]" + % (vehicleType, system, linename.upper()) + ) - return (self.vehicleTypeToDelays[vehicleType][TransitCapacity.DELAY_CONST], - self.vehicleTypeToDelays[vehicleType][TransitCapacity.DELAY_PERBOARD], - self.vehicleTypeToDelays[vehicleType][TransitCapacity.DELAY_PERALIGHT]) + return ( + self.vehicleTypeToDelays[vehicleType][TransitCapacity.DELAY_CONST], + self.vehicleTypeToDelays[vehicleType][TransitCapacity.DELAY_PERBOARD], + self.vehicleTypeToDelays[vehicleType][TransitCapacity.DELAY_PERALIGHT], + ) def addVehicleType(self, newVehicleType, newVehicleCapacity): """ @@ -223,72 +318,132 @@ def addLinenameFromTemplate(self, newLine, templateLine): Dupe the entry in self.linenameToAttributes for template into newline """ if templateLine not in self.linenameToAttributes: - raise NetworkException("addLinename with unknown templateLine %s for %s" % (templateLine, newLine)) - - self.linenameToAttributes[newLine] = copy.deepcopy(self.linenameToAttributes[templateLine]) - self.linenameToSimple[newLine] = copy.deepcopy(self.linenameToSimple[templateLine]) - - def addLineName(self, newLine, system, fullname, vehicletype_AM, vehicletype_PM, vehicletype_OP): + raise NetworkException( + "addLinename with unknown templateLine %s for %s" + % (templateLine, newLine) + ) + + self.linenameToAttributes[newLine] = copy.deepcopy( + self.linenameToAttributes[templateLine] + ) + self.linenameToSimple[newLine] = copy.deepcopy( + self.linenameToSimple[templateLine] + ) + + def addLineName( + self, newLine, system, fullname, vehicletype_AM, vehicletype_PM, vehicletype_OP + ): """ Adds a new line with the given vehicle type information """ - self.linenameToAttributes[newLine] = [system, fullname, vehicletype_AM, vehicletype_PM, vehicletype_OP] - self.linenameToSimple[newLine] = [fullname, fullname] + self.linenameToAttributes[newLine] = [ + system, + fullname, + vehicletype_AM, + vehicletype_PM, + vehicletype_OP, + ] + self.linenameToSimple[newLine] = [fullname, fullname] - def setAllVehicleTypes(self, linename, vehicleType, lineNameIsRegex = False): + def setAllVehicleTypes(self, linename, vehicleType, lineNameIsRegex=False): """ Simple method to set the vehicle types for this line name. *linename* is a string; pass *lineNameIsRegex* to interpret it as a regex """ - self.setVehicleTypes(linename, - vehicleType_AM=vehicleType, - vehicleType_PM=vehicleType, - vehicleType_OP=vehicleType, - lineNameIsRegex=lineNameIsRegex) - + self.setVehicleTypes( + linename, + vehicleType_AM=vehicleType, + vehicleType_PM=vehicleType, + vehicleType_OP=vehicleType, + lineNameIsRegex=lineNameIsRegex, + ) + def setVehicleTypeForPrefix(self, prefix, system, vehicleType): """ Sets the vehicle type for the given prefix. """ if vehicleType not in self.vehicleTypeToCapacity: - WranglerLogger.warn("Setting vehicle type for prefix {} but vehicleType {} unknown" % (prefix, vehicleType)) + WranglerLogger.warn( + "Setting vehicle type for prefix {} but vehicleType {} unknown" + % (prefix, vehicleType) + ) if prefix in self.prefixToVehicleType.keys(): - WranglerLogger.debug("Overwriting system/vehicle type for prefix {}. Was {}/{}, setting to {}/{}".format(prefix, - self.prefixToVehicleType[prefix][0], self.prefixToVehicleType[prefix][1], system, vehicleType)) + WranglerLogger.debug( + "Overwriting system/vehicle type for prefix {}. Was {}/{}, setting to {}/{}".format( + prefix, + self.prefixToVehicleType[prefix][0], + self.prefixToVehicleType[prefix][1], + system, + vehicleType, + ) + ) # prefix -> [ system, vehicletype ] self.prefixToVehicleType[prefix] = [system, vehicleType] - def setVehicleTypes(self, linename, vehicleType_AM, vehicleType_PM, vehicleType_OP, lineNameIsRegex = False): + def setVehicleTypes( + self, + linename, + vehicleType_AM, + vehicleType_PM, + vehicleType_OP, + lineNameIsRegex=False, + ): """ Sets the vehicle types for this line name. *linename* is a string; pass *lineNameIsRegex* to interpret it as a regex """ if vehicleType_AM not in self.vehicleTypeToCapacity: - WranglerLogger.warn("Setting vehicle type for line %s but vehicleType %s unknown" % (linename, vehicleType_AM)) + WranglerLogger.warn( + "Setting vehicle type for line %s but vehicleType %s unknown" + % (linename, vehicleType_AM) + ) if vehicleType_PM not in self.vehicleTypeToCapacity: - WranglerLogger.warn("Setting vehicle type for line %s but vehicleType %s unknown" % (linename, vehicleType_PM)) + WranglerLogger.warn( + "Setting vehicle type for line %s but vehicleType %s unknown" + % (linename, vehicleType_PM) + ) if vehicleType_OP not in self.vehicleTypeToCapacity: - WranglerLogger.warn("Setting vehicle type for line %s but vehicleType %s unknown" % (linename, vehicleType_OP)) - + WranglerLogger.warn( + "Setting vehicle type for line %s but vehicleType %s unknown" + % (linename, vehicleType_OP) + ) if lineNameIsRegex: num_updated = 0 linename_re = re.compile(linename, flags=re.IGNORECASE) for name in self.linenameToAttributes.keys(): if re.search(linename_re, name): - self.linenameToAttributes[name][TransitCapacity.ATTR_AMVEHTYPE] = vehicleType_AM - self.linenameToAttributes[name][TransitCapacity.ATTR_PMVEHTYPE] = vehicleType_PM - self.linenameToAttributes[name][TransitCapacity.ATTR_OPVEHTYPE] = vehicleType_OP + self.linenameToAttributes[name][ + TransitCapacity.ATTR_AMVEHTYPE + ] = vehicleType_AM + self.linenameToAttributes[name][ + TransitCapacity.ATTR_PMVEHTYPE + ] = vehicleType_PM + self.linenameToAttributes[name][ + TransitCapacity.ATTR_OPVEHTYPE + ] = vehicleType_OP num_updated += 1 - if num_updated==0: - WranglerLogger.warn("setVehicleTypes called with lineNameIsRegex and no matching configuration for line name {}".format(linename)) + if num_updated == 0: + WranglerLogger.warn( + "setVehicleTypes called with lineNameIsRegex and no matching configuration for line name {}".format( + linename + ) + ) else: if linename.upper() not in self.linenameToAttributes: - raise NetworkException("TransitCapacity: setAllVehicleTypes for unknown linename %s" % linename) - - self.linenameToAttributes[linename.upper()][TransitCapacity.ATTR_AMVEHTYPE] = vehicleType_AM - self.linenameToAttributes[linename.upper()][TransitCapacity.ATTR_PMVEHTYPE] = vehicleType_PM - self.linenameToAttributes[linename.upper()][TransitCapacity.ATTR_OPVEHTYPE] = vehicleType_OP - \ No newline at end of file + raise NetworkException( + "TransitCapacity: setAllVehicleTypes for unknown linename %s" + % linename + ) + + self.linenameToAttributes[linename.upper()][ + TransitCapacity.ATTR_AMVEHTYPE + ] = vehicleType_AM + self.linenameToAttributes[linename.upper()][ + TransitCapacity.ATTR_PMVEHTYPE + ] = vehicleType_PM + self.linenameToAttributes[linename.upper()][ + TransitCapacity.ATTR_OPVEHTYPE + ] = vehicleType_OP diff --git a/Wrangler/TransitLine.py b/Wrangler/TransitLine.py index 170257e..ae0f694 100644 --- a/Wrangler/TransitLine.py +++ b/Wrangler/TransitLine.py @@ -4,7 +4,8 @@ from .Node import Node from .Logger import WranglerLogger -__all__ = ['TransitLine'] +__all__ = ["TransitLine"] + class TransitLine(object): """ @@ -15,101 +16,107 @@ class TransitLine(object): thisroute['MODE']='5' """ - + HOURS_PER_TIMEPERIOD = { - Network.MODEL_TYPE_CHAMP:{ - "AM":3.0, #what about 4-6a? - "MD":6.5, - "PM":3.0, - "EV":8.5, - "EA":3.0 + Network.MODEL_TYPE_CHAMP: { + "AM": 3.0, # what about 4-6a? + "MD": 6.5, + "PM": 3.0, + "EV": 8.5, + "EA": 3.0, + }, + Network.MODEL_TYPE_TM1: { + # https://github.com/BayAreaMetro/modeling-website/wiki/TimePeriods + "EA": 3.0, + "AM": 4.0, + "MD": 5.0, + "PM": 4.0, + "EV": 8.0, }, - Network.MODEL_TYPE_TM1:{ - # https://github.com/BayAreaMetro/modeling-website/wiki/TimePeriods - "EA":3.0, - "AM":4.0, - "MD":5.0, - "PM":4.0, - "EV":8.0 - } } MODETYPE_TO_MODES = { - Network.MODEL_TYPE_CHAMP:{ - "Local" :[11,12,16,17,18,19], - "BRT" :[13,20], - "LRT" :[14,15,21], - "Premium":[22,23,24,25,26,27,28,29,30], - "Ferry" :[31], - "BART" :[32] + Network.MODEL_TYPE_CHAMP: { + "Local": [11, 12, 16, 17, 18, 19], + "BRT": [13, 20], + "LRT": [14, 15, 21], + "Premium": [22, 23, 24, 25, 26, 27, 28, 29, 30], + "Ferry": [31], + "BART": [32], + }, + Network.MODEL_TYPE_TM1: { + "Local": range(10, 80), + "Express Bus": range(80, 100), + "Ferry": range(100, 110), + "Light Rail": range(110, 120), + "Heavy Rail": range(120, 130), + "Commuter Rail": range(130, 140), }, - Network.MODEL_TYPE_TM1:{ - "Local" :range( 10, 80), - "Express Bus" :range( 80,100), - "Ferry" :range(100,110), - "Light Rail" :range(110,120), - "Heavy Rail" :range(120,130), - "Commuter Rail":range(130,140) - } } # Do these modes have offstreet stops? MODENUM_TO_OFFSTREET = { - Network.MODEL_TYPE_CHAMP:{ - 11:False, # muni bus - 12:False, # muni Express bus - 13:False, # muni BRT - 14:False, # muni cable car -- These are special because they don't have explicit WNR nodes - 15:False, # muni LRT -- and are just implemented by reading the muni.xfer line as muni.access - 16:False, # Shuttles - 17:False, # SamTrans bus - 18:False, # AC bus - 19:False, # other local bus - 20:False, # Regional BRT - 21:True, # Santa Clara LRT - 22:False, # AC premium bus - 23:False, # GG premium bus - 24:False, # SamTrans premium bus - 25:False, # Other premium bus - 26:True, # Caltrain - 27:True, # SMART - 28:True, # eBART - 29:True, # Regional Rail/ACE/Amtrak - 30:True, # HSR - 31:True, # Ferry - 32:True # BART + Network.MODEL_TYPE_CHAMP: { + 11: False, # muni bus + 12: False, # muni Express bus + 13: False, # muni BRT + 14: False, # muni cable car -- These are special because they don't have explicit WNR nodes + 15: False, # muni LRT -- and are just implemented by reading the muni.xfer line as muni.access + 16: False, # Shuttles + 17: False, # SamTrans bus + 18: False, # AC bus + 19: False, # other local bus + 20: False, # Regional BRT + 21: True, # Santa Clara LRT + 22: False, # AC premium bus + 23: False, # GG premium bus + 24: False, # SamTrans premium bus + 25: False, # Other premium bus + 26: True, # Caltrain + 27: True, # SMART + 28: True, # eBART + 29: True, # Regional Rail/ACE/Amtrak + 30: True, # HSR + 31: True, # Ferry + 32: True, # BART + }, + Network.MODEL_TYPE_TM1: { + # https://github.com/BayAreaMetro/modeling-website/wiki/TransitModes + 20: False, # Muni Cable Car + 100: True, # East Bay Ferry + 101: True, # Golden Gate Ferry + 102: True, # Golden Gate Ferry + 103: True, # Tiburon Ferry + 104: True, # Vallejo Baylink Ferry + 105: True, # South City Ferry + 110: False, # Muni Metro + 111: True, # Santa Clara VTA LRT + 120: True, # BART + 121: True, # Oakland Airport Connector + 130: True, # Caltrain + 131: True, # Amtrak Capitol Corridor + 132: True, # Amtrak San Joaquin + 133: True, # ACE + 134: True, # Dumbarton Rail + 135: True, # SMART + 136: True, # EBART + 137: True, # High speed rail }, - Network.MODEL_TYPE_TM1:{ - # https://github.com/BayAreaMetro/modeling-website/wiki/TransitModes - 20 :False, # Muni Cable Car - 100:True, # East Bay Ferry - 101:True, # Golden Gate Ferry - 102:True, # Golden Gate Ferry - 103:True, # Tiburon Ferry - 104:True, # Vallejo Baylink Ferry - 105:True, # South City Ferry - 110:False, # Muni Metro - 111:True, # Santa Clara VTA LRT - 120:True, # BART - 121:True, # Oakland Airport Connector - 130:True, # Caltrain - 131:True, # Amtrak Capitol Corridor - 132:True, # Amtrak San Joaquin - 133:True, # ACE - 134:True, # Dumbarton Rail - 135:True, # SMART - 136:True, # EBART - 137:True # High speed rail - } } - + def __init__(self, name=None, template=None): - self.attr = { "FREQ[1]":0, "FREQ[2]":0, "FREQ[3]":0, "FREQ[4]":0, "FREQ[5]":0 } + self.attr = { + "FREQ[1]": 0, + "FREQ[2]": 0, + "FREQ[3]": 0, + "FREQ[4]": 0, + "FREQ[5]": 0, + } self.n = [] self.comment = None self.name = name - if name and name.find('"')==0: + if name and name.find('"') == 0: self.name = name[1:-1] # Strip leading/trailing dbl-quotes if template: @@ -131,7 +138,9 @@ def __eq__(self, other): elif isinstance(other, TransitLine): return self.name == other.name else: - WranglerLogger.error("TransitLine.__eq__ called with other type {}".format(type(other))) + WranglerLogger.error( + "TransitLine.__eq__ called with other type {}".format(type(other)) + ) return False def __next__(self): @@ -146,117 +155,148 @@ def __next__(self): raise StopIteration self.currentStopIdx += 1 - return int(self.n[self.currentStopIdx-1].num) + return int(self.n[self.currentStopIdx - 1].num) # python 2 backwards compat next = __next__ def setFreqs(self, freqs, timepers=None, allowDowngrades=True): - '''Set some or all five headways (AM,MD,PM,EV,EA) - - freqs is a list of numbers (or can be one number if only setting one headway) - also accepts list of strings of numbers e.g. ["8","0","8","0","0"] - - If setting fewer than 5 headways, timepers must specify the time period(s) - for which headways are being set. Can be numbers like [1,3] or strings like ['AM','PM']. - If setting all headways, True or 'All' may be passed. - - allowDowngrades (optional, pass either True or False) specifies whether headways - may be increased (i.e., whether service may be reduced) with the current action. - ''' - all_timepers = ['AM','MD','PM','EV','EA'] - if timepers in (None, True, 'All', 'all', 'ALL'): - if not len(freqs)==5: raise NetworkException('Must specify all 5 frequencies or specify time periods to set') + """Set some or all five headways (AM,MD,PM,EV,EA) + - freqs is a list of numbers (or can be one number if only setting one headway) + also accepts list of strings of numbers e.g. ["8","0","8","0","0"] + - If setting fewer than 5 headways, timepers must specify the time period(s) + for which headways are being set. Can be numbers like [1,3] or strings like ['AM','PM']. + If setting all headways, True or 'All' may be passed. + - allowDowngrades (optional, pass either True or False) specifies whether headways + may be increased (i.e., whether service may be reduced) with the current action. + """ + all_timepers = ["AM", "MD", "PM", "EV", "EA"] + if timepers in (None, True, "All", "all", "ALL"): + if not len(freqs) == 5: + raise NetworkException( + "Must specify all 5 frequencies or specify time periods to set" + ) num_freqs = 5 num_timepers = 5 timepers = all_timepers[:] else: try: num_freqs = len(freqs) - except TypeError: # only single number, not list, passed + except TypeError: # only single number, not list, passed num_freqs = 1 freqs = [freqs] try: num_timepers = len(timepers) - except TypeError: # only single time period, not list, passed + except TypeError: # only single time period, not list, passed num_timepers = 1 timepers = [timepers] - if num_freqs != num_timepers: raise NetworkException('Specified ' + num_freqs + ' frequencies for ' + num_timepers + ' time periods') + if num_freqs != num_timepers: + raise NetworkException( + "Specified " + + num_freqs + + " frequencies for " + + num_timepers + + " time periods" + ) for i in range(num_timepers): timeper = timepers[i] try: - timeper_int = int(timeper) # time period may be number (1) or string ("1") + timeper_int = int( + timeper + ) # time period may be number (1) or string ("1") timepers[i] = all_timepers[timeper_int - 1] timeper_idx = timeper_int except ValueError: # time period may be descriptive ("AM") timeper = timeper.upper() - if timeper not in all_timepers: raise NetworkException('"' + timeper + '" is not a valid time period') + if timeper not in all_timepers: + raise NetworkException( + '"' + timeper + '" is not a valid time period' + ) timeper_idx = 1 + all_timepers.index(timeper) - attr_set = 'FREQ[' + str(timeper_idx) + ']' - if(allowDowngrades): + attr_set = "FREQ[" + str(timeper_idx) + "]" + if allowDowngrades: self.attr[attr_set] = float(freqs[i]) else: - self.attr[attr_set] = min(float(freqs[i]),float(self.attr[attr_set])) + self.attr[attr_set] = min(float(freqs[i]), float(self.attr[attr_set])) - def getFreqs(self): """ Return the frequencies for this line as a list of 5 strings (representing AM,MD,PM,EV,EA for CHAMP, or EA,AM,MD,PM,EV for TM1) """ try: - if 'HEADWAY[1]' in self.attr: - return [self.attr['HEADWAY[1]'], - self.attr['HEADWAY[2]'], - self.attr['HEADWAY[3]'], - self.attr['HEADWAY[4]'], - self.attr['HEADWAY[5]']] - - return [self.attr['FREQ[1]'], - self.attr['FREQ[2]'], - self.attr['FREQ[3]'], - self.attr['FREQ[4]'], - self.attr['FREQ[5]']] + if "HEADWAY[1]" in self.attr: + return [ + self.attr["HEADWAY[1]"], + self.attr["HEADWAY[2]"], + self.attr["HEADWAY[3]"], + self.attr["HEADWAY[4]"], + self.attr["HEADWAY[5]"], + ] + + return [ + self.attr["FREQ[1]"], + self.attr["FREQ[2]"], + self.attr["FREQ[3]"], + self.attr["FREQ[4]"], + self.attr["FREQ[5]"], + ] except: - WranglerLogger.fatal("problem with getFreqs() for {} self.attr={}".format(self.name, self.attr)) + WranglerLogger.fatal( + "problem with getFreqs() for {} self.attr={}".format( + self.name, self.attr + ) + ) def getFreq(self, timeperiod, modeltype): """ Returns a float version of the frequency for the given *timeperiod*, which should be one of ``AM``, ``MD``, ``PM``, ``EV`` or ``EA`` """ - if modeltype==Network.MODEL_TYPE_CHAMP: - if timeperiod=="AM": + if modeltype == Network.MODEL_TYPE_CHAMP: + if timeperiod == "AM": return float(self.attr["FREQ[1]"]) - elif timeperiod=="MD": + elif timeperiod == "MD": return float(self.attr["FREQ[2]"]) - elif timeperiod=="PM": + elif timeperiod == "PM": return float(self.attr["FREQ[3]"]) - elif timeperiod=="EV": + elif timeperiod == "EV": return float(self.attr["FREQ[4]"]) - elif timeperiod=="EA": + elif timeperiod == "EA": return float(self.attr["FREQ[5]"]) - if modeltype==Network.MODEL_TYPE_TM1: - if timeperiod=="EA": + if modeltype == Network.MODEL_TYPE_TM1: + if timeperiod == "EA": return float(self.attr["FREQ[1]"]) - elif timeperiod=="AM": + elif timeperiod == "AM": return float(self.attr["FREQ[2]"]) - elif timeperiod=="MD": + elif timeperiod == "MD": return float(self.attr["FREQ[3]"]) - elif timeperiod=="PM": + elif timeperiod == "PM": return float(self.attr["FREQ[4]"]) - elif timeperiod=="EV": + elif timeperiod == "EV": return float(self.attr["FREQ[5]"]) - raise NetworkException("getFreq() received invalid timeperiod {} or modeltype {}".format(timeperiod, modeltype)) + raise NetworkException( + "getFreq() received invalid timeperiod {} or modeltype {}".format( + timeperiod, modeltype + ) + ) def hasService(self): """ Returns true if any frequency is nonzero. """ - if self.getFreq("AM") != 0: return True - if self.getFreq("MD") != 0: return True - if self.getFreq("PM") != 0: return True - if self.getFreq("EV") != 0: return True - if self.getFreq("EA") != 0: return True + if self.getFreq("AM") != 0: + return True + if self.getFreq("MD") != 0: + return True + if self.getFreq("PM") != 0: + return True + if self.getFreq("EV") != 0: + return True + if self.getFreq("EA") != 0: + return True return False def setOwner(self, newOwner): @@ -267,11 +307,11 @@ def setOwner(self, newOwner): def getModeType(self, modeltype): """ - Returns on of the keys in MODETYPE_TO_MODES + Returns on of the keys in MODETYPE_TO_MODES (e.g. one of "Local", "BRT", "LRT", "Premium", "Ferry" or "BART") """ - modenum = int(self.attr['MODE']) - for modetype,modelist in TransitLine.MODETYPE_TO_MODES[modeltype].items(): + modenum = int(self.attr["MODE"]) + for modetype, modelist in TransitLine.MODETYPE_TO_MODES[modeltype].items(): if modenum in modelist: return modetype return None @@ -281,7 +321,9 @@ def isOneWay(self): Returns a bool indicating if the line is oneway """ if "ONEWAY" not in self.attr: - WranglerLogger.debug("line [{}] lacks ONEWAY attribute; assuming true".format(self.name)) + WranglerLogger.debug( + "line [{}] lacks ONEWAY attribute; assuming true".format(self.name) + ) return True oneway = self.attr["ONEWAY"] @@ -289,7 +331,7 @@ def isOneWay(self): return False # default is true return True - + def setOneWay(self, oneway=True): """ Sets the oneway flag based on the given arg. @@ -298,12 +340,12 @@ def setOneWay(self, oneway=True): self.attr["ONEWAY"] = "T" else: self.attr["ONEWAY"] = "F" - + def hasOffstreetNodes(self, modeltype): """ Returns True if the line has offstreet nodes """ - modenum = int(self.attr['MODE']) + modenum = int(self.attr["MODE"]) if modenum in TransitLine.MODENUM_TO_OFFSTREET[modeltype]: return TransitLine.MODENUM_TO_OFFSTREET[modeltype][modenum] @@ -318,11 +360,11 @@ def vehiclesPerPeriod(self, timeperiod, modeltype): freq = self.getFreq(timeperiod, modeltype) if freq < 0.01: return 0.0 - + # minutes per time period divided by frequency - return 60.0*self.HOURS_PER_TIMEPERIOD[modeltype][timeperiod]/freq - - def hasNode(self,nodeNumber): + return 60.0 * self.HOURS_PER_TIMEPERIOD[modeltype][timeperiod] / freq + + def hasNode(self, nodeNumber): """ Returns True if the given *nodeNumber* is a node in this line (stop or no). *nodeNumber* should be an integer. @@ -331,8 +373,8 @@ def hasNode(self,nodeNumber): if abs(int(node.num)) == abs(nodeNumber): return True return False - - def hasLink(self,nodeA,nodeB): + + def hasLink(self, nodeA, nodeB): """ Returns True iff *(nodeA,nodeB)* is a link in this line. *nodeA* and *nodeB* should be integers and this method is stop-insensitive. @@ -345,24 +387,26 @@ def hasLink(self,nodeA,nodeB): return True nodeNumPrev = nodeNum return False - - def hasSegment(self,nodeA,nodeB): + + def hasSegment(self, nodeA, nodeB): """ Returns True iff *nodeA* and *nodeB* appear in this line, and *nodeA* appears before *nodeB*. This method is stop-insensitive. Also it does not do any special checking for two-way lines. """ - hasA=False + hasA = False for node in self.n: nodeNum = abs(int(node.num)) if nodeNum == abs(nodeA): - hasA=True + hasA = True elif nodeNum == abs(nodeB): - if hasA: return True - else: return False + if hasA: + return True + else: + return False return False - def hasSequence(self,list_of_node_ids): + def hasSequence(self, list_of_node_ids): """ Returns True iff the nodes indicated by list_of_node_ids appear in this line, in the exact specified order. This method is stop-insenstive. @@ -370,11 +414,11 @@ def hasSequence(self,list_of_node_ids): """ node_ids = self.listNodeIds() for i in range(len(node_ids)): - if node_ids[i:i+len(list_of_node_ids)] == list_of_node_ids: + if node_ids[i : i + len(list_of_node_ids)] == list_of_node_ids: return True return False - def listNodeIds(self,ignoreStops=True): + def listNodeIds(self, ignoreStops=True): """ Returns a list of integers representing the node ids that appear along this line. This method is stop-sensitive if called with ignoreStops=False. @@ -382,19 +426,19 @@ def listNodeIds(self,ignoreStops=True): node_ids = [] for node in self.n: nodeNum = int(node.num) - if(ignoreStops): + if ignoreStops: nodeNum = abs(nodeNum) node_ids.append(nodeNum) return node_ids - def numStops(self): """ Counts and returns the number of stops in the line. """ numStops = 0 for node in self.n: - if node.isStop(): numStops += 1 + if node.isStop(): + numStops += 1 return numStops def setNodes(self, newnodelist): @@ -403,10 +447,11 @@ def setNodes(self, newnodelist): converts these to Node types uses this new list, throwing away the previous node list. """ for i in range(len(newnodelist)): - if isinstance(newnodelist[i],int): newnodelist[i] = Node(newnodelist[i]) + if isinstance(newnodelist[i], int): + newnodelist[i] = Node(newnodelist[i]) self.n = newnodelist - - def insertNode(self,refNodeNum,newNodeNum,stop=False,after=True): + + def insertNode(self, refNodeNum, newNodeNum, stop=False, after=True): """ Inserts the given *newNodeNum* into this line, as a stop if *stop* is True. The new node is inserted after *refNodeNum* if *after* is True, otherwise it is inserted @@ -420,21 +465,28 @@ def insertNode(self,refNodeNum,newNodeNum,stop=False,after=True): nodeIdx = 0 while True: # out of nodes -- done - if nodeIdx >= len(self.n): return - + if nodeIdx >= len(self.n): + return + currentNodeNum = abs(int(self.n[nodeIdx].num)) if currentNodeNum == abs(refNodeNum): - if after==True: - self.n.insert(nodeIdx+1,newNode) - WranglerLogger.debug("In line %s: inserted node %s after node %s" % (self.name,newNode.num,str(refNodeNum))) + if after == True: + self.n.insert(nodeIdx + 1, newNode) + WranglerLogger.debug( + "In line %s: inserted node %s after node %s" + % (self.name, newNode.num, str(refNodeNum)) + ) else: - self.n.insert(nodeIdx,newNode) - WranglerLogger.debug("In line %s: inserted node %s before node %s" % (self.name,newNode.num,str(refNodeNum))) - nodeIdx += 1 # skip ahead one since we just added - + self.n.insert(nodeIdx, newNode) + WranglerLogger.debug( + "In line %s: inserted node %s before node %s" + % (self.name, newNode.num, str(refNodeNum)) + ) + nodeIdx += 1 # skip ahead one since we just added + nodeIdx += 1 - - def splitLink(self,nodeA,nodeB,newNodeNum,stop=False,verboseLog=True): + + def splitLink(self, nodeA, nodeB, newNodeNum, stop=False, verboseLog=True): """ Checks to see if the link exists in the line (throws an exception if not) and then inserts the *newNodeNum* in between *nodeA* and *nodeB* (as a stop, if *stop* is True) @@ -443,19 +495,26 @@ def splitLink(self,nodeA,nodeB,newNodeNum,stop=False,verboseLog=True): This is stop-insensitive to *nodeA* and *nodeB*. """ - if not self.hasLink(nodeA,nodeB): - raise NetworkException( "Line %s Doesn't have that link - so can't split it" % (self.name)) + if not self.hasLink(nodeA, nodeB): + raise NetworkException( + "Line %s Doesn't have that link - so can't split it" % (self.name) + ) newNode = Node(newNodeNum) - if stop==True: newNode.setStop(True) - + if stop == True: + newNode.setStop(True) + nodeNumPrev = -1 for nodeIdx in range(len(self.n)): currentNodeNum = abs(int(self.n[nodeIdx].num)) if currentNodeNum == abs(nodeB) and nodeNumPrev == abs(nodeA): - self.n.insert(nodeIdx,newNode) - if verboseLog: WranglerLogger.debug("In line %s: inserted node %s between node %s and node %s" % (self.name,newNode.num,str(nodeA),str(nodeB))) + self.n.insert(nodeIdx, newNode) + if verboseLog: + WranglerLogger.debug( + "In line %s: inserted node %s between node %s and node %s" + % (self.name, newNode.num, str(nodeA), str(nodeB)) + ) nodeNumPrev = currentNodeNum - + def extendLine(self, oldnode, newsection, beginning=True): """ Replace nodes up through **and including** *oldnode* with *newsection*. @@ -467,63 +526,73 @@ def extendLine(self, oldnode, newsection, beginning=True): """ oldnode_found = False for ind in range(len(self.n)): - if self.n[ind].getNum()==oldnode: + if self.n[ind].getNum() == oldnode: oldnode_found = True break if oldnode_found == False: - raise NetworkException("extendLine() called but oldnode {} not found in line nodes {}".format(oldnode, self.listNodeIds(ignoreStops=False))) + raise NetworkException( + "extendLine() called but oldnode {} not found in line nodes {}".format( + oldnode, self.listNodeIds(ignoreStops=False) + ) + ) # make the new nodes for i in range(len(newsection)): - if isinstance(newsection[i],int): newsection[i] = Node(newsection[i]) - + if isinstance(newsection[i], int): + newsection[i] = Node(newsection[i]) + if beginning: # print self.n[:ind+1] - self.n[:ind+1] = newsection + self.n[: ind + 1] = newsection else: self.n[ind:] = newsection - + def replaceSegment(self, node1, node2, newsection, preserveStopStatus=False): - """ Replaces the section from node1 to node2 with the newsection - Newsection can be an array of numbers; this will make nodes. - preserveStopStatus means if node1 is a stop, make the replacement first node a stop, ditto for node2 + """Replaces the section from node1 to node2 with the newsection + Newsection can be an array of numbers; this will make nodes. + preserveStopStatus means if node1 is a stop, make the replacement first node a stop, ditto for node2 """ # Make a list of ints since newsection might be nodes new_section_ints = copy.deepcopy(newsection) for i in range(len(new_section_ints)): - if isinstance(new_section_ints[i],Node): new_section_ints[i] = new_section_ints[i].num - - WranglerLogger.debug("replacing segment in {} {}-{} with {}".format( - self.name, node1, node2 ,new_section_ints)) + if isinstance(new_section_ints[i], Node): + new_section_ints[i] = new_section_ints[i].num + + WranglerLogger.debug( + "replacing segment in {} {}-{} with {}".format( + self.name, node1, node2, new_section_ints + ) + ) try: ind1 = self.n.index(node1) stop1 = True except: ind1 = self.n.index(-node1) stop1 = False - + try: ind2 = self.n.index(node2) stop2 = True except: ind2 = self.n.index(-node2) stop2 = False - + attr1 = self.n[ind1].attr attr2 = self.n[ind2].attr - + # make the new nodes for i in range(len(newsection)): - if isinstance(newsection[i],int): newsection[i] = Node(newsection[i]) + if isinstance(newsection[i], int): + newsection[i] = Node(newsection[i]) # xfer the attributes - newsection[0].attr=attr1 - newsection[-1].attr=attr2 - + newsection[0].attr = attr1 + newsection[-1].attr = attr2 + if preserveStopStatus: newsection[0].setStop(stop1) newsection[-1].setStop(stop2) - - self.n[ind1:ind2+1] = newsection + + self.n[ind1 : ind2 + 1] = newsection def replaceSequence(self, node_ids_to_replace, replacement_node_ids): """ @@ -533,37 +602,51 @@ def replaceSequence(self, node_ids_to_replace, replacement_node_ids): Returns true iff the sequence is successfully replaced. """ if self.hasSequence(node_ids_to_replace): - WranglerLogger.debug("replacing sequence " + str(node_ids_to_replace) + " with " + str(replacement_node_ids) + " for " + self.name) + WranglerLogger.debug( + "replacing sequence " + + str(node_ids_to_replace) + + " with " + + str(replacement_node_ids) + + " for " + + self.name + ) else: return False node_ids = self.listNodeIds() replaceNodesStartingAt = -1 for i in range(len(node_ids)): - if node_ids[i:i+len(node_ids_to_replace)] == node_ids_to_replace: + if node_ids[i : i + len(node_ids_to_replace)] == node_ids_to_replace: replaceNodesStartingAt = i break if replaceNodesStartingAt < 0: - WranglerLogger.debug("an unexpected error occurred in replaceSequence for " + self.name) + WranglerLogger.debug( + "an unexpected error occurred in replaceSequence for " + self.name + ) return False attr1 = self.n[replaceNodesStartingAt].attr - attr2 = self.n[replaceNodesStartingAt+len(node_ids_to_replace)].attr - + attr2 = self.n[replaceNodesStartingAt + len(node_ids_to_replace)].attr + # make the new nodes - replacement_nodes = list(replacement_node_ids) # copy this, we'll make them nodes + replacement_nodes = list( + replacement_node_ids + ) # copy this, we'll make them nodes for i in range(len(replacement_nodes)): - if isinstance(replacement_nodes[i],int): replacement_nodes[i] = Node(replacement_nodes[i]) + if isinstance(replacement_nodes[i], int): + replacement_nodes[i] = Node(replacement_nodes[i]) # they aren't stops replacement_nodes[i].setStop(False) # xfer the attributes - replacement_nodes[0].attr=attr1 - replacement_nodes[-1].attr=attr2 + replacement_nodes[0].attr = attr1 + replacement_nodes[-1].attr = attr2 - self.n[replaceNodesStartingAt:replaceNodesStartingAt+len(node_ids_to_replace)] = replacement_nodes + self.n[ + replaceNodesStartingAt : replaceNodesStartingAt + len(node_ids_to_replace) + ] = replacement_nodes return True def setStop(self, nodenum, isStop=True): - """ + """ Throws an exception if the nodenum isn't found """ found = False @@ -572,24 +655,28 @@ def setStop(self, nodenum, isStop=True): node.setStop(isStop) found = True if not found: - raise NetworkException("TransitLine %s setStop called but stop %d not found" % (self.name, nodenum)) + raise NetworkException( + "TransitLine %s setStop called but stop %d not found" + % (self.name, nodenum) + ) def addStopsToSet(self, set): for nodeIdx in range(len(self.n)): if self.n[nodeIdx].isStop(): set.add(int(self.n[nodeIdx].num)) - + def reverse(self): """ Reverses the current line -- adds a "-" to the name, and reverses the node order """ # if name is 12 chars, have to drop one -- cube has a MAX of 12 - if len(self.name)>=11: self.name = self.name[:11] + if len(self.name) >= 11: + self.name = self.name[:11] self.name = self.name + "R" self.n.reverse() - + def _applyTemplate(self, template): - '''Copy all attributes (including nodes) from an existing transit line to this line''' + """Copy all attributes (including nodes) from an existing transit line to this line""" self.attr = copy.deepcopy(template.attr) self.n = copy.deepcopy(template.n) self.comment = template.comment @@ -600,15 +687,17 @@ def hasDuplicateStops(self): First == last is ok """ _stop_to_idx = {} - _stop_list = [] + _stop_list = [] for node in self.n: - if not node.isStop(): continue + if not node.isStop(): + continue node_num = node.getNum() # { stop num -> [list of stop indices]}, index starts at 0 - if node_num not in _stop_to_idx: _stop_to_idx[node_num] = [] + if node_num not in _stop_to_idx: + _stop_to_idx[node_num] = [] _stop_to_idx[node_num].append(len(_stop_list)) # list of stops @@ -616,11 +705,15 @@ def hasDuplicateStops(self): # check for dupes for node_num in _stop_to_idx.keys(): - if len(_stop_to_idx[node_num]) == 1: continue + if len(_stop_to_idx[node_num]) == 1: + continue # First == last is ok - if _stop_to_idx[node_num] == [0,len(_stop_list)-1]: continue + if _stop_to_idx[node_num] == [0, len(_stop_list) - 1]: + continue - WranglerLogger.warn("Duplicate stops for line {}: stop {}".format(self.name, node_num)) + WranglerLogger.warn( + "Duplicate stops for line {}: stop {}".format(self.name, node_num) + ) return True return False @@ -634,41 +727,49 @@ def removeDummyJag(self, to_remove_dict): """ removed_nodes = False # iterate backwards so we can freely delete from the list - for node_idx in range(len(self.n)-3, -1, -1): + for node_idx in range(len(self.n) - 3, -1, -1): - nodeNum = int(self.n[node_idx+2].num) - prev_nodeNum = int(self.n[node_idx+1].num) - prev_prev_nodeNum = int(self.n[node_idx ].num) + nodeNum = int(self.n[node_idx + 2].num) + prev_nodeNum = int(self.n[node_idx + 1].num) + prev_prev_nodeNum = int(self.n[node_idx].num) if nodeNum == prev_prev_nodeNum and prev_nodeNum in to_remove_dict: # WranglerLogger.debug("removeDummyJag: {} ({},{},{}) => {}".format(self.name, nodeNum, prev_nodeNum, prev_prev_nodeNum, nodeNum)) - del[self.n[node_idx+2]] - del[self.n[node_idx+1]] + del [self.n[node_idx + 2]] + del [self.n[node_idx + 1]] removed_nodes = True return removed_nodes # Dictionary methods - def __getitem__(self,key): return self.attr[key.upper()] - def __setitem__(self,key,value): self.attr[key.upper()]=value - def __cmp__(self,other): return cmp(self.name,other) + def __getitem__(self, key): + return self.attr[key.upper()] + + def __setitem__(self, key, value): + self.attr[key.upper()] = value + + def __cmp__(self, other): + return cmp(self.name, other) # String representation: for outputting to line-file def __repr__(self): - s = '\nLINE NAME=\"%s\",\n ' % (self.name,) - if self.comment: s+= self.comment + s = '\nLINE NAME="%s",\n ' % (self.name,) + if self.comment: + s += self.comment # Line attributes - s += ",\n ".join(["%s=%s" % (k,v) for k,v in sorted(self.attr.items())]) + s += ",\n ".join(["%s=%s" % (k, v) for k, v in sorted(self.attr.items())]) # Node list s += ",\n" prevAttr = True for nodeIdx in range(len(self.n)): - s += self.n[nodeIdx].lineFileRepr(prependNEquals=prevAttr, lastNode=(nodeIdx==len(self.n)-1)) - prevAttr = len(self.n[nodeIdx].attr)>0 + s += self.n[nodeIdx].lineFileRepr( + prependNEquals=prevAttr, lastNode=(nodeIdx == len(self.n) - 1) + ) + prevAttr = len(self.n[nodeIdx].attr) > 0 return s def __str__(self): - s = 'Line name \"%s\" freqs=%s' % (self.name, str(self.getFreqs())) + s = 'Line name "%s" freqs=%s' % (self.name, str(self.getFreqs())) return s diff --git a/Wrangler/TransitLink.py b/Wrangler/TransitLink.py index 3fcfcdd..66ca2d6 100644 --- a/Wrangler/TransitLink.py +++ b/Wrangler/TransitLink.py @@ -1,20 +1,22 @@ import re from .Regexes import nodepair_pattern -__all__ = ['TransitLink'] +__all__ = ["TransitLink"] + class TransitLink(dict): - """ Transit support Link. - 'nodes' property is the node-pair for this link (e.g. 24133,34133) - 'comment' is any end-of-line comment for this link - (must include the leading semicolon) - All other attributes are stored in a dictionary (e.g. thislink['MODE']='1,2') + """Transit support Link. + 'nodes' property is the node-pair for this link (e.g. 24133,34133) + 'comment' is any end-of-line comment for this link + (must include the leading semicolon) + All other attributes are stored in a dictionary (e.g. thislink['MODE']='1,2') """ + def __init__(self): dict.__init__(self) - self.id='' - self.comment='' - + self.id = "" + self.comment = "" + self.Anode = None self.Bnode = None @@ -24,39 +26,39 @@ def __repr__(self): # Deal w/all link attributes fields = [] for k in sorted(self.keys()): - fields.append("%s=%s" % (k,self[k])) + fields.append("%s=%s" % (k, self[k])) s += ", ".join(fields) s += self.comment return s - + def addNodesToSet(self, set): - """ Add integer versions of the nodes in this like to the given set - """ + """Add integer versions of the nodes in this like to the given set""" m = re.match(nodepair_pattern, self.id) set.add(int(m.group(1))) set.add(int(m.group(2))) - + def setId(self, id): self.id = id m = re.match(nodepair_pattern, self.id) self.Anode = int(m.group(1)) - self.Bnode = int(m.group(2)) + self.Bnode = int(m.group(2)) def isOneway(self): for key in self.keys(): - - if key.upper()=="ONEWAY": - if self[key].upper() in ["NO", "N", "0", "F", "FALSE"]: return False + + if key.upper() == "ONEWAY": + if self[key].upper() in ["NO", "N", "0", "F", "FALSE"]: + return False return True # key not found - what's the default? return True - + def setOneway(self, oneway_str): for key in self.keys(): - if key.upper()=="ONEWAY": + if key.upper() == "ONEWAY": self[key] = oneway_str return # key not found - self["ONEWAY"] = oneway_str \ No newline at end of file + self["ONEWAY"] = oneway_str diff --git a/Wrangler/TransitNetwork.py b/Wrangler/TransitNetwork.py index f9f096b..9c846da 100644 --- a/Wrangler/TransitNetwork.py +++ b/Wrangler/TransitNetwork.py @@ -16,50 +16,86 @@ from .TransitParser import TransitParser, transit_file_def from .ZACLink import ZACLink -__all__ = ['TransitNetwork'] +__all__ = ["TransitNetwork"] + class TransitNetwork(Network): """ Full Cube representation of a transit network (all components) """ + FARE_FILES = { - Network.MODEL_TYPE_CHAMP: - ["caltrain.fare", "smart.fare", "ebart.fare", - "amtrak.fare", "hsr.fare", "ferry.fare", - "bart.fare", "xfer.fare", "farelinks.fare"], - Network.MODEL_TYPE_TM1: - ["ACE.far", "Amtrak.far", "BART.far", - "Caltrain.far", "Ferry.far", "HSR.far", - "SMART.far", "xfare.far", "farelinks.far", - "transit_faremat.block"], - Network.MODEL_TYPE_TM2: - ["fares.far", "fareMatrix.txt"], + Network.MODEL_TYPE_CHAMP: [ + "caltrain.fare", + "smart.fare", + "ebart.fare", + "amtrak.fare", + "hsr.fare", + "ferry.fare", + "bart.fare", + "xfer.fare", + "farelinks.fare", + ], + Network.MODEL_TYPE_TM1: [ + "ACE.far", + "Amtrak.far", + "BART.far", + "Caltrain.far", + "Ferry.far", + "HSR.far", + "SMART.far", + "xfare.far", + "farelinks.far", + "transit_faremat.block", + ], + Network.MODEL_TYPE_TM2: ["fares.far", "fareMatrix.txt"], } - # Static reference to a TransitCapacity instance capacity = None - def __init__(self, modelType, modelVersion, basenetworkpath=None, networkBaseDir=None, networkProjectSubdir=None, - networkSeedSubdir=None, networkPlanSubdir=None, isTiered=False, networkName=None): + def __init__( + self, + modelType, + modelVersion, + basenetworkpath=None, + networkBaseDir=None, + networkProjectSubdir=None, + networkSeedSubdir=None, + networkPlanSubdir=None, + isTiered=False, + networkName=None, + ): """ If *basenetworkpath* is passed and *isTiered* is True, then start by reading the files named *networkName*.* in the *basenetworkpath* """ - Network.__init__(self, modelType, modelVersion, networkBaseDir, networkProjectSubdir, networkSeedSubdir, - networkPlanSubdir, networkName) - self.program = TransitParser.PROGRAM_TRNBUILD # will be one of PROGRAM_PT or PROGRAM_TRNBUILD - self.lines = [] - self.links = [] # TransitLink instances, Factor instances and comments (strings) - self.pnrs = {} # key is file name since these need to stay separated - self.zacs = [] - self.accessli = [] - self.xferli = [] - self.nodes = [] # transit node coords - self.supps = [] # Supplinks - self.faresystems = {} # key is Id number - self.ptsystem = PTSystem() # single instance - self.farefiles = {} # farefile name -> [ lines in farefile ] + Network.__init__( + self, + modelType, + modelVersion, + networkBaseDir, + networkProjectSubdir, + networkSeedSubdir, + networkPlanSubdir, + networkName, + ) + self.program = ( + TransitParser.PROGRAM_TRNBUILD + ) # will be one of PROGRAM_PT or PROGRAM_TRNBUILD + self.lines = [] + self.links = ( + [] + ) # TransitLink instances, Factor instances and comments (strings) + self.pnrs = {} # key is file name since these need to stay separated + self.zacs = [] + self.accessli = [] + self.xferli = [] + self.nodes = [] # transit node coords + self.supps = [] # Supplinks + self.faresystems = {} # key is Id number + self.ptsystem = PTSystem() # single instance + self.farefiles = {} # farefile name -> [ lines in farefile ] for farefile in TransitNetwork.FARE_FILES[self.modelType]: self.farefiles[farefile] = [] @@ -69,13 +105,18 @@ def __init__(self, modelType, modelVersion, basenetworkpath=None, networkBaseDir if basenetworkpath and isTiered: if not networkName: - raise NetworkException("Cannot initialize tiered TransitNetwork with basenetworkpath %s: no networkName specified" % basenetworkpath) + raise NetworkException( + "Cannot initialize tiered TransitNetwork with basenetworkpath %s: no networkName specified" + % basenetworkpath + ) # for CHAMP and TM2, transit lines are here if self.modelType in [Network.MODEL_TYPE_CHAMP, Network.MODEL_TYPE_TM2]: - for filename in glob.glob(os.path.join(basenetworkpath, networkName + ".*")): + for filename in glob.glob( + os.path.join(basenetworkpath, networkName + ".*") + ): suffix = filename.rsplit(".")[-1].lower() - if suffix in ["lin","link","pnr","zac","access","xfer"]: + if suffix in ["lin", "link", "pnr", "zac", "access", "xfer"]: self.parseFile(filename) # this doesn't have to match the network name @@ -86,17 +127,20 @@ def __init__(self, modelType, modelVersion, basenetworkpath=None, networkBaseDir elif self.modelType in [Network.MODEL_TYPE_TM1]: # read the the block file to find the line filenames if it exists - block_filename = os.path.join(basenetworkpath, "transit_lines", networkName + ".block") + block_filename = os.path.join( + basenetworkpath, "transit_lines", networkName + ".block" + ) line_filenames = [] - flat_dirs = False + flat_dirs = False if os.path.exists(block_filename): WranglerLogger.info("Reading {}".format(block_filename)) file_re = re.compile(r"^\s*read\s+file\s*=\s*trn[\\](\S*)$") - block_file = open(block_filename,"r") + block_file = open(block_filename, "r") for line in block_file: result = re.match(file_re, line) - if result: line_filenames.append(result.group(1)) + if result: + line_filenames.append(result.group(1)) block_file.close() else: # if it doesn't exist, assume networkName.lin and flat file structure @@ -108,9 +152,15 @@ def __init__(self, modelType, modelVersion, basenetworkpath=None, networkBaseDir # read those line files for filename in line_filenames: if flat_dirs: - self.parseFile(os.path.join(basenetworkpath, filename), insert_replace=False) + self.parseFile( + os.path.join(basenetworkpath, filename), + insert_replace=False, + ) else: - self.parseFile(os.path.join(basenetworkpath, "transit_lines", filename), insert_replace=False) + self.parseFile( + os.path.join(basenetworkpath, "transit_lines", filename), + insert_replace=False, + ) # now the rest if flat_dirs: @@ -137,31 +187,37 @@ def __init__(self, modelType, modelVersion, basenetworkpath=None, networkBaseDir fullfarefile = os.path.join(basenetworkpath, farefile) # try transit_fares subdir if it's not here if not os.path.exists(fullfarefile): - fullfarefile = os.path.join(basenetworkpath, "transit_fares", farefile) + fullfarefile = os.path.join( + basenetworkpath, "transit_fares", farefile + ) - if modelType==Network.MODEL_TYPE_TM2: + if modelType == Network.MODEL_TYPE_TM2: suffix = farefile.rsplit(".")[-1].lower() - if suffix=="far": + if suffix == "far": # parse TM2 fare files self.parseFile(fullfarefile) WranglerLogger.info("Read {}".format(fullfarefile)) else: # fare zone matrix files are just numbers - Faresystem.readFareZoneMatrixFile(fullfarefile, self.faresystems) + Faresystem.readFareZoneMatrixFile( + fullfarefile, self.faresystems + ) else: linecount = 0 # WranglerLogger.debug("cwd=%s farefile %s exists? %d" % (os.getcwd(), fullfarefile, os.path.exists(fullfarefile))) if os.path.exists(fullfarefile): - infile = open(fullfarefile, 'r') + infile = open(fullfarefile, "r") lines = infile.readlines() self.farefiles[farefile].extend(lines) linecount = len(lines) infile.close() - WranglerLogger.debug("Read %5d lines from fare file %s" % (linecount, fullfarefile)) + WranglerLogger.debug( + "Read %5d lines from fare file %s" % (linecount, fullfarefile) + ) def __iter__(self): """ @@ -181,39 +237,46 @@ def __next__(self): """ - if self.currentLineIdx >= len(self.lines): # are we out of lines? + if self.currentLineIdx >= len(self.lines): # are we out of lines? raise StopIteration - while not isinstance(self.lines[self.currentLineIdx],TransitLine): + while not isinstance(self.lines[self.currentLineIdx], TransitLine): self.currentLineIdx += 1 if self.currentLineIdx >= len(self.lines): raise StopIteration self.currentLineIdx += 1 - return self.lines[self.currentLineIdx-1] + return self.lines[self.currentLineIdx - 1] # python 2 backwards compat next = __next__ def __repr__(self): - return "TransitNetwork: %s lines, %s links, %s PNRs, %s ZACs" % (len(self.lines),len(self.links),len(self.pnrs),len(self.zacs)) + return "TransitNetwork: %s lines, %s links, %s PNRs, %s ZACs" % ( + len(self.lines), + len(self.links), + len(self.pnrs), + len(self.zacs), + ) def isEmpty(self): """ TODO: could be smarter here and check that there are no non-comments since those don't really count. """ - if (len(self.lines) == 0 and - len(self.links) == 0 and - len(self.pnrs) == 0 and - len(self.zacs) == 0 and - len(self.accessli) == 0 and - len(self.xferli) == 0): + if ( + len(self.lines) == 0 + and len(self.links) == 0 + and len(self.pnrs) == 0 + and len(self.zacs) == 0 + and len(self.accessli) == 0 + and len(self.xferli) == 0 + ): return True - + return False - + def clear(self, projectstr): """ Clears out all network data to prep for a project apply, e.g. the MuniTEP project is a complete @@ -224,21 +287,27 @@ def clear(self, projectstr): if self.isEmpty(): # nothing to do! return - + query = "Clearing network for %s:\n" % projectstr - query += " %d lines, %d links, %d pnrs, %d zacs, %d accessli, %d xferli\n" % (len(self.lines), - len(self.links), len(self.pnrs), len(self.zacs), len(self.accessli), len(self.xferli)) + query += " %d lines, %d links, %d pnrs, %d zacs, %d accessli, %d xferli\n" % ( + len(self.lines), + len(self.links), + len(self.pnrs), + len(self.zacs), + len(self.accessli), + len(self.xferli), + ) query += "Is this ok? (y/n) " WranglerLogger.debug(query) try: - response = raw_input("") # python 2 + response = raw_input("") # python 2 except: - response = input("") # python 3 + response = input("") # python 3 WranglerLogger.debug("response=[%s]" % response) if response != "Y" and response != "y": exit(0) - + del self.lines[:] del self.links[:] self.pnrs.clear() @@ -262,7 +331,8 @@ def validateFrequencies(self): # For each line for line in self: - if not isinstance(line,TransitLine): continue + if not isinstance(line, TransitLine): + continue freqs = line.getFreqs() nonzero_found = False @@ -271,8 +341,10 @@ def validateFrequencies(self): nonzero_found = True break - if nonzero_found==False: - raise NetworkException('Lines {} has only zero frequencies'.format(line.name)) + if nonzero_found == False: + raise NetworkException( + "Lines {} has only zero frequencies".format(line.name) + ) def validateWnrsAndPnrs(self): """ @@ -281,77 +353,90 @@ def validateWnrsAndPnrs(self): Pretty verbose in the debug log. """ WranglerLogger.debug("Validating Off Street Transit Node Connections") - - nodeInfo = {} # lineset => { station node => { xfer node => [ walk node, pnr node ] }} - setToModeType = {} # lineset => list of ModeTypes ("Local", etc) - setToOffstreet = {} # lineset => True if has offstreet nodes - doneNodes = set() - - critical_found = False + + nodeInfo = ( + {} + ) # lineset => { station node => { xfer node => [ walk node, pnr node ] }} + setToModeType = {} # lineset => list of ModeTypes ("Local", etc) + setToOffstreet = {} # lineset => True if has offstreet nodes + doneNodes = set() + + critical_found = False # For each line for line in self: - if not isinstance(line,TransitLine): continue + if not isinstance(line, TransitLine): + continue # print "validating", line - + lineset = line.name[0:3] if lineset not in nodeInfo: - nodeInfo[lineset] = {} - setToModeType[lineset] = [] + nodeInfo[lineset] = {} + setToModeType[lineset] = [] setToOffstreet[lineset] = False if line.getModeType(self.modelType) not in setToModeType[lineset]: setToModeType[lineset].append(line.getModeType(self.modelType)) - setToOffstreet[lineset] = (setToOffstreet[lineset] or line.hasOffstreetNodes(self.modelType)) - + setToOffstreet[lineset] = setToOffstreet[ + lineset + ] or line.hasOffstreetNodes(self.modelType) + # for each stop for stopIdx in range(len(line.n)): - if not line.n[stopIdx].isStop(): continue - + if not line.n[stopIdx].isStop(): + continue + stopNodeStr = line.n[stopIdx].num wnrNodes = set() pnrNodes = set() - - if stopNodeStr in nodeInfo[lineset]: continue + + if stopNodeStr in nodeInfo[lineset]: + continue nodeInfo[lineset][stopNodeStr] = {} - - #print " check if we have access to an on-street node" + + # print " check if we have access to an on-street node" for link in self.xferli: - if not isinstance(link,Linki): continue + if not isinstance(link, Linki): + continue # This xfer links the node to the on-street network if link.A == stopNodeStr: - nodeInfo[lineset][stopNodeStr][link.B] = ["-","-"] + nodeInfo[lineset][stopNodeStr][link.B] = ["-", "-"] elif link.B == stopNodeStr: - nodeInfo[lineset][stopNodeStr][link.A] = ["-","-"] - - #print " Check for WNR" + nodeInfo[lineset][stopNodeStr][link.A] = ["-", "-"] + + # print " Check for WNR" for zac in self.zacs: - if not isinstance(zac,ZACLink): continue - + if not isinstance(zac, ZACLink): + continue + m = re.match(nodepair_pattern, zac.id) - if m.group(1)==stopNodeStr: + if m.group(1) == stopNodeStr: # this one is invalid for TM1 if self.modelType in [Network.MODEL_TYPE_TM1]: - errorstr = "ZONEACCESS link should be funnel-stop but stop-funnel found: {}".format(zac) + errorstr = "ZONEACCESS link should be funnel-stop but stop-funnel found: {}".format( + zac + ) WranglerLogger.critical(errorstr) critical_found = True else: wnrNodes.add(int(m.group(2))) - if m.group(2)==stopNodeStr: + if m.group(2) == stopNodeStr: wnrNodes.add(int(m.group(1))) - #print "Check for PNR" + # print "Check for PNR" for pnr_filename in self.pnrs.keys(): for pnr in self.pnrs[pnr_filename]: - if not isinstance(pnr, PNRLink): continue + if not isinstance(pnr, PNRLink): + continue pnr.parseID() - if pnr.station==stopNodeStr and pnr.pnr!=PNRLink.UNNUMBERED: + if pnr.station == stopNodeStr and pnr.pnr != PNRLink.UNNUMBERED: pnrNodes.add(int(pnr.pnr)) - #print "Check that our access links go from an onstreet xfer to a pnr or to a wnr" + # print "Check that our access links go from an onstreet xfer to a pnr or to a wnr" for link in self.accessli: - if not isinstance(link,Linki): continue + if not isinstance(link, Linki): + continue try: if int(link.A) in wnrNodes: nodeInfo[lineset][stopNodeStr][link.B][0] = link.A @@ -363,93 +448,125 @@ def validateWnrsAndPnrs(self): nodeInfo[lineset][stopNodeStr][link.A][1] = link.B except KeyError: # if it's not offstreet then that's ok - if not setToOffstreet[lineset]: continue - - errorstr = "Invalid access link found in %s lineset %s (incl offstreet) stopNode %s -- Missing xfer? A=%s B=%s, xfernodes=%s wnrNodes=%s pnrNodes=%s" % \ - (line.getModeType(self.modelType), lineset, stopNodeStr, link.A, link.B, str(nodeInfo[lineset][stopNodeStr].keys()), str(wnrNodes), str(pnrNodes)) + if not setToOffstreet[lineset]: + continue + + errorstr = ( + "Invalid access link found in %s lineset %s (incl offstreet) stopNode %s -- Missing xfer? A=%s B=%s, xfernodes=%s wnrNodes=%s pnrNodes=%s" + % ( + line.getModeType(self.modelType), + lineset, + stopNodeStr, + link.A, + link.B, + str(nodeInfo[lineset][stopNodeStr].keys()), + str(wnrNodes), + str(pnrNodes), + ) + ) WranglerLogger.warning(errorstr) # raise NetworkException(errorstr) - + nodeNames = {} if "CHAMP_node_names" in os.environ: book = xlrd.open_workbook(os.environ["CHAMP_node_names"]) sh = book.sheet_by_index(0) - for rx in range(0,sh.nrows): # skip header + for rx in range(0, sh.nrows): # skip header therow = sh.row(rx) nodeNames[int(therow[0].value)] = therow[1].value # WranglerLogger.info(str(nodeNames)) - + # print it all out for lineset in nodeInfo.keys(): stops = list(nodeInfo[lineset].keys()) stops.sort() - - WranglerLogger.debug("--------------- Line set %s %s -- hasOffstreet? %s------------------" % - (lineset, str(setToModeType[lineset]), str(setToOffstreet[lineset]))) - WranglerLogger.debug("%-40s %10s %10s %10s %10s" % ("stopname", "stop", "xfer", "wnr", "pnr")) + + WranglerLogger.debug( + "--------------- Line set %s %s -- hasOffstreet? %s------------------" + % (lineset, str(setToModeType[lineset]), str(setToOffstreet[lineset])) + ) + WranglerLogger.debug( + "%-40s %10s %10s %10s %10s" % ("stopname", "stop", "xfer", "wnr", "pnr") + ) for stopNodeStr in stops: numWnrs = 0 stopname = "Unknown stop name" - if int(stopNodeStr) in nodeNames: stopname = nodeNames[int(stopNodeStr)] + if int(stopNodeStr) in nodeNames: + stopname = nodeNames[int(stopNodeStr)] for xfernode in nodeInfo[lineset][stopNodeStr].keys(): - WranglerLogger.debug("%-40s %10s %10s %10s %10s" % - (stopname, stopNodeStr, xfernode, - nodeInfo[lineset][stopNodeStr][xfernode][0], - nodeInfo[lineset][stopNodeStr][xfernode][1])) - if nodeInfo[lineset][stopNodeStr][xfernode][0] != "-": numWnrs += 1 - + WranglerLogger.debug( + "%-40s %10s %10s %10s %10s" + % ( + stopname, + stopNodeStr, + xfernode, + nodeInfo[lineset][stopNodeStr][xfernode][0], + nodeInfo[lineset][stopNodeStr][xfernode][1], + ) + ) + if nodeInfo[lineset][stopNodeStr][xfernode][0] != "-": + numWnrs += 1 + if numWnrs == 0 and setToOffstreet[lineset]: - errorstr = "Zero wnrNodes or onstreetxfers for stop %s!" % stopNodeStr + errorstr = ( + "Zero wnrNodes or onstreetxfers for stop %s!" % stopNodeStr + ) WranglerLogger.critical(errorstr) critical_found = True if critical_found: raise NetworkException("Critical errors found") - + def line(self, name): """ If a string is passed in, return the line for that name exactly (a :py:class:`TransitLine` object). If a regex, return all relevant lines (a list of TransitLine objects). If 'all', return all lines (a list of TransitLine objects). """ - if isinstance(name,str): + if isinstance(name, str): if name in self.lines: return self.lines[self.lines.index(name)] - if str(type(name))==str(type(re.compile("."))): + if str(type(name)) == str(type(re.compile("."))): toret = [] for i in range(len(self.lines)): - if isinstance(self.lines[i],str): continue - if name.match(self.lines[i].name): toret.append(self.lines[i]) + if isinstance(self.lines[i], str): + continue + if name.match(self.lines[i].name): + toret.append(self.lines[i]) return toret - if name=='all': + if name == "all": allLines = [] for i in range(len(self.lines)): allLines.append(self.lines[i]) return allLines - raise NetworkException('Line name not found: %s' % (name,)) + raise NetworkException("Line name not found: %s" % (name,)) def deleteLine(self, name): """ If a string is passed in, delete the line for that name exactly. (Throws an exception of it's not a line name) If a regex, delete all the lines that match, debug-logging the deleted line names. """ - if isinstance(name,str): + if isinstance(name, str): del self.lines[self.lines.index(name)] return - if str(type(name))==str(type(re.compile("."))): - for idx in range(len(self.lines)-1,-1,-1): # go backwards - if isinstance(self.lines[idx],str): continue + if str(type(name)) == str(type(re.compile("."))): + for idx in range(len(self.lines) - 1, -1, -1): # go backwards + if isinstance(self.lines[idx], str): + continue if name.match(self.lines[idx].name): - WranglerLogger.debug("Deleting line {}".format(self.lines[idx].name)) + WranglerLogger.debug( + "Deleting line {}".format(self.lines[idx].name) + ) del self.lines[idx] return # didn't understand name argument - raise NetworkException("deleteLine() didn't understand name argument [{}]".format(name)) - + raise NetworkException( + "deleteLine() didn't understand name argument [{}]".format(name) + ) def deleteLinkForNodes(self, nodeA, nodeB, include_reverse=True): """ @@ -458,17 +575,22 @@ def deleteLinkForNodes(self, nodeA, nodeB, include_reverse=True): Returns number of links deleted. """ del_idxs = [] - for idx in range(len(self.links)-1,-1,-1): # go backwards - if not isinstance(self.links[idx],TransitLink): continue + for idx in range(len(self.links) - 1, -1, -1): # go backwards + if not isinstance(self.links[idx], TransitLink): + continue if self.links[idx].Anode == nodeA and self.links[idx].Bnode == nodeB: del_idxs.append(idx) - elif include_reverse and self.links[idx].Anode == nodeB and self.links[idx].Bnode == nodeA: + elif ( + include_reverse + and self.links[idx].Anode == nodeB + and self.links[idx].Bnode == nodeA + ): del_idxs.append(idx) for del_idx in del_idxs: WranglerLogger.debug("Removing link %s" % str(self.links[del_idx])) del self.links[del_idx] - + return len(del_idxs) def numPNRLinks(self): @@ -478,11 +600,11 @@ def numPNRLinks(self): num_pnr_links = 0 for pnr_file in self.pnrs.keys(): for pnr_link in self.pnrs[pnr_file]: - if not isinstance(pnr_link, PNRLink): continue + if not isinstance(pnr_link, PNRLink): + continue num_pnr_links += 1 return num_pnr_links - def deletePNRLinkForId(self, pnr_id): """ Delete the PNRLink with the given id. @@ -490,18 +612,21 @@ def deletePNRLinkForId(self, pnr_id): for pnr_file in self.pnrs.keys(): del_idxs = [] # find pnr links to delete - for idx in range(len(self.pnrs[pnr_file])-1,-1,-1): # go backwards - if not isinstance(self.pnrs[pnr_file][idx], PNRLink): continue + for idx in range(len(self.pnrs[pnr_file]) - 1, -1, -1): # go backwards + if not isinstance(self.pnrs[pnr_file][idx], PNRLink): + continue if self.pnrs[pnr_file][idx].id == pnr_id: del_idxs.append(idx) # delete them for del_idx in del_idxs: - WranglerLogger.debug("Removing PNR link {} from {}".format(self.pnrs[pnr_file][del_idx], pnr_file)) + WranglerLogger.debug( + "Removing PNR link {} from {}".format( + self.pnrs[pnr_file][del_idx], pnr_file + ) + ) del self.pnrs[pnr_file][del_idx] - - def deleteAccessXferLinkForNode(self, nodenum, access_links=True, xfer_links=True): """ Delete any Linki in self.accessli (if access_links) and/or self.xferli (if xfer_links) @@ -510,29 +635,43 @@ def deleteAccessXferLinkForNode(self, nodenum, access_links=True, xfer_links=Tru """ del_acc_idxs = [] if access_links: - for idx in range(len(self.accessli)-1,-1,-1): # go backwards - if not isinstance(self.accessli[idx],Linki): continue - if int(self.accessli[idx].A) == nodenum or int(self.accessli[idx].B) == nodenum: + for idx in range(len(self.accessli) - 1, -1, -1): # go backwards + if not isinstance(self.accessli[idx], Linki): + continue + if ( + int(self.accessli[idx].A) == nodenum + or int(self.accessli[idx].B) == nodenum + ): del_acc_idxs.append(idx) - + for del_idx in del_acc_idxs: - WranglerLogger.debug("Removing access link %s" % str(self.accessli[del_idx])) + WranglerLogger.debug( + "Removing access link %s" % str(self.accessli[del_idx]) + ) del self.accessli[del_idx] del_xfer_idxs = [] if xfer_links: - for idx in range(len(self.xferli)-1,-1,-1): # go backwards - if not isinstance(self.xferli[idx],Linki): continue - if int(self.xferli[idx].A) == nodenum or int(self.xferli[idx].B) == nodenum: + for idx in range(len(self.xferli) - 1, -1, -1): # go backwards + if not isinstance(self.xferli[idx], Linki): + continue + if ( + int(self.xferli[idx].A) == nodenum + or int(self.xferli[idx].B) == nodenum + ): del_xfer_idxs.append(idx) - + for del_idx in del_xfer_idxs: - WranglerLogger.debug("Removing xfere link %s" % str(self.xferli[del_idx])) + WranglerLogger.debug( + "Removing xfere link %s" % str(self.xferli[del_idx]) + ) del self.xferli[del_idx] - + return len(del_acc_idxs) + len(del_xfer_idxs) - - def splitLinkInTransitLines(self,nodeA,nodeB,newNode,stop=False,verboseLog=True): + + def splitLinkInTransitLines( + self, nodeA, nodeB, newNode, stop=False, verboseLog=True + ): """ Goes through each line and for any with links going from *nodeA* to *nodeB*, inserts the *newNode* in between them (as a stop if *stop* is True). @@ -541,74 +680,87 @@ def splitLinkInTransitLines(self,nodeA,nodeB,newNode,stop=False,verboseLog=True) lines_split = [] totReplacements = 0 for line in self: - if line.hasLink(nodeA,nodeB): - line.splitLink(nodeA,nodeB,newNode,stop=stop,verboseLog=verboseLog) - totReplacements+=1 + if line.hasLink(nodeA, nodeB): + line.splitLink(nodeA, nodeB, newNode, stop=stop, verboseLog=verboseLog) + totReplacements += 1 lines_split.append(line.name) # log only if instructed instructed - if verboseLog: WranglerLogger.debug("Total Lines with Link %s-%s split:%d" % (str(nodeA),str(nodeB),totReplacements)) + if verboseLog: + WranglerLogger.debug( + "Total Lines with Link %s-%s split:%d" + % (str(nodeA), str(nodeB), totReplacements) + ) return lines_split - - def replaceSegmentInTransitLines(self,nodeA,nodeB,newNodes): + + def replaceSegmentInTransitLines(self, nodeA, nodeB, newNodes): """ *newNodes* should include nodeA and nodeB if they are not going away """ totReplacements = 0 - allExp=re.compile(".") - newSection=newNodes # [nodeA]+newNodes+[nodeB] + allExp = re.compile(".") + newSection = newNodes # [nodeA]+newNodes+[nodeB] for line in self.line(allExp): - if line.hasSegment(nodeA,nodeB): + if line.hasSegment(nodeA, nodeB): WranglerLogger.debug(line.name) - line.replaceSegment(nodeA,nodeB,newSection) - totReplacements+=1 - WranglerLogger.debug("Total Lines with Segment %s-%s replaced:%d" % (str(nodeA),str(nodeB),totReplacements)) + line.replaceSegment(nodeA, nodeB, newSection) + totReplacements += 1 + WranglerLogger.debug( + "Total Lines with Segment %s-%s replaced:%d" + % (str(nodeA), str(nodeB), totReplacements) + ) def setCombiFreqsForShortLine(self, shortLine, longLine, combFreqs): """ - Set all five headways for a short line to equal a combined + Set all five headways for a short line to equal a combined headway including long line. i.e. set 1-California Short frequencies by inputing the combined frequencies of both lines. - + .. note:: Make sure *longLine* frequencies are set first! """ try: - longLineInst=self.line(longLine) + longLineInst = self.line(longLine) except: - raise NetworkException('Unknown Route! %s' % (longLine)) - try: - shortLineInst=self.line(shortLine) + raise NetworkException("Unknown Route! %s" % (longLine)) + try: + shortLineInst = self.line(shortLine) except: - raise NetworkException('Unknown Route! %s' % (shortLine)) + raise NetworkException("Unknown Route! %s" % (shortLine)) [tp1Long, tp2Long, tp3Long, tp4Long, tp5Long] = longLineInst.getFreqs() [tp1Comb, tp2Comb, tp3Comb, tp4Comb, tp5Comb] = combFreqs - [tp1Short,tp2Short,tp3Short,tp4Short,tp5Short] = [0,0,0,0,0] - if (tp1Long-tp1Comb)>0: tp1Short=tp1Comb*tp1Long/(tp1Long-tp1Comb) - if (tp2Long-tp2Comb)>0: tp2Short=tp2Comb*tp2Long/(tp2Long-tp2Comb) - if (tp3Long-tp3Comb)>0: tp3Short=tp3Comb*tp3Long/(tp3Long-tp3Comb) - if (tp4Long-tp4Comb)>0: tp4Short=tp4Comb*tp4Long/(tp4Long-tp4Comb) - if (tp5Long-tp5Comb)>0: tp5Short=tp5Comb*tp5Long/(tp5Long-tp5Comb) - shortLineInst.setFreqs([tp1Short,tp2Short,tp3Short,tp4Short,tp5Short]) - - + [tp1Short, tp2Short, tp3Short, tp4Short, tp5Short] = [0, 0, 0, 0, 0] + if (tp1Long - tp1Comb) > 0: + tp1Short = tp1Comb * tp1Long / (tp1Long - tp1Comb) + if (tp2Long - tp2Comb) > 0: + tp2Short = tp2Comb * tp2Long / (tp2Long - tp2Comb) + if (tp3Long - tp3Comb) > 0: + tp3Short = tp3Comb * tp3Long / (tp3Long - tp3Comb) + if (tp4Long - tp4Comb) > 0: + tp4Short = tp4Comb * tp4Long / (tp4Long - tp4Comb) + if (tp5Long - tp5Comb) > 0: + tp5Short = tp5Comb * tp5Long / (tp5Long - tp5Comb) + shortLineInst.setFreqs([tp1Short, tp2Short, tp3Short, tp4Short, tp5Short]) + def getCombinedFreq(self, names, coverage_set=False): """ Pass a regex pattern, we'll show the combined frequency. This doesn't change anything, it's just a useful tool. """ lines = self.line(names) - denom = [0,0,0,0,0] + denom = [0, 0, 0, 0, 0] for l in lines: - if coverage_set: coverage_set.discard(l.name) + if coverage_set: + coverage_set.discard(l.name) freqs = l.getFreqs() for t in range(5): - if float(freqs[t])>0.0: - denom[t] += 1/float(freqs[t]) - - combined = [0,0,0,0,0] + if float(freqs[t]) > 0.0: + denom[t] += 1 / float(freqs[t]) + + combined = [0, 0, 0, 0, 0] for t in range(5): - if denom[t] > 0: combined[t] = round(1/denom[t],2) + if denom[t] > 0: + combined[t] = round(1 / denom[t], 2) return combined def getValueFromXfare(self, fare_filename, from_mode, to_mode): @@ -624,16 +776,20 @@ def getValueFromXfare(self, fare_filename, from_mode, to_mode): for line in self.farefiles[fare_filename]: # WranglerLogger.debug("getValueFromXfare() line = {}".format(line)) result = xfare_re.match(line) - if result == None: continue + if result == None: + continue my_from_mode = int(result.group(1)) - if my_from_mode != from_mode: continue + if my_from_mode != from_mode: + continue my_to_mode_strings = result.group(2).split(",") my_to_modes = [int(x) for x in my_to_mode_strings] # WranglerLogger.debug("getValueFromXfare my_to_modes={}".format(my_to_modes)) if len(my_to_modes) < to_mode: - raise NetworkException("to_mode {} not found: {}".format(to_mode, my_to_modes)) - return my_to_modes[to_mode-1] # index starts at zero + raise NetworkException( + "to_mode {} not found: {}".format(to_mode, my_to_modes) + ) + return my_to_modes[to_mode - 1] # index starts at zero raise NetworkException("from_mode {} not found".format(from_mode)) @@ -652,21 +808,25 @@ def setValueToXfare(self, fare_filename, from_mode, to_mode, value): # WranglerLogger.debug("getValueFromXfare() line = {}".format(line)) result = xfare_re.match(line) - if result == None: continue + if result == None: + continue my_from_mode = int(result.group(2)) - if my_from_mode != from_mode: continue + if my_from_mode != from_mode: + continue my_to_mode_strings = result.group(3).split(",") my_to_modes = [int(x) for x in my_to_mode_strings] # WranglerLogger.debug("getValueFromXfare my_to_modes={}".format(my_to_modes)) if len(my_to_modes) < to_mode: - raise NetworkException("to_mode {} not found: {}".format(to_mode, my_to_modes)) - - my_to_modes[to_mode-1] = value # index starts at zero + raise NetworkException( + "to_mode {} not found: {}".format(to_mode, my_to_modes) + ) + + my_to_modes[to_mode - 1] = value # index starts at zero # put the line back together my_to_mode_strings = [str(x) for x in my_to_modes] - comma_str = ',' - line = "{}{}\n".format(result.group(1),comma_str.join(my_to_mode_strings)) + comma_str = "," + line = "{}{}\n".format(result.group(1), comma_str.join(my_to_mode_strings)) self.farefiles[fare_filename][line_idx] = line return @@ -685,41 +845,55 @@ def verifyTransitLineFrequencies(self, frequencies, coverage=None): if coverage: covpattern = re.compile(coverage) for i in range(len(self.lines)): - if isinstance(self.lines[i],str): continue - if covpattern.match(self.lines[i].name): covset.add(self.lines[i].name) + if isinstance(self.lines[i], str): + continue + if covpattern.match(self.lines[i].name): + covset.add(self.lines[i].name) # print covset - - labels = frequencies.keys(); labels.sort() + + labels = frequencies.keys() + labels.sort() for label in labels: logstr = "Verifying %-40s: " % label - - for regexnum in [0,1]: - frequencies[label][regexnum]=frequencies[label][regexnum].strip() - if frequencies[label][regexnum]=="": continue + + for regexnum in [0, 1]: + frequencies[label][regexnum] = frequencies[label][regexnum].strip() + if frequencies[label][regexnum] == "": + continue pattern = re.compile(frequencies[label][regexnum]) freqs = self.getCombinedFreq(pattern, coverage_set=covset) - if freqs[0]+freqs[1]+freqs[2]+freqs[3]+freqs[4]==0: - logstr += "-- Found no matching lines for pattern [%s]" % (frequencies[label][regexnum]) + if freqs[0] + freqs[1] + freqs[2] + freqs[3] + freqs[4] == 0: + logstr += "-- Found no matching lines for pattern [%s]" % ( + frequencies[label][regexnum] + ) for timeperiod in range(5): - if abs(freqs[timeperiod]-frequencies[label][2][timeperiod])>0.2: + if abs(freqs[timeperiod] - frequencies[label][2][timeperiod]) > 0.2: logstr += "-- Mismatch. Desired %s" % str(frequencies[label][2]) - logstr += "but got ",str(freqs) + logstr += "but got ", str(freqs) lines = self.line(pattern) WranglerLogger.error(logstr) WranglerLogger.error("Problem lines:") - for line in lines: WranglerLogger.error(str(line)) + for line in lines: + WranglerLogger.error(str(line)) raise NetworkException("Mismatching frequency") - logstr += "-- Match%d!" % (regexnum+1) + logstr += "-- Match%d!" % (regexnum + 1) WranglerLogger.debug(logstr) - + if coverage: WranglerLogger.debug("Found %d uncovered lines" % len(covset)) for linename in covset: WranglerLogger.debug(self.line(linename)) - - def write(self, path='.', name='transit', writeEmptyFiles=True, suppressQuery=False, suppressValidation=False, - cubeNetFileForValidation=None, line_only=False): + def write( + self, + path=".", + name="transit", + writeEmptyFiles=True, + suppressQuery=False, + suppressValidation=False, + cubeNetFileForValidation=None, + line_only=False, + ): """ Write out this full transit network to disk in path specified. """ @@ -727,26 +901,31 @@ def write(self, path='.', name='transit', writeEmptyFiles=True, suppressQuery=Fa self.validateFrequencies() self.validateWnrsAndPnrs() - + if not cubeNetFileForValidation: - WranglerLogger.fatal("Trying to validate TransitNetwork but cubeNetFileForValidation not passed") + WranglerLogger.fatal( + "Trying to validate TransitNetwork but cubeNetFileForValidation not passed" + ) exit(2) - + self.checkValidityOfLinks(cubeNetFile=cubeNetFileForValidation) - if not os.path.exists(path): WranglerLogger.debug("\nPath [%s] doesn't exist; creating." % path) os.mkdir(path) else: - trnfile = os.path.join(path,name+".lin") + trnfile = os.path.join(path, name + ".lin") if os.path.exists(trnfile) and not suppressQuery: - print("File [{}] exists already. Overwrite contents? (y/n/s) ".format(trnfile)) + print( + "File [{}] exists already. Overwrite contents? (y/n/s) ".format( + trnfile + ) + ) try: - response = raw_input("") # python 2 + response = raw_input("") # python 2 except: - response = input("") # python 3 + response = input("") # python 3 WranglerLogger.debug("response = [%s]" % response) if response == "s" or response == "S": WranglerLogger.debug("Skipping!") @@ -757,31 +936,39 @@ def write(self, path='.', name='transit', writeEmptyFiles=True, suppressQuery=Fa WranglerLogger.info("Writing into %s\\%s" % (path, name)) logstr = "" - if len(self.lines)>0 or writeEmptyFiles: + if len(self.lines) > 0 or writeEmptyFiles: # for verifying uniqueness of line names line_names = set() logstr += " lines" - f = open(os.path.join(path,name+".lin"), 'w'); + f = open(os.path.join(path, name + ".lin"), "w") if self.program == TransitParser.PROGRAM_TRNBUILD: f.write(";;<>;;\n") elif self.program == TransitParser.PROGRAM_PT: f.write(";;<><>;;\n") for line in self.lines: - if isinstance(line,str): + if isinstance(line, str): f.write(line) else: # write it first - f.write(repr(line)+"\n") + f.write(repr(line) + "\n") # Cube TRNBUILD documentation for LINE NAME # It may be up to 12 characters in length, and must be unique. if line.name.upper() in line_names: - raise NetworkException("Line name {} not unique".format(line.name)) + raise NetworkException( + "Line name {} not unique".format(line.name) + ) if len(line.name) > 12: - raise NetworkException("Line name {} too long".format(line.name)) + raise NetworkException( + "Line name {} too long".format(line.name) + ) if line.hasDuplicateStops(): - raise NetworkException("Line {} has a stop that occurs more than once".format(line.name)) + raise NetworkException( + "Line {} has a stop that occurs more than once".format( + line.name + ) + ) line_names.add(line.name.upper()) f.close() @@ -791,14 +978,14 @@ def write(self, path='.', name='transit', writeEmptyFiles=True, suppressQuery=Fa WranglerLogger.info("") return - if len(self.links)>0 or writeEmptyFiles: + if len(self.links) > 0 or writeEmptyFiles: logstr += " links" - f = open(os.path.join(path,name+".link"), 'w'); + f = open(os.path.join(path, name + ".link"), "w") for link in self.links: - f.write(str(link)+"\n") + f.write(str(link) + "\n") f.close() - if len(self.pnrs)>0 or writeEmptyFiles: + if len(self.pnrs) > 0 or writeEmptyFiles: for pnr_file in self.pnrs.keys(): logstr += " {}_pnr".format(pnr_file) @@ -806,46 +993,46 @@ def write(self, path='.', name='transit', writeEmptyFiles=True, suppressQuery=Fa if pnr_file.startswith(name): pnr_out_file = "{}.pnr".format(pnr_file) else: - pnr_out_file = "{}_{}.pnr".format(name,pnr_file) + pnr_out_file = "{}_{}.pnr".format(name, pnr_file) - f = open(os.path.join(path,pnr_out_file),'a') + f = open(os.path.join(path, pnr_out_file), "a") for pnr in self.pnrs[pnr_file]: - f.write(str(pnr)+"\n") + f.write(str(pnr) + "\n") f.close() - if len(self.zacs)>0 or writeEmptyFiles: + if len(self.zacs) > 0 or writeEmptyFiles: logstr += " zac" - f = open(os.path.join(path,name+".zac"), 'w'); + f = open(os.path.join(path, name + ".zac"), "w") for zac in self.zacs: - f.write(str(zac)+"\n") + f.write(str(zac) + "\n") f.close() - if len(self.accessli)>0 or writeEmptyFiles: + if len(self.accessli) > 0 or writeEmptyFiles: logstr += " access" - f = open(os.path.join(path,name+".access"), 'w'); + f = open(os.path.join(path, name + ".access"), "w") for accessli in self.accessli: - f.write(str(accessli)+"\n") + f.write(str(accessli) + "\n") f.close() - - if len(self.xferli)>0 or writeEmptyFiles: + + if len(self.xferli) > 0 or writeEmptyFiles: logstr += " xfer" - f = open(os.path.join(path,name+".xfer"), 'w'); + f = open(os.path.join(path, name + ".xfer"), "w") for xferli in self.xferli: - f.write(str(xferli)+"\n") + f.write(str(xferli) + "\n") f.close() - if len(self.nodes)>0 or writeEmptyFiles: + if len(self.nodes) > 0 or writeEmptyFiles: logstr += " nodes" - f = open(os.path.join(path,"Transit_Support_Nodes.dat"), 'w'); + f = open(os.path.join(path, "Transit_Support_Nodes.dat"), "w") for nodes in self.nodes: - f.write(str(nodes)+"\n") + f.write(str(nodes) + "\n") f.close() - if len(self.supps)>0 or writeEmptyFiles: + if len(self.supps) > 0 or writeEmptyFiles: logstr += " supps" - f = open(os.path.join(path,"WALK_access.sup"), 'w'); + f = open(os.path.join(path, "WALK_access.sup"), "w") for supplink in self.supps: - f.write(str(supplink)+"\n") + f.write(str(supplink) + "\n") f.close() # fares @@ -854,15 +1041,17 @@ def write(self, path='.', name='transit', writeEmptyFiles=True, suppressQuery=Fa for farefile in TransitNetwork.FARE_FILES[self.modelType]: # don't write an empty one unless there isn't anything there if len(self.farefiles[farefile]) == 0: - if writeEmptyFiles and not os.path.exists(os.path.join(path,farefile)): + if writeEmptyFiles and not os.path.exists( + os.path.join(path, farefile) + ): logstr += " " + farefile - f = open(os.path.join(path,farefile), 'w') + f = open(os.path.join(path, farefile), "w") f.write("; no fares known\n") f.close() - + else: logstr += " " + farefile - f = open(os.path.join(path,farefile), 'w') + f = open(os.path.join(path, farefile), "w") for line in self.farefiles[farefile]: f.write(line) f.close() @@ -870,19 +1059,20 @@ def write(self, path='.', name='transit', writeEmptyFiles=True, suppressQuery=Fa if len(self.faresystems) > 0 or writeEmptyFiles: logstr += " faresystem" # fare and farematrix files - f = open(os.path.join(path,name+".far"), 'w') - f2 = open(os.path.join(path, name+"_farematrix.txt"), 'w') + f = open(os.path.join(path, name + ".far"), "w") + f2 = open(os.path.join(path, name + "_farematrix.txt"), "w") for fare_id in sorted(self.faresystems.keys()): - f.write(str(self.faresystems[fare_id])+"\n") + f.write(str(self.faresystems[fare_id]) + "\n") f2.write(self.faresystems[fare_id].getFareZoneMatrixLines()) f.close() f2.close() - - if self.modelType == Network.MODEL_TYPE_TM2 and (self.ptsystem.isEmpty()==False or writeEmptyFiles): + if self.modelType == Network.MODEL_TYPE_TM2 and ( + self.ptsystem.isEmpty() == False or writeEmptyFiles + ): logstr += " pts" - f = open(os.path.join(path,name+".pts"), 'w') - f.write(str(self.ptsystem)+"\n") + f = open(os.path.join(path, name + ".pts"), "w") + f.write(str(self.ptsystem) + "\n") f.close() logstr += "... done." @@ -895,27 +1085,45 @@ def parseAndPrintTransitFile(self, trntxt, verbosity=1): Verbosity=2: 1 line per node """ self.parser.setVerbosity(verbosity) - success, children, nextcharacter = self.parser.parse(trntxt, production="transit_file") - if not nextcharacter==len(trntxt): - errorstr = "\n Did not successfully read the whole file; got to nextcharacter=%d out of %d total" % (nextcharacter, len(trntxt)) - errorstr += "\n Did read %d lines, next unread text = [%s]" % (len(children), trntxt[nextcharacter:nextcharacter+200]) + success, children, nextcharacter = self.parser.parse( + trntxt, production="transit_file" + ) + if not nextcharacter == len(trntxt): + errorstr = ( + "\n Did not successfully read the whole file; got to nextcharacter=%d out of %d total" + % (nextcharacter, len(trntxt)) + ) + errorstr += "\n Did read %d lines, next unread text = [%s]" % ( + len(children), + trntxt[nextcharacter : nextcharacter + 200], + ) raise NetworkException(errorstr) # Convert from parser-tree format to in-memory transit data structures: (program, convertedLines) = self.parser.convertLineData() - convertedLinks = self.parser.convertLinkData() - convertedPNR = self.parser.convertPNRData() - convertedZAC = self.parser.convertZACData() - convertedAccessLinki = self.parser.convertLinkiData("access") - convertedXferLinki = self.parser.convertLinkiData("xfer") - convertedNodes = self.parser.convertLinkiData("node") - convertedSupplinks = self.parser.convertSupplinksData() - convertedFaresystems = self.parser.convertFaresystemData() - convertedPTSystem = self.parser.convertPTSystemData() - - return program, convertedLines, convertedLinks, convertedPNR, convertedZAC, \ - convertedAccessLinki, convertedXferLinki, convertedNodes, convertedSupplinks, \ - convertedFaresystems, convertedPTSystem + convertedLinks = self.parser.convertLinkData() + convertedPNR = self.parser.convertPNRData() + convertedZAC = self.parser.convertZACData() + convertedAccessLinki = self.parser.convertLinkiData("access") + convertedXferLinki = self.parser.convertLinkiData("xfer") + convertedNodes = self.parser.convertLinkiData("node") + convertedSupplinks = self.parser.convertSupplinksData() + convertedFaresystems = self.parser.convertFaresystemData() + convertedPTSystem = self.parser.convertPTSystemData() + + return ( + program, + convertedLines, + convertedLinks, + convertedPNR, + convertedZAC, + convertedAccessLinki, + convertedXferLinki, + convertedNodes, + convertedSupplinks, + convertedFaresystems, + convertedPTSystem, + ) def parseFile(self, fullfile, insert_replace=True): """ @@ -923,9 +1131,9 @@ def parseFile(self, fullfile, insert_replace=True): insert_replace=True if you want to replace the data in place rather than appending """ suffix = fullfile.rsplit(".")[-1].lower() - self.parseFileAsSuffix(fullfile,suffix,insert_replace) - - def parseFileAsSuffix(self,fullfile,suffix,insert_replace): + self.parseFileAsSuffix(fullfile, suffix, insert_replace) + + def parseFileAsSuffix(self, fullfile, suffix, insert_replace): """ This is a little bit of a hack, but it's meant to allow us to do something like read an xfer file as an access file... @@ -933,49 +1141,90 @@ def parseFileAsSuffix(self,fullfile,suffix,insert_replace): self.parser = TransitParser(transit_file_def, 0) self.parser.tfp.liType = suffix logstr = " Reading %s as %s" % (fullfile, suffix) - f = open(fullfile, 'r'); - prog,lines,links,pnr,zac,accessli,xferli,nodes,supps,faresys,pts = self.parseAndPrintTransitFile(f.read().rstrip('\0'), verbosity=0) + f = open(fullfile, "r") + ( + prog, + lines, + links, + pnr, + zac, + accessli, + xferli, + nodes, + supps, + faresys, + pts, + ) = self.parseAndPrintTransitFile(f.read().rstrip("\0"), verbosity=0) f.close() - logstr += self.doMerge(fullfile,prog,lines,links,pnr,zac,accessli,xferli,nodes,supps,faresys,pts,insert_replace) + logstr += self.doMerge( + fullfile, + prog, + lines, + links, + pnr, + zac, + accessli, + xferli, + nodes, + supps, + faresys, + pts, + insert_replace, + ) WranglerLogger.debug(logstr) - - def doMerge(self,path,prog,lines,links,pnrs,zacs,accessli,xferli,nodes,supps,faresys,pts,insert_replace=False): + + def doMerge( + self, + path, + prog, + lines, + links, + pnrs, + zacs, + accessli, + xferli, + nodes, + supps, + faresys, + pts, + insert_replace=False, + ): """ Merge a set of transit lines & support links with this network's transit representation. """ logstr = " -- Merging" - if len(lines)>0: + if len(lines) > 0: logstr += " %s lines" % len(lines) if len(self.lines) == 0: self.program = prog else: # don't mix PT and TRNBUILD - assert((prog == TransitParser.PROGRAM_UNKNOWN) or (prog == self.program)) + assert (prog == TransitParser.PROGRAM_UNKNOWN) or (prog == self.program) extendlines = copy.deepcopy(lines) for line in lines: - if isinstance(line,TransitLine) and (line in self.lines): + if isinstance(line, TransitLine) and (line in self.lines): # logstr += " *%s" % (line.name) if insert_replace: - self.lines[self.lines.index(line)]=line + self.lines[self.lines.index(line)] = line extendlines.remove(line) else: self.lines.remove(line) - if len(extendlines)>0: + if len(extendlines) > 0: # for line in extendlines: print line - self.lines.extend(["\n;######################### From: "+path+"\n"]) + self.lines.extend(["\n;######################### From: " + path + "\n"]) self.lines.extend(extendlines) - if len(links)>0: + if len(links) > 0: logstr += " %d links" % len(links) - self.links.extend(["\n;######################### From: "+path+"\n"]) + self.links.extend(["\n;######################### From: " + path + "\n"]) self.links.extend(links) - if len(pnrs)>0: + if len(pnrs) > 0: # if reading X.pnr, use X pnr_basename = os.path.basename(path) (pnr_root, pnr_ext) = os.path.splitext(pnr_basename) @@ -983,39 +1232,41 @@ def doMerge(self,path,prog,lines,links,pnrs,zacs,accessli,xferli,nodes,supps,far logstr += " {} {}_PNRs".format(len(pnrs), pnr_root) if pnr_root not in self.pnrs: self.pnrs[pnr_root] = [] - self.pnrs[pnr_root].extend( ["\n;######################### From: "+path+"\n"]) + self.pnrs[pnr_root].extend( + ["\n;######################### From: " + path + "\n"] + ) self.pnrs[pnr_root].extend(pnrs) - if len(zacs)>0: + if len(zacs) > 0: logstr += " %d ZACs" % len(zacs) - self.zacs.extend( ["\n;######################### From: "+path+"\n"]) + self.zacs.extend(["\n;######################### From: " + path + "\n"]) self.zacs.extend(zacs) - if len(accessli)>0: + if len(accessli) > 0: logstr += " %d accesslinks" % len(accessli) - self.accessli.extend( ["\n;######################### From: "+path+"\n"]) + self.accessli.extend(["\n;######################### From: " + path + "\n"]) self.accessli.extend(accessli) - if len(xferli)>0: + if len(xferli) > 0: logstr += " %d xferlinks" % len(xferli) - self.xferli.extend( ["\n;######################### From: "+path+"\n"]) + self.xferli.extend(["\n;######################### From: " + path + "\n"]) self.xferli.extend(xferli) - if len(nodes)>0: + if len(nodes) > 0: logstr += " %d nodes" % len(nodes) - self.nodes.extend( ["\n;######################### From: "+path+"\n"]) + self.nodes.extend(["\n;######################### From: " + path + "\n"]) self.nodes.extend(nodes) - if len(supps)>0: + if len(supps) > 0: logstr += " %d supps" % len(supps) - self.supps.extend( ["\n;######################### From: "+path+"\n"]) + self.supps.extend(["\n;######################### From: " + path + "\n"]) self.supps.extend(supps) - if len(faresys)>0: + if len(faresys) > 0: logstr += " %d faresystems" % len(faresys) # merge the faresystems dictionary - for (fs_id,fs) in faresys.items(): + for (fs_id, fs) in faresys.items(): if fs_id in self.faresystems: WranglerLogger.fatal("FARESYSTEM definition collision:") WranglerLogger.fatal(" existing: " + str(self.faresystems[fs_id])) @@ -1031,7 +1282,7 @@ def doMerge(self,path,prog,lines,links,pnrs,zacs,accessli,xferli,nodes,supps,far logstr += "...done." return logstr - def mergeDir(self,path,insert_replace=False): + def mergeDir(self, path, insert_replace=False): """ Append all the transit-related files in the given directory. Does NOT apply __init__.py modifications from that directory. @@ -1042,15 +1293,41 @@ def mergeDir(self,path,insert_replace=False): for filename in dirlist: suffix = filename.rsplit(".")[-1].lower() - if suffix in ["lin","link","pnr","zac","access","xfer","pts"]: + if suffix in ["lin", "link", "pnr", "zac", "access", "xfer", "pts"]: self.parser = TransitParser(transit_file_def, verbosity=0) self.parser.tfp.liType = suffix - fullfile = os.path.join(path,filename) + fullfile = os.path.join(path, filename) logstr = " Reading %s" % filename - f = open(fullfile, 'r'); - prog,lines,links,pnr,zac,accessli,xferli,nodes,supps,faresys,pts = self.parseAndPrintTransitFile(f.read(), verbosity=0) + f = open(fullfile, "r") + ( + prog, + lines, + links, + pnr, + zac, + accessli, + xferli, + nodes, + supps, + faresys, + pts, + ) = self.parseAndPrintTransitFile(f.read(), verbosity=0) f.close() - logstr += self.doMerge(fullfile,prog,lines,links,pnr,zac,accessli,xferli,nodes,supps,faresys,pts,insert_replace) + logstr += self.doMerge( + fullfile, + prog, + lines, + links, + pnr, + zac, + accessli, + xferli, + nodes, + supps, + faresys, + pts, + insert_replace, + ) WranglerLogger.debug(logstr) @staticmethod @@ -1060,67 +1337,75 @@ def initializeTransitCapacity(directory="."): def findSimpleDwellDelay(self, line): """ Returns the simple mode/owner-based dwell delay for the given *line*. This could - be a method in :py:class:`TransitLine` but I think it's more logical to be + be a method in :py:class:`TransitLine` but I think it's more logical to be :py:class:`TransitNetwork` specific... """ # use AM to lookup the vehicle simpleDwell = TransitNetwork.capacity.getSimpleDwell(line.name, "AM") - + owner = None - if 'OWNER' in line.attr: - owner = line.attr['OWNER'].strip(r'"\'') + if "OWNER" in line.attr: + owner = line.attr["OWNER"].strip(r'"\'') - if owner and owner.upper() == 'TPS': + if owner and owner.upper() == "TPS": simpleDwell -= 0.1 - if owner and owner.upper() == 'BRT': + if owner and owner.upper() == "BRT": # (20% Savings Low Floor)*(20% Savings POP) - simpleDwell = simpleDwell*0.8*0.8 + simpleDwell = simpleDwell * 0.8 * 0.8 # but lets not go below 0.3 if simpleDwell < 0.3: simpleDwell = 0.3 return simpleDwell - - def addDelay(self, timeperiod="Simple", additionalLinkFile=None, - complexDelayModes=[], complexAccessModes=[], - transitAssignmentData=None, - MSAweight=1.0, previousNet=None, logPrefix="", stripTimeFacRunTimeAttrs=True): - """ - Replaces the old ``addDelay.awk`` script. - + + def addDelay( + self, + timeperiod="Simple", + additionalLinkFile=None, + complexDelayModes=[], + complexAccessModes=[], + transitAssignmentData=None, + MSAweight=1.0, + previousNet=None, + logPrefix="", + stripTimeFacRunTimeAttrs=True, + ): + """ + Replaces the old ``addDelay.awk`` script. + The simple version simply looks up a delay for all stops based on the transit line's OWNER and MODE. (Owners ``TPS`` and ``BRT`` get shorter delays.) It will also dupe any two-way lines that are one of the complexAccessModes because those access mode shutoffs only make sense if the lines are one-way. - + Exempts nodes that are in the network's TransitLinks and in the optional *additionalLinkFile*, from dwell delay; the idea being that these are LRT or fixed guideway links and the link time includes a dwell delay. - + If *transitAssignmentData* is passed in, however, then the boards, alights and vehicle type from that data are used to calculate delay for the given *complexDelayModes*. - + When *MSAweight* < 1.0, then the delay is modified to be a linear combination of (prev delay x (1.0-*MSAweight*)) + (new delay x *MSAweight*)) - + *logPrefix* is a string used for logging: this method appends to the following files: - - * ``lineStats[timeperiod].csv`` contains *logPrefix*, line name, total Dwell for the line, + + * ``lineStats[timeperiod].csv`` contains *logPrefix*, line name, total Dwell for the line, number of closed nodes for the line * ``dwellbucket[timeperiod].csv`` contails distribution information for the dwells. It includes *logPrefix*, dwell bucket number, and dwell bucket count. Currently dwell buckets are 0.1 minutes - + When *stripTimeFacRunTimeAttrs* is passed as TRUE, TIMEFAC and RUNTIME is stripped for ALL modes. Otherwise it's ignored. """ # Use own links and, if passed, additionaLinkFile to form linSet, which is the set of - # nodes in the links + # nodes in the links linkSet = set() for link in self.links: - if isinstance(link,TransitLink): + if isinstance(link, TransitLink): link.addNodesToSet(linkSet) # print linkSet logstr = "addDelay: Size of linkset = %d" % (len(linkSet)) @@ -1128,42 +1413,54 @@ def addDelay(self, timeperiod="Simple", additionalLinkFile=None, if additionalLinkFile: linknet = TransitNetwork(self.modelType, self.modelVersion) linknet.parser = TransitParser(transit_file_def, verbosity=0) - f = open(additionalLinkFile, 'r'); - junk,junk,additionallinks,junk,junk,junk,junk,junk,junk,junk,junk = \ - linknet.parseAndPrintTransitFile(f.read(), verbosity=0) + f = open(additionalLinkFile, "r") + ( + junk, + junk, + additionallinks, + junk, + junk, + junk, + junk, + junk, + junk, + junk, + junk, + ) = linknet.parseAndPrintTransitFile(f.read(), verbosity=0) f.close() for link in additionallinks: - if isinstance(link,TransitLink): + if isinstance(link, TransitLink): link.addNodesToSet(linkSet) # print linkSet logstr += " => %d with %s\n" % (len(linkSet), additionalLinkFile) WranglerLogger.info(logstr) # record keeping for logging - statsfile = open("lineStats"+timeperiod+".csv", "a") - dwellbucketfile = open("dwellbucket"+timeperiod+".csv", "a") - totalLineDwell = {} # linename => total dwell - totalClosedNodes = {} # linename => closed nodes - DWELL_BUCKET_SIZE = 0.1 # minutes - dwellBuckets = defaultdict(int) # initialize to index => bucket - + statsfile = open("lineStats" + timeperiod + ".csv", "a") + dwellbucketfile = open("dwellbucket" + timeperiod + ".csv", "a") + totalLineDwell = {} # linename => total dwell + totalClosedNodes = {} # linename => closed nodes + DWELL_BUCKET_SIZE = 0.1 # minutes + dwellBuckets = defaultdict(int) # initialize to index => bucket + # Dupe the one-way lines for complexAccessModes - if timeperiod=="Simple" and len(complexAccessModes)>0: + if timeperiod == "Simple" and len(complexAccessModes) > 0: line_idx = 0 while True: # out of lines, done! - if line_idx >= len(self.lines): break - + if line_idx >= len(self.lines): + break + # skip non-TransitLines - if not isinstance(self.lines[line_idx],TransitLine): + if not isinstance(self.lines[line_idx], TransitLine): line_idx += 1 continue - + # skip non-ComplexAccessMode lines - if int(self.lines[line_idx].attr['MODE']) not in complexAccessModes: + if int(self.lines[line_idx].attr["MODE"]) not in complexAccessModes: line_idx += 1 continue - + # this is a relevant line -- is it oneway? then we're ok if self.lines[line_idx].isOneWay(): line_idx += 1 @@ -1173,16 +1470,18 @@ def addDelay(self, timeperiod="Simple", additionalLinkFile=None, self.lines[line_idx].setOneWay() reverse_line = copy.deepcopy(self.lines[line_idx]) reverse_line.reverse() - - WranglerLogger.debug("Reversed line %s to line %s" % (str(self.lines[line_idx]), str(reverse_line))) - self.lines.insert(line_idx+1,reverse_line) + + WranglerLogger.debug( + "Reversed line %s to line %s" + % (str(self.lines[line_idx]), str(reverse_line)) + ) + self.lines.insert(line_idx + 1, reverse_line) line_idx += 2 - # iterate through my lines for line in self: - totalLineDwell[line.name] = 0.0 + totalLineDwell[line.name] = 0.0 totalClosedNodes[line.name] = 0 # strip the TIMEFAC and the RUNTIME, if desired @@ -1191,112 +1490,177 @@ def addDelay(self, timeperiod="Simple", additionalLinkFile=None, WranglerLogger.debug("Stripping RUNTIME from %s" % line.name) del line.attr["RUNTIME"] if "TIMEFAC" in line.attr: - WranglerLogger.debug("Stripping TIMEFAC from %s" % line.name) + WranglerLogger.debug("Stripping TIMEFAC from %s" % line.name) del line.attr["TIMEFAC"] - + # Passing on all the lines that do not have service during the specific time of day - if timeperiod in TransitLine.HOURS_PER_TIMEPERIOD[self.modelType] and line.getFreq(timeperiod, self.modelType) == 0.0: continue - - + if ( + timeperiod in TransitLine.HOURS_PER_TIMEPERIOD[self.modelType] + and line.getFreq(timeperiod, self.modelType) == 0.0 + ): + continue + simpleDwellDelay = self.findSimpleDwellDelay(line) for nodeIdx in range(len(line.n)): # linkSet nodes exempt - don't add delay 'cos that's inherent to the link - if int(line.n[nodeIdx].num) in linkSet: continue + if int(line.n[nodeIdx].num) in linkSet: + continue # last stop - no delay, end of the line - if nodeIdx == len(line.n)-1: continue + if nodeIdx == len(line.n) - 1: + continue # dwell delay for stop nodes only - if not line.n[nodeIdx].isStop(): continue - + if not line.n[nodeIdx].isStop(): + continue + # ======================================================================================= # turn off access? - if (transitAssignmentData and - (nodeIdx>0) and - (int(line.attr["MODE"]) in complexAccessModes)): - try: - loadFactor = transitAssignmentData.loadFactor(line.name, - abs(int(line.n[nodeIdx-1].num)), - abs(int(line.n[nodeIdx].num)), - nodeIdx) + if ( + transitAssignmentData + and (nodeIdx > 0) + and (int(line.attr["MODE"]) in complexAccessModes) + ): + try: + loadFactor = transitAssignmentData.loadFactor( + line.name, + abs(int(line.n[nodeIdx - 1].num)), + abs(int(line.n[nodeIdx].num)), + nodeIdx, + ) except: - WranglerLogger.warning("Failed to get loadfactor for (%s, A=%d B=%d SEQ=%d); assuming 0" % - (line.name, abs(int(line.n[nodeIdx-1].num)), abs(int(line.n[nodeIdx].num)),nodeIdx)) + WranglerLogger.warning( + "Failed to get loadfactor for (%s, A=%d B=%d SEQ=%d); assuming 0" + % ( + line.name, + abs(int(line.n[nodeIdx - 1].num)), + abs(int(line.n[nodeIdx].num)), + nodeIdx, + ) + ) loadFactor = 0.0 - - # disallow boardings (ACCESS=2) (for all nodes except first stop) + + # disallow boardings (ACCESS=2) (for all nodes except first stop) # if the previous link has load factor greater than 1.0 if loadFactor > 1.0: - line.n[nodeIdx].attr["ACCESS"] = 2 - totalClosedNodes[line.name] += 1 + line.n[nodeIdx].attr["ACCESS"] = 2 + totalClosedNodes[line.name] += 1 - # ======================================================================================= + # ======================================================================================= # Simple delay if # - we do not have boards/alighting data, # - or if we're not configured to do a complex delay operation - if not transitAssignmentData or (int(line.attr["MODE"]) not in complexDelayModes): + if not transitAssignmentData or ( + int(line.attr["MODE"]) not in complexDelayModes + ): if simpleDwellDelay > 0: - line.n[nodeIdx].attr["DELAY"] = str(simpleDwellDelay) - totalLineDwell[line.name] += simpleDwellDelay - dwellBuckets[int(math.floor(simpleDwellDelay/DWELL_BUCKET_SIZE))] += 1 + line.n[nodeIdx].attr["DELAY"] = str(simpleDwellDelay) + totalLineDwell[line.name] += simpleDwellDelay + dwellBuckets[ + int(math.floor(simpleDwellDelay / DWELL_BUCKET_SIZE)) + ] += 1 continue - + # Complex Delay # ======================================================================================= vehiclesPerPeriod = line.vehiclesPerPeriod(timeperiod, self.modelType) try: - boards = transitAssignmentData.numBoards(line.name, - abs(int(line.n[nodeIdx].num)), - abs(int(line.n[nodeIdx+1].num)), - nodeIdx+1) + boards = transitAssignmentData.numBoards( + line.name, + abs(int(line.n[nodeIdx].num)), + abs(int(line.n[nodeIdx + 1].num)), + nodeIdx + 1, + ) except: - WranglerLogger.warning("Failed to get boards for (%s, A=%d B=%d SEQ=%d); assuming 0" % - (line.name, abs(int(line.n[nodeIdx].num)), abs(int(line.n[nodeIdx+1].num)),nodeIdx+1)) + WranglerLogger.warning( + "Failed to get boards for (%s, A=%d B=%d SEQ=%d); assuming 0" + % ( + line.name, + abs(int(line.n[nodeIdx].num)), + abs(int(line.n[nodeIdx + 1].num)), + nodeIdx + 1, + ) + ) boards = 0 # At the first stop, vehicle has no exits and load factor - if nodeIdx == 0: - exits = 0 + if nodeIdx == 0: + exits = 0 else: try: - exits = transitAssignmentData.numExits(line.name, - abs(int(line.n[nodeIdx-1].num)), - abs(int(line.n[nodeIdx].num)), - nodeIdx) + exits = transitAssignmentData.numExits( + line.name, + abs(int(line.n[nodeIdx - 1].num)), + abs(int(line.n[nodeIdx].num)), + nodeIdx, + ) except: - WranglerLogger.warning("Failed to get exits for (%s, A=%d B=%d SEQ=%d); assuming 0" % - (line.name, abs(int(line.n[nodeIdx-1].num)), abs(int(line.n[nodeIdx].num)),nodeIdx)) + WranglerLogger.warning( + "Failed to get exits for (%s, A=%d B=%d SEQ=%d); assuming 0" + % ( + line.name, + abs(int(line.n[nodeIdx - 1].num)), + abs(int(line.n[nodeIdx].num)), + nodeIdx, + ) + ) exits = 0 - - if MSAweight < 1.0: try: - existingDelay = float(previousNet.line(line.name).n[nodeIdx].attr["DELAY"]) + existingDelay = float( + previousNet.line(line.name).n[nodeIdx].attr["DELAY"] + ) except: - WranglerLogger.debug("No delay found for line %s node %s -- using 0" % - (line.name, previousNet.line(line.name).n[nodeIdx].num)) - existingDelay = 0.0 # this can happen if no boards/alights and const=0 + WranglerLogger.debug( + "No delay found for line %s node %s -- using 0" + % (line.name, previousNet.line(line.name).n[nodeIdx].num) + ) + existingDelay = ( + 0.0 # this can happen if no boards/alights and const=0 + ) else: MSAdelay = -99999999 existingDelay = 0.0 - (delay_const,delay_per_board,delay_per_alight) = transitAssignmentData.capacity.getComplexDwells(line.name, timeperiod) - - WranglerLogger.debug("line name=%s, timeperiod=%s, delay_const,perboard,peralight=%.3f, %.3f, %.3f" % - (line.name, timeperiod, delay_const, delay_per_board, delay_per_alight)) - - dwellDelay = (1.0-MSAweight)*existingDelay + \ - MSAweight*((delay_per_board*float(boards)/vehiclesPerPeriod) + - (delay_per_alight*float(exits)/vehiclesPerPeriod) + - delay_const) - line.n[nodeIdx].attr["DELAY"] ="%.3f" % dwellDelay - totalLineDwell[line.name] += dwellDelay - dwellBuckets[int(math.floor(dwellDelay/DWELL_BUCKET_SIZE))] += 1 + ( + delay_const, + delay_per_board, + delay_per_alight, + ) = transitAssignmentData.capacity.getComplexDwells( + line.name, timeperiod + ) + + WranglerLogger.debug( + "line name=%s, timeperiod=%s, delay_const,perboard,peralight=%.3f, %.3f, %.3f" + % ( + line.name, + timeperiod, + delay_const, + delay_per_board, + delay_per_alight, + ) + ) + + dwellDelay = (1.0 - MSAweight) * existingDelay + MSAweight * ( + (delay_per_board * float(boards) / vehiclesPerPeriod) + + (delay_per_alight * float(exits) / vehiclesPerPeriod) + + delay_const + ) + line.n[nodeIdx].attr["DELAY"] = "%.3f" % dwellDelay + totalLineDwell[line.name] += dwellDelay + dwellBuckets[int(math.floor(dwellDelay / DWELL_BUCKET_SIZE))] += 1 # end for each node loop - statsfile.write("%s,%s,%f,%d\n" % (logPrefix, line.name, - totalLineDwell[line.name], totalClosedNodes[line.name])) + statsfile.write( + "%s,%s,%f,%d\n" + % ( + logPrefix, + line.name, + totalLineDwell[line.name], + totalClosedNodes[line.name], + ) + ) # end for each line loop for bucketnum in dwellBuckets.keys(): @@ -1313,26 +1677,38 @@ def checkCapacityConfiguration(self, complexDelayModes, complexAccessModes): """ if not TransitNetwork.capacity: TransitNetwork.capacity = TransitCapacity() - + failures = 0 for line in self: linename = line.name.upper() - mode = int(line.attr["MODE"]) + mode = int(line.attr["MODE"]) if mode in complexDelayModes or mode in complexAccessModes: - + for timeperiod in ["AM", "MD", "PM", "EV", "EA"]: - if line.getFreq(timeperiod, self.modelType) == 0: continue + if line.getFreq(timeperiod, self.modelType) == 0: + continue try: - (vehicletype, cap) = TransitNetwork.capacity.getVehicleTypeAndCapacity(linename, timeperiod) + ( + vehicletype, + cap, + ) = TransitNetwork.capacity.getVehicleTypeAndCapacity( + linename, timeperiod + ) if mode in complexDelayModes: - (delc,delpb,delpa) = TransitNetwork.capacity.getComplexDwells(linename, timeperiod) + ( + delc, + delpb, + delpa, + ) = TransitNetwork.capacity.getComplexDwells( + linename, timeperiod + ) except NetworkException as e: print(e) failures += 1 - return (failures == 0) - + return failures == 0 + def moveBusesToHovAndExpressLanes(self): """ Moves transit lines from GP links to equivalent HOV links and equivalent Express Lane links @@ -1340,156 +1716,287 @@ def moveBusesToHovAndExpressLanes(self): # In order to run this, the roadway network needs written for us to read # so pick a place to write it import tempfile + tempdir = tempfile.mkdtemp() WranglerLogger.debug("Writing roadway network to tempdir {}".format(tempdir)) - Network.allNetworks['hwy'].write(path=tempdir, name="freeflow.net", writeEmptyFiles=False, suppressQuery=True, suppressValidation=True) + Network.allNetworks["hwy"].write( + path=tempdir, + name="freeflow.net", + writeEmptyFiles=False, + suppressQuery=True, + suppressValidation=True, + ) tempnet = os.path.join(tempdir, "freeflow.net") # Read it import Cube - link_vars = ['LANES','USE','FT','TOLLCLASS','ROUTENUM','ROUTEDIR','PROJ'] - (nodes_dict, links_dict) = Cube.import_cube_nodes_links_from_csvs(tempnet, extra_link_vars=link_vars, - links_csv=os.path.join(tempdir,"cubenet_links.csv"), - nodes_csv=os.path.join(tempdir,"cubenet_nodes.csv"), - exportIfExists=True) - WranglerLogger.debug("Have {} nodes and {} links".format(len(nodes_dict), len(links_dict))) + + link_vars = ["LANES", "USE", "FT", "TOLLCLASS", "ROUTENUM", "ROUTEDIR", "PROJ"] + (nodes_dict, links_dict) = Cube.import_cube_nodes_links_from_csvs( + tempnet, + extra_link_vars=link_vars, + links_csv=os.path.join(tempdir, "cubenet_links.csv"), + nodes_csv=os.path.join(tempdir, "cubenet_nodes.csv"), + exportIfExists=True, + ) + WranglerLogger.debug( + "Have {} nodes and {} links".format(len(nodes_dict), len(links_dict)) + ) # links_dict: (a,b) => list with distance followed by extra_link_vars links_list = [] for a_b_tuple in links_dict.keys(): # put all attributes into a list distance = float(links_dict[a_b_tuple][0]) - lanes = int(links_dict[a_b_tuple][1]) - use = int(links_dict[a_b_tuple][2]) - ft = int(links_dict[a_b_tuple][3]) - tollclass= int(links_dict[a_b_tuple][4]) + lanes = int(links_dict[a_b_tuple][1]) + use = int(links_dict[a_b_tuple][2]) + ft = int(links_dict[a_b_tuple][3]) + tollclass = int(links_dict[a_b_tuple][4]) routenum = int(links_dict[a_b_tuple][5]) routedir = links_dict[a_b_tuple][6].strip(" '") - if routedir == "' '": routedir = "" - proj = links_dict[a_b_tuple][7].strip(" '") - if proj == "' '": proj = "" - - link_list = [ a_b_tuple[0], a_b_tuple[1]] + [distance, lanes, use, ft, tollclass, routenum, routedir, proj] + if routedir == "' '": + routedir = "" + proj = links_dict[a_b_tuple][7].strip(" '") + if proj == "' '": + proj = "" + + link_list = [a_b_tuple[0], a_b_tuple[1]] + [ + distance, + lanes, + use, + ft, + tollclass, + routenum, + routedir, + proj, + ] # append to list of links links_list.append(link_list) - + # let's use pandas for this import pandas + pandas.options.display.width = 500 pandas.options.display.max_columns = 100 - link_cols = ["a","b","DISTANCE"] + link_vars + link_cols = ["a", "b", "DISTANCE"] + link_vars links_df = pandas.DataFrame.from_records(data=links_list, columns=link_cols) WranglerLogger.debug("\n:{}".format(links_df.head())) # filter out HOV and express lane links - hov_links_df = links_df.loc[ (links_df.USE == 2)|(links_df.USE==3) ] - el_links_df = links_df.loc[ links_df.TOLLCLASS >= 11 ] - notruck_links_df = links_df.loc[ (links_df.TOLLCLASS==0) & (links_df.USE==4)] - gp_links_df = links_df.loc[ (links_df.USE==1)&((links_df.FT<=3)|(links_df.FT==5)|(links_df.FT==7)|(links_df.FT==8)|(links_df.FT==10))] + hov_links_df = links_df.loc[(links_df.USE == 2) | (links_df.USE == 3)] + el_links_df = links_df.loc[links_df.TOLLCLASS >= 11] + notruck_links_df = links_df.loc[(links_df.TOLLCLASS == 0) & (links_df.USE == 4)] + gp_links_df = links_df.loc[ + (links_df.USE == 1) + & ( + (links_df.FT <= 3) + | (links_df.FT == 5) + | (links_df.FT == 7) + | (links_df.FT == 8) + | (links_df.FT == 10) + ) + ] gp_notruck_links_df = gp_links_df.append(notruck_links_df) - dummy_links_df = links_df.loc[ links_df.FT==6 ] + dummy_links_df = links_df.loc[links_df.FT == 6] - WranglerLogger.debug("Found {} hov links, {} express lane links and {} general purpose links".format( - len(hov_links_df), len(el_links_df), len(gp_notruck_links_df))) + WranglerLogger.debug( + "Found {} hov links, {} express lane links and {} general purpose links".format( + len(hov_links_df), len(el_links_df), len(gp_notruck_links_df) + ) + ) # dummy B -> hov A, a_GP1 will be the first point of dummy access link - hov_group1_df = pandas.merge(left=hov_links_df, right=dummy_links_df[["a","b"]], - how="inner", left_on=["a"], right_on=["b"], suffixes=["","_GP1"]).drop(columns="b_GP1") + hov_group1_df = pandas.merge( + left=hov_links_df, + right=dummy_links_df[["a", "b"]], + how="inner", + left_on=["a"], + right_on=["b"], + suffixes=["", "_GP1"], + ).drop(columns="b_GP1") # hov B -> dummy A, b_GP2 will be the second point of dummy egress link - hov_group1_df = pandas.merge(left=hov_group1_df, right=dummy_links_df[["a","b"]], - how="inner", left_on=["b"], right_on=["a"], suffixes=["","_GP2"]).drop(columns="a_GP2") + hov_group1_df = pandas.merge( + left=hov_group1_df, + right=dummy_links_df[["a", "b"]], + how="inner", + left_on=["b"], + right_on=["a"], + suffixes=["", "_GP2"], + ).drop(columns="a_GP2") # merge to the full GP links for complete info - hov_group1_df = pandas.merge(left=hov_group1_df, right=gp_notruck_links_df, how="inner", - left_on=["a_GP1", "b_GP2"], right_on=["a","b"], suffixes=["","_GP"]).drop(columns=["a_GP1","b_GP2"]) - - WranglerLogger.debug("Found general purpose links for {} out of {} hov links: \n{}".format( - len(hov_group1_df), len(hov_links_df), hov_group1_df.head())) + hov_group1_df = pandas.merge( + left=hov_group1_df, + right=gp_notruck_links_df, + how="inner", + left_on=["a_GP1", "b_GP2"], + right_on=["a", "b"], + suffixes=["", "_GP"], + ).drop(columns=["a_GP1", "b_GP2"]) + + WranglerLogger.debug( + "Found general purpose links for {} out of {} hov links: \n{}".format( + len(hov_group1_df), len(hov_links_df), hov_group1_df.head() + ) + ) # Note which links don't have GP equivalents - hov_unmatched_df = pandas.merge(left=hov_links_df, right=hov_group1_df[["a","b","a_GP","b_GP"]], how="left") + hov_unmatched_df = pandas.merge( + left=hov_links_df, + right=hov_group1_df[["a", "b", "a_GP", "b_GP"]], + how="left", + ) WranglerLogger.debug("\n{}".format(hov_unmatched_df.head())) - hov_unmatched_df = hov_unmatched_df.loc[ pandas.isnull(hov_unmatched_df.a_GP) ].drop(columns=["a_GP","b_GP"]) - WranglerLogger.debug("hov links without match ({}):\n{}".format(len(hov_unmatched_df), hov_unmatched_df)) - hov_unmatched_df_QAQC_FILE = 'hov_unmatched.csv' - WranglerLogger.info("Export these links to {} for debugging".format(hov_unmatched_df_QAQC_FILE)) + hov_unmatched_df = hov_unmatched_df.loc[ + pandas.isnull(hov_unmatched_df.a_GP) + ].drop(columns=["a_GP", "b_GP"]) + WranglerLogger.debug( + "hov links without match ({}):\n{}".format( + len(hov_unmatched_df), hov_unmatched_df + ) + ) + hov_unmatched_df_QAQC_FILE = "hov_unmatched.csv" + WranglerLogger.info( + "Export these links to {} for debugging".format(hov_unmatched_df_QAQC_FILE) + ) hov_unmatched_df.to_csv(hov_unmatched_df_QAQC_FILE, index=False) # replace all instances of a_GP, b_GP with a_GP,a,hov,b_hov,b_gp # keep hov_nodes and gp_nodes lines_moved = [] hov_nodes = {} - gp_nodes = {} - hov_dict_list = hov_group1_df.to_dict(orient='records') + gp_nodes = {} + hov_dict_list = hov_group1_df.to_dict(orient="records") for hov_record in hov_dict_list: # split twice - lines_split1 = self.splitLinkInTransitLines(int(hov_record["a_GP"]), int(hov_record["b_GP"]), newNode=-1*int(hov_record["a"]), stop=False, verboseLog=False) - lines_split2 = self.splitLinkInTransitLines(int(hov_record["a" ]), int(hov_record["b_GP"]), newNode=-1*int(hov_record["b"]), stop=False, verboseLog=False) + lines_split1 = self.splitLinkInTransitLines( + int(hov_record["a_GP"]), + int(hov_record["b_GP"]), + newNode=-1 * int(hov_record["a"]), + stop=False, + verboseLog=False, + ) + lines_split2 = self.splitLinkInTransitLines( + int(hov_record["a"]), + int(hov_record["b_GP"]), + newNode=-1 * int(hov_record["b"]), + stop=False, + verboseLog=False, + ) lines_moved.extend(lines_split1) lines_moved.extend(lines_split2) lines_moved = sorted(set(lines_moved)) # keep these for fixing up lines hov_nodes[int(hov_record["a"])] = int(hov_record["a_GP"]) hov_nodes[int(hov_record["b"])] = int(hov_record["b_GP"]) - gp_nodes[-1*int(hov_record["a_GP"])] = int(hov_record["a"]) - gp_nodes[-1*int(hov_record["b_GP"])] = int(hov_record["b"]) + gp_nodes[-1 * int(hov_record["a_GP"])] = int(hov_record["a"]) + gp_nodes[-1 * int(hov_record["b_GP"])] = int(hov_record["b"]) # when two links in a row are moved, there can be an artifact where the dummy link is used twice -- remove these for line in self.line(re.compile(".")): line.removeDummyJag(gp_nodes) - WranglerLogger.info("Moved the following {} lines to hov links: {}".format(len(lines_moved), lines_moved)) + WranglerLogger.info( + "Moved the following {} lines to hov links: {}".format( + len(lines_moved), lines_moved + ) + ) ################## do it again for express lanes ################## # dummy B -> el A, a_GP1 will be the first point of dummy access link - el_group1_df = pandas.merge(left=el_links_df, right=dummy_links_df[["a","b"]], - how="inner", left_on=["a"], right_on=["b"], suffixes=["","_GP1"]).drop(columns="b_GP1") + el_group1_df = pandas.merge( + left=el_links_df, + right=dummy_links_df[["a", "b"]], + how="inner", + left_on=["a"], + right_on=["b"], + suffixes=["", "_GP1"], + ).drop(columns="b_GP1") # el B -> dummy A, b_GP2 will be the second point of dummy egress link - el_group1_df = pandas.merge(left=el_group1_df, right=dummy_links_df[["a","b"]], - how="inner", left_on=["b"], right_on=["a"], suffixes=["","_GP2"]).drop(columns="a_GP2") + el_group1_df = pandas.merge( + left=el_group1_df, + right=dummy_links_df[["a", "b"]], + how="inner", + left_on=["b"], + right_on=["a"], + suffixes=["", "_GP2"], + ).drop(columns="a_GP2") # merge to the full GP links for complete info - el_group1_df = pandas.merge(left=el_group1_df, right=gp_notruck_links_df, how="inner", - left_on=["a_GP1", "b_GP2"], right_on=["a","b"], suffixes=["","_GP"]).drop(columns=["a_GP1","b_GP2"]) - - WranglerLogger.debug("Found general purpose links for {} out of {} el links: \n{}".format( - len(el_group1_df), len(el_links_df), el_group1_df.head())) + el_group1_df = pandas.merge( + left=el_group1_df, + right=gp_notruck_links_df, + how="inner", + left_on=["a_GP1", "b_GP2"], + right_on=["a", "b"], + suffixes=["", "_GP"], + ).drop(columns=["a_GP1", "b_GP2"]) + + WranglerLogger.debug( + "Found general purpose links for {} out of {} el links: \n{}".format( + len(el_group1_df), len(el_links_df), el_group1_df.head() + ) + ) # Note which links don't have GP equivalents - el_unmatched_df = pandas.merge(left=el_links_df, right=el_group1_df[["a","b","a_GP","b_GP"]], how="left") + el_unmatched_df = pandas.merge( + left=el_links_df, right=el_group1_df[["a", "b", "a_GP", "b_GP"]], how="left" + ) WranglerLogger.debug("\n{}".format(el_unmatched_df.head())) - el_unmatched_df = el_unmatched_df.loc[ pandas.isnull(el_unmatched_df.a_GP) ].drop(columns=["a_GP","b_GP"]) - WranglerLogger.debug("el links without match ({}):\n{}".format(len(el_unmatched_df), el_unmatched_df)) - + el_unmatched_df = el_unmatched_df.loc[pandas.isnull(el_unmatched_df.a_GP)].drop( + columns=["a_GP", "b_GP"] + ) + WranglerLogger.debug( + "el links without match ({}):\n{}".format( + len(el_unmatched_df), el_unmatched_df + ) + ) + # replace all instances of a_GP, b_GP with a_GP,a,el,b_el,b_gp # keep el_nodes and gp_nodes lines_moved = [] - el_nodes = {} - gp_nodes = {} - el_dict_list = el_group1_df.to_dict(orient='records') + el_nodes = {} + gp_nodes = {} + el_dict_list = el_group1_df.to_dict(orient="records") for el_record in el_dict_list: - # split twice - lines_split1 = self.splitLinkInTransitLines(int(el_record["a_GP"]), int(el_record["b_GP"]), newNode=-1*int(el_record["a"]), stop=False, verboseLog=False) - lines_split2 = self.splitLinkInTransitLines(int(el_record["a" ]), int(el_record["b_GP"]), newNode=-1*int(el_record["b"]), stop=False, verboseLog=False) + # split twice + lines_split1 = self.splitLinkInTransitLines( + int(el_record["a_GP"]), + int(el_record["b_GP"]), + newNode=-1 * int(el_record["a"]), + stop=False, + verboseLog=False, + ) + lines_split2 = self.splitLinkInTransitLines( + int(el_record["a"]), + int(el_record["b_GP"]), + newNode=-1 * int(el_record["b"]), + stop=False, + verboseLog=False, + ) lines_moved.extend(lines_split1) lines_moved.extend(lines_split2) lines_moved = sorted(set(lines_moved)) # keep these for fixing up lines el_nodes[int(el_record["a"])] = int(el_record["a_GP"]) el_nodes[int(el_record["b"])] = int(el_record["b_GP"]) - gp_nodes[-1*int(el_record["a_GP"])] = int(el_record["a"]) - gp_nodes[-1*int(el_record["b_GP"])] = int(el_record["b"]) + gp_nodes[-1 * int(el_record["a_GP"])] = int(el_record["a"]) + gp_nodes[-1 * int(el_record["b_GP"])] = int(el_record["b"]) # when two links in a row are moved, there can be an artifact where the dummy link is used twice -- remove these for line in self.line(re.compile(".")): line.removeDummyJag(gp_nodes) - WranglerLogger.info("Moved the following {} lines to el links: {}".format(len(lines_moved), lines_moved)) + WranglerLogger.info( + "Moved the following {} lines to el links: {}".format( + len(lines_moved), lines_moved + ) + ) # remove the temp dir shutil.rmtree(tempdir) @@ -1500,73 +2007,89 @@ def checkValidityOfLinks(self, cubeNetFile): That is, each link in a .lin should either be in the roadway network, or in a .link file. """ import Cube - + extra_link_vars = [] - link_var_names = {} + link_var_names = {} if self.modelType == Network.MODEL_TYPE_CHAMP: - extra_link_vars=['STREETNAME', - 'LANE_AM', 'LANE_OP','LANE_PM', - 'BUSLANE_AM', 'BUSLANE_OP', 'BUSLANE_PM'] + extra_link_vars = [ + "STREETNAME", + "LANE_AM", + "LANE_OP", + "LANE_PM", + "BUSLANE_AM", + "BUSLANE_OP", + "BUSLANE_PM", + ] elif self.modelType == Network.MODEL_TYPE_TM1: - extra_link_vars=['LANES','BRT'] - link_var_names={ 'DISTANCE':0, 'LANES':1, 'BRT':2 } - - (nodes_dict, links_dict) = Cube.import_cube_nodes_links_from_csvs(cubeNetFile, - extra_link_vars=extra_link_vars, - extra_node_vars=[], - links_csv=os.path.join(os.getcwd(),"cubenet_validate_links.csv"), - nodes_csv=os.path.join(os.getcwd(),"cubenet_validate_nodes.csv"), - exportIfExists=True) + extra_link_vars = ["LANES", "BRT"] + link_var_names = {"DISTANCE": 0, "LANES": 1, "BRT": 2} + + (nodes_dict, links_dict) = Cube.import_cube_nodes_links_from_csvs( + cubeNetFile, + extra_link_vars=extra_link_vars, + extra_node_vars=[], + links_csv=os.path.join(os.getcwd(), "cubenet_validate_links.csv"), + nodes_csv=os.path.join(os.getcwd(), "cubenet_validate_nodes.csv"), + exportIfExists=True, + ) for line in self: - + # todo fix this line_is_oneway = True - + last_node = None for node in line: - # this is the first node - nothing to do + # this is the first node - nothing to do if not last_node: last_node = node continue - + # we need to check this link but possibly also the reverse link_list = [(abs(last_node), abs(node))] if not line_is_oneway: link_list.append((abs(node), abs(last_node))) - + # check the link(s) - for (a,b) in link_list: - + for (a, b) in link_list: + # it's a road link - if (a,b) in links_dict: + if (a, b) in links_dict: if self.modelType == Network.MODEL_TYPE_TM1: # if LANES = 0 and BRT != 1 then transit will be suuuuuuuper slow - if ((int(links_dict[(a,b)][link_var_names['LANES']]) == 0) and \ - (int(links_dict[(a,b)][link_var_names['BRT' ]]) != 1)): - msg = "line {} is running on link {}-{} which has LANES=0 and BRT!=1".format(line.name, a, b) + if ( + int(links_dict[(a, b)][link_var_names["LANES"]]) == 0 + ) and (int(links_dict[(a, b)][link_var_names["BRT"]]) != 1): + msg = "line {} is running on link {}-{} which has LANES=0 and BRT!=1".format( + line.name, a, b + ) WranglerLogger.fatal(msg) raise NetworkException(msg) continue - + found_link = False for link in self.links: - if not isinstance(link,TransitLink): continue - + if not isinstance(link, TransitLink): + continue + if link.Anode == a and link.Bnode == b: found_link = True break - + if not link.isOneway() and link.Anode == b and link.Bnode == a: found_link = True break - - if found_link: continue - WranglerLogger.warn("TransitNetwork.checkValidityOfLinks: (%d, %d) not in the roadway network nor in the off-road links (line %s)" % (a, b, line.name)) - + if found_link: + continue + + WranglerLogger.warn( + "TransitNetwork.checkValidityOfLinks: (%d, %d) not in the roadway network nor in the off-road links (line %s)" + % (a, b, line.name) + ) + last_node = node def applyProject(self, parentdir, networkdir, gitdir, projectsubdir=None, **kwargs): @@ -1582,7 +2105,7 @@ def applyProject(self, parentdir, networkdir, gitdir, projectsubdir=None, **kwar projectname = projectsubdir else: projectname = networkdir - + evalstr = "import %s; %s.apply(self" % (projectname, projectname) for key in kwargs.keys(): val = kwargs[key] @@ -1593,31 +2116,38 @@ def applyProject(self, parentdir, networkdir, gitdir, projectsubdir=None, **kwar except: print("Failed to exec [%s]".format(evalstr)) raise - + evalstr = "dir(%s)" % projectname projectdir = eval(evalstr) # WranglerLogger.debug("projectdir = " + str(projectdir)) - pyear = (eval("%s.year()" % projectname) if 'year' in projectdir else None) - pdesc = (eval("%s.desc()" % projectname) if 'desc' in projectdir else None) - + pyear = eval("%s.year()" % projectname) if "year" in projectdir else None + pdesc = eval("%s.desc()" % projectname) if "desc" in projectdir else None + # print "projectname=" + str(projectname) # print "pyear=" + str(pyear) # print "pdesc=" + str(pdesc) - + # fares for farefile in TransitNetwork.FARE_FILES[self.modelType]: fullfarefile = os.path.join(gitdir, farefile) linecount = 0 # WranglerLogger.debug("cwd=%s farefile %s exists? %d" % (os.getcwd(), fullfarefile, os.path.exists(fullfarefile))) - + if os.path.exists(fullfarefile): - infile = open(fullfarefile, 'r') + infile = open(fullfarefile, "r") lines = infile.readlines() self.farefiles[farefile].extend(lines) linecount = len(lines) infile.close() - WranglerLogger.debug("Read %5d lines from fare file %s" % (linecount, fullfarefile)) - - return self.logProject(gitdir=gitdir, - projectname=(networkdir + "\\" + projectsubdir if projectsubdir else networkdir), - year=pyear, projectdesc=pdesc) + WranglerLogger.debug( + "Read %5d lines from fare file %s" % (linecount, fullfarefile) + ) + + return self.logProject( + gitdir=gitdir, + projectname=( + networkdir + "\\" + projectsubdir if projectsubdir else networkdir + ), + year=pyear, + projectdesc=pdesc, + ) diff --git a/Wrangler/TransitParser.py b/Wrangler/TransitParser.py index 9218769..dd9fa9a 100644 --- a/Wrangler/TransitParser.py +++ b/Wrangler/TransitParser.py @@ -16,14 +16,14 @@ from .TransitLink import TransitLink from .ZACLink import ZACLink -__all__ = [ 'TransitParser' ] +__all__ = ["TransitParser"] -WRANGLER_FILE_SUFFICES = [ "lin", "link", "pnr", "zac", "access", "xfer", "pts" ] +WRANGLER_FILE_SUFFICES = ["lin", "link", "pnr", "zac", "access", "xfer", "pts"] # PARSER DEFINITION ------------------------------------------------------------------------------ # NOTE: even though XYSPEED and TIMEFAC are node attributes here, I'm not sure that's really ok -- # Cube documentation implies TF and XYSPD are node attributes... -transit_file_def=r''' +transit_file_def = r""" transit_file := smcw*, ( accessli / line / link / pnr / zac / supplink / factor / faresystem / waitcrvdef / crowdcrvdef / operator / mode / vehicletype )+, smcw*, whitespace* line := whitespace?, smcw?, c"LINE", whitespace, lin_attr*, lin_node*, whitespace? @@ -112,36 +112,37 @@ := [ \t\r\n]+ := [ \t]+ smcw := whitespace?, (semicolon_comment / c_comment, whitespace?)+ -''' +""" + class TransitFileProcessor(DispatchProcessor): - """ Class to process transit files - """ + """Class to process transit files""" + def __init__(self, verbosity=1): - self.verbosity=verbosity + self.verbosity = verbosity self.lines = [] self.links = [] - self.pnrs = [] - self.zacs = [] + self.pnrs = [] + self.zacs = [] self.accesslis = [] - self.xferlis = [] - self.nodes = [] - self.liType = '' + self.xferlis = [] + self.nodes = [] + self.liType = "" self.supplinks = [] - self.factors = [] - self.faresystems = [] + self.factors = [] + self.faresystems = [] # PT System control statements - self.waitcrvdefs = [] + self.waitcrvdefs = [] self.crowdcrvdefs = [] - self.operators = [] - self.modes = [] + self.operators = [] + self.modes = [] self.vehicletypes = [] self.linecomments = [] def crackTags(self, leaf, buffer): tag = leaf[0] - text = buffer[leaf[1]:leaf[2]] + text = buffer[leaf[1] : leaf[2]] subtags = leaf[3] b = [] @@ -150,106 +151,106 @@ def crackTags(self, leaf, buffer): for leaf in subtags: b.append(self.crackTags(leaf, buffer)) - return (tag,text,b) + return (tag, text, b) def line(self, tup, buffer): - (tag,start,stop,subtags) = tup + (tag, start, stop, subtags) = tup # this is the whole line - if self.verbosity>=1: - print(tag,start,stop) + if self.verbosity >= 1: + print(tag, start, stop) # Append list items for this line for leaf in subtags: - xxx = self.crackTags(leaf,buffer) + xxx = self.crackTags(leaf, buffer) self.lines.append(xxx) - if self.verbosity==2: + if self.verbosity == 2: # lines are composed of smcw (semicolon-comment / whitespace), line_attr and lin_node for linepart in subtags: - print(" ",linepart[0], " -> [ "), + print(" ", linepart[0], " -> [ "), for partpart in linepart[3]: - print(partpart[0], "(", buffer[partpart[1]:partpart[2]],")"), + print(partpart[0], "(", buffer[partpart[1] : partpart[2]], ")"), print(" ]") def link(self, tup, buffer): - (tag,start,stop,subtags) = tup + (tag, start, stop, subtags) = tup # this is the whole link - if self.verbosity>=1: + if self.verbosity >= 1: print(tag, start, stop) # Append list items for this link for leaf in subtags: - xxx = self.crackTags(leaf,buffer) + xxx = self.crackTags(leaf, buffer) self.links.append(xxx) - if self.verbosity==2: + if self.verbosity == 2: # links are composed of smcw and link_attr for linkpart in subtags: - print(" ",linkpart[0], " -> [ "), + print(" ", linkpart[0], " -> [ "), for partpart in linkpart[3]: - print(partpart[0], "(", buffer[partpart[1]:partpart[2]], ")"), + print(partpart[0], "(", buffer[partpart[1] : partpart[2]], ")"), print(" ]") def pnr(self, tup, buffer): - (tag,start,stop,subtags) = tup + (tag, start, stop, subtags) = tup - if self.verbosity>=1: + if self.verbosity >= 1: print(tag, start, stop) # Append list items for this link for leaf in subtags: - xxx = self.crackTags(leaf,buffer) + xxx = self.crackTags(leaf, buffer) self.pnrs.append(xxx) - if self.verbosity==2: + if self.verbosity == 2: # pnrs are composed of smcw and pnr_attr for pnrpart in subtags: - print(" ",pnrpart[0], " -> [ "), + print(" ", pnrpart[0], " -> [ "), for partpart in pnrpart[3]: - print(partpart[0], "(", buffer[partpart[1]:partpart[2]], ")"), + print(partpart[0], "(", buffer[partpart[1] : partpart[2]], ")"), print(" ]") def zac(self, tup, buffer): - (tag,start,stop,subtags) = tup + (tag, start, stop, subtags) = tup - if self.verbosity>=1: + if self.verbosity >= 1: print(tag, start, stop) - if self.verbosity==2: + if self.verbosity == 2: # zacs are composed of smcw and zac_attr for zacpart in subtags: - print(" ",zacpart[0], " -> [ "), + print(" ", zacpart[0], " -> [ "), for partpart in zacpart[3]: - print(partpart[0], "(", buffer[partpart[1]:partpart[2]], ")"), + print(partpart[0], "(", buffer[partpart[1] : partpart[2]], ")"), print(" ]") # Append list items for this link for leaf in subtags: - xxx = self.crackTags(leaf,buffer) + xxx = self.crackTags(leaf, buffer) self.zacs.append(xxx) def process_line(self, tup, buffer): """ Generic version, returns list of pieces. """ - (tag,start,stop,subtags) = tup + (tag, start, stop, subtags) = tup - if self.verbosity>=1: + if self.verbosity >= 1: print(tag, start, stop) - if self.verbosity==2: + if self.verbosity == 2: for part in subtags: - print(" ",part[0], " -> [ "), + print(" ", part[0], " -> [ "), for partpart in part[3]: - print(partpart[0], "(", buffer[partpart[1]:partpart[2]], ")"), + print(partpart[0], "(", buffer[partpart[1] : partpart[2]], ")"), print(" ]") - + # Append list items for this link # TODO: make the others more like this -- let the list separate the parse structures! retlist = [] for leaf in subtags: - xxx = self.crackTags(leaf,buffer) + xxx = self.crackTags(leaf, buffer) retlist.append(xxx) return retlist @@ -286,60 +287,64 @@ def vehicletype(self, tup, buffer): self.vehicletypes.append(myvt) def smcw(self, tup, buffer): - """ Semicolon comment whitespace - """ - (tag,start,stop,subtags) = tup + """Semicolon comment whitespace""" + (tag, start, stop, subtags) = tup - if self.verbosity>=1: + if self.verbosity >= 1: print(tag, start, stop) - + for leaf in subtags: - xxx = self.crackTags(leaf,buffer) + xxx = self.crackTags(leaf, buffer) self.linecomments.append(xxx) - + def accessli(self, tup, buffer): - (tag,start,stop,subtags) = tup + (tag, start, stop, subtags) = tup - if self.verbosity>=1: + if self.verbosity >= 1: print(tag, start, stop) - + for leaf in subtags: - xxx = self.crackTags(leaf,buffer) - if self.liType=="access": + xxx = self.crackTags(leaf, buffer) + if self.liType == "access": self.accesslis.append(xxx) - elif self.liType=="xfer": + elif self.liType == "xfer": self.xferlis.append(xxx) - elif self.liType=="node": + elif self.liType == "node": self.nodes.append(xxx) else: - raise NetworkException("Found access or xfer link without classification. {}".format(self.liType)) + raise NetworkException( + "Found access or xfer link without classification. {}".format( + self.liType + ) + ) + class TransitParser(Parser): # line files are one of these - PROGRAM_PT = "PT" + PROGRAM_PT = "PT" PROGRAM_TRNBUILD = "TRNBUILD" - PROGRAM_UNKNOWN = "unknown" + PROGRAM_UNKNOWN = "unknown" def __init__(self, filedef=transit_file_def, verbosity=1): Parser.__init__(self, filedef) - self.verbosity=verbosity + self.verbosity = verbosity self.tfp = TransitFileProcessor(self.verbosity) - def setVerbosity(self,verbosity): - self.verbosity=verbosity - self.tfp.verbosity=verbosity + def setVerbosity(self, verbosity): + self.verbosity = verbosity + self.tfp.verbosity = verbosity def buildProcessor(self): return self.tfp def convertLineData(self): - """ Convert the parsed tree of data into a usable python list of transit lines - returns (PROGRAM_PT or PROGRAM_TRNBUILD, list of comments and transit line objects) + """Convert the parsed tree of data into a usable python list of transit lines + returns (PROGRAM_PT or PROGRAM_TRNBUILD, list of comments and transit line objects) """ program = TransitParser.PROGRAM_UNKNOWN # default rows = [] - currentRoute = None + currentRoute = None currentComments = [] # try to figure out what type of file this is -- TRNBUILD or PT @@ -348,7 +353,7 @@ def convertLineData(self): cmt = comment[2][0][1] # print("cmt={}".format(cmt)) # note the first semicolon is stripped - if cmt.startswith(';<>;;'): + if cmt.startswith(";<>;;"): program = TransitParser.PROGRAM_TRNBUILD elif cmt.startswith(";<><>;;"): program = TransitParser.PROGRAM_PT @@ -361,7 +366,7 @@ def convertLineData(self): line_num += 1 # Add comments as simple strings - if line[0] == 'smcw': + if line[0] == "smcw": cmt = line[1].strip() # WranglerLogger.debug("smcw line={}".format(line)) @@ -374,24 +379,27 @@ def convertLineData(self): continue # Handle Line attributes - if line[0] == 'lin_attr': + if line[0] == "lin_attr": key = None value = None comment = None # Pay attention only to the children of lin_attr elements kids = line[2] for child in kids: - if child[0]=='lin_attr_name': key=child[1] - if child[0]=='attr_value': value=child[1] - if child[0]=='semicolon_comment': comment=child[1].strip() + if child[0] == "lin_attr_name": + key = child[1] + if child[0] == "attr_value": + value = child[1] + if child[0] == "semicolon_comment": + comment = child[1].strip() # If this is a NAME attribute, we need to start a new TransitLine! - if key=='NAME': + if key == "NAME": if currentRoute: rows.append(currentRoute) # now add the comments stored up - if len(currentComments)>0: + if len(currentComments) > 0: # WranglerLogger.debug("currentComments: {}".format(currentComments)) rows.extend(currentComments) currentComments = [] @@ -401,7 +409,8 @@ def convertLineData(self): currentRoute[key] = value # Just store all other attributes # And save line comment if there is one - if comment: currentRoute.comment = comment + if comment: + currentRoute.comment = comment continue # Handle Node list @@ -410,30 +419,37 @@ def convertLineData(self): kids = line[2] node = None for child in kids: - if child[0]=='nodenum': + if child[0] == "nodenum": node = Node(child[1]) - if child[0]=='lin_nodeattr': + if child[0] == "lin_nodeattr": key = None value = None for nodechild in child[2]: - if nodechild[0]=='lin_nodeattr_name': key = nodechild[1] - if nodechild[0]=='attr_value': value = nodechild[1] - if nodechild[0]=='semicolon_comment': comment=nodechild[1].strip() + if nodechild[0] == "lin_nodeattr_name": + key = nodechild[1] + if nodechild[0] == "attr_value": + value = nodechild[1] + if nodechild[0] == "semicolon_comment": + comment = nodechild[1].strip() node[key] = value - if comment: node.comment = comment + if comment: + node.comment = comment currentRoute.n.append(node) continue # Got something other than lin_node, lin_attr, or smcw: - WranglerLogger.critical("** SHOULD NOT BE HERE: %s (%s)" % (line[0], line[1])) + WranglerLogger.critical( + "** SHOULD NOT BE HERE: %s (%s)" % (line[0], line[1]) + ) # End of tree; store final route and return - if currentRoute: rows.append(currentRoute) + if currentRoute: + rows.append(currentRoute) return (program, rows) def convertLinkData(self): - """ Convert the parsed tree of data into a usable python list of transit links - returns list of comments and transit link & factor objects + """Convert the parsed tree of data into a usable python list of transit links + returns list of comments and transit link & factor objects """ rows = [] currentLink = None @@ -446,9 +462,9 @@ def convertLinkData(self): # Each link is a 3-tuple: key, value, list-of-children. # Add comments as simple strings: - if link[0] in ('smcw','semicolon_comment'): + if link[0] in ("smcw", "semicolon_comment"): if currentLink: - currentLink.comment = " "+link[1].strip() # Link comment + currentLink.comment = " " + link[1].strip() # Link comment rows.append(currentLink) currentLink = None else: @@ -456,70 +472,81 @@ def convertLinkData(self): continue # Link records - if link[0] == 'link_attr': + if link[0] == "link_attr": # Pay attention only to the children of lin_attr elements kids = link[2] for child in kids: - if child[0] in ('link_attr_name','word_nodes','word_modes'): + if child[0] in ("link_attr_name", "word_nodes", "word_modes"): key = child[1] # If this is a NAME attribute, we need to start a new TransitLink. - if key in ('nodes','NODES'): - if currentLink: rows.append(currentLink) - currentLink = TransitLink() # Create new dictionary for this transit support link - - if child[0]=='nodepair': + if key in ("nodes", "NODES"): + if currentLink: + rows.append(currentLink) + currentLink = ( + TransitLink() + ) # Create new dictionary for this transit support link + + if child[0] == "nodepair": currentLink.setId(child[1]) - if child[0] in ('attr_value','numseq'): + if child[0] in ("attr_value", "numseq"): currentLink[key] = child[1] continue # Got something unexpected: - WranglerLogger.critical("** SHOULD NOT BE HERE: %s (%s)" % (link[0], link[1])) + WranglerLogger.critical( + "** SHOULD NOT BE HERE: %s (%s)" % (link[0], link[1]) + ) # Save last link too - if currentLink: rows.append(currentLink) - + if currentLink: + rows.append(currentLink) for factor in self.tfp.factors: currentFactor = Factor() # factor[0]: - # ('smcw', '; BART-eBART timed transfer\n', + # ('smcw', '; BART-eBART timed transfer\n', # [('semicolon_comment', '; BART-eBART timed transfer\n', # [('comment', ' BART-eBART timed transfer', [])])]) # keep as line comment - if factor[0][0] == 'smcw': + if factor[0][0] == "smcw": smcw = factor.pop(0) rows.append(smcw[1].strip()) # the rest are attributes - # [('factor_attr', 'MAXWAITTIME=1, ', [('factor_attr_name', 'MAXWAITTIME', []), ('attr_value', '1', [('alphanums', '1', [])])]), + # [('factor_attr', 'MAXWAITTIME=1, ', [('factor_attr_name', 'MAXWAITTIME', []), ('attr_value', '1', [('alphanums', '1', [])])]), # ('factor_attr', 'NODES=15536\n', [('factor_attr_name', 'NODES', [('word_nodes', 'NODES', [])]), ('attr_value', '15536', [('alphanums', '15536', [])])])] for factor_attr in factor: - if factor_attr[0] == 'semicolon_comment': + if factor_attr[0] == "semicolon_comment": comments.append(factor_attr[1]) continue - if factor_attr[0] != 'factor_attr': - WranglerLogger.critical("** unexpected factor item: {}".format(factor_attr)) + if factor_attr[0] != "factor_attr": + WranglerLogger.critical( + "** unexpected factor item: {}".format(factor_attr) + ) - factor_attr_name = factor_attr[2][0] # ('factor_attr_name', 'MAXWAITTIME', []) - factor_attr_val = factor_attr[2][1] # ('attr_value', '1', [('alphanums', '1', [])]) + factor_attr_name = factor_attr[2][ + 0 + ] # ('factor_attr_name', 'MAXWAITTIME', []) + factor_attr_val = factor_attr[2][ + 1 + ] # ('attr_value', '1', [('alphanums', '1', [])]) # set it currentFactor[factor_attr_name[1]] = factor_attr_val[1] rows.append(currentFactor) - if len(comments)>0: + if len(comments) > 0: rows.extend(comments) comments = [] return rows def convertPNRData(self): - """ Convert the parsed tree of data into a usable python list of PNR objects - returns list of strings and PNR objects + """Convert the parsed tree of data into a usable python list of PNR objects + returns list of strings and PNR objects """ rows = [] currentPNR = None @@ -531,7 +558,7 @@ def convertPNRData(self): # Add comments as simple strings # Textline Comments - if pnr[0] =='smcw': + if pnr[0] == "smcw": # Line comment; thus existing PNR must be finished. if currentPNR: rows.append(currentPNR) @@ -541,28 +568,28 @@ def convertPNRData(self): continue # PNR records - if pnr[0] == 'pnr_attr': + if pnr[0] == "pnr_attr": # Pay attention only to the children of attr elements kids = pnr[2] for child in kids: - if child[0] in ('pnr_attr_name','word_node','word_zones'): + if child[0] in ("pnr_attr_name", "word_node", "word_zones"): key = child[1] # If this is a NAME attribute, we need to start a new PNR. - if key in ('node','NODE'): + if key in ("node", "NODE"): if currentPNR: rows.append(currentPNR) - currentPNR = PNRLink() # Create new dictionary for this PNR + currentPNR = PNRLink() # Create new dictionary for this PNR - if child[0]=='nodepair' or child[0]=='nodenum': - #print "child[0]/[1]",child[0],child[1] + if child[0] == "nodepair" or child[0] == "nodenum": + # print "child[0]/[1]",child[0],child[1] currentPNR.id = child[1] currentPNR.parseID() - if child[0] in ('attr_value','numseq'): + if child[0] in ("attr_value", "numseq"): currentPNR[key.upper()] = child[1] - if child[0]=='semicolon_comment': - currentPNR.comment = ' '+child[1].strip() + if child[0] == "semicolon_comment": + currentPNR.comment = " " + child[1].strip() continue @@ -570,12 +597,13 @@ def convertPNRData(self): WranglerLogger.critical("** SHOULD NOT BE HERE: %s (%s)" % (pnr[0], pnr[1])) # Save last link too - if currentPNR: rows.append(currentPNR) + if currentPNR: + rows.append(currentPNR) return rows def convertZACData(self): - """ Convert the parsed tree of data into a usable python list of ZAC objects - returns list of strings and ZAC objects + """Convert the parsed tree of data into a usable python list of ZAC objects + returns list of strings and ZAC objects """ rows = [] currentZAC = None @@ -587,9 +615,9 @@ def convertZACData(self): # Add comments as simple strings # Textline Comments - if zac[0] in ('smcw','semicolon_comment'): + if zac[0] in ("smcw", "semicolon_comment"): if currentZAC: - currentZAC.comment = ' '+zac[1].strip() + currentZAC.comment = " " + zac[1].strip() rows.append(currentZAC) currentZAC = None else: @@ -598,21 +626,22 @@ def convertZACData(self): continue # Link records - if zac[0] == 'zac_attr': + if zac[0] == "zac_attr": # Pay attention only to the children of lin_attr elements kids = zac[2] for child in kids: - if child[0]=='nodepair': + if child[0] == "nodepair": # Save old ZAC - if currentZAC: rows.append(currentZAC) + if currentZAC: + rows.append(currentZAC) # Start new ZAC - currentZAC = ZACLink() # Create new dictionary for this ZAC. - currentZAC.id=child[1] + currentZAC = ZACLink() # Create new dictionary for this ZAC. + currentZAC.id = child[1] - if child[0] =='zac_attr_name': + if child[0] == "zac_attr_name": key = child[1] - if child[0]=='attr_value': + if child[0] == "attr_value": currentZAC[key] = child[1] continue @@ -621,12 +650,13 @@ def convertZACData(self): WranglerLogger.critical("** SHOULD NOT BE HERE: %s (%s)" % (zac[0], zac[1])) # Save last link too - if currentZAC: rows.append(currentZAC) + if currentZAC: + rows.append(currentZAC) return rows def convertLinkiData(self, linktype): - """ Convert the parsed tree of data into a usable python list of ZAC objects - returns list of strings and ZAC objects + """Convert the parsed tree of data into a usable python list of ZAC objects + returns list of strings and ZAC objects """ rows = [] currentLinki = None @@ -634,42 +664,44 @@ def convertLinkiData(self, linktype): value = None linkis = [] - if linktype=="access": - linkis=self.tfp.accesslis - elif linktype=="xfer": - linkis=self.tfp.xferlis - elif linktype=="node": - linkis=self.tfp.nodes + if linktype == "access": + linkis = self.tfp.accesslis + elif linktype == "xfer": + linkis = self.tfp.xferlis + elif linktype == "node": + linkis = self.tfp.nodes else: raise NetworkException("ConvertLinkiData with invalid linktype") - + for accessli in linkis: # whitespace?, smcw?, nodenumA, spaces?, nodenumB, spaces?, (float/int)?, spaces?, semicolon_comment? - if accessli[0]=='smcw': + if accessli[0] == "smcw": rows.append(accessli[1].strip()) - elif accessli[0]=='nodenumA': + elif accessli[0] == "nodenumA": currentLinki = Linki() rows.append(currentLinki) currentLinki.A = accessli[1].strip() - elif accessli[0]=='nodenumB': + elif accessli[0] == "nodenumB": currentLinki.B = accessli[1].strip() - elif accessli[0]=='float': + elif accessli[0] == "float": currentLinki.distance = accessli[1].strip() - elif accessli[0]=='int': + elif accessli[0] == "int": currentLinki.xferTime = accessli[1].strip() - elif accessli[0]=='semicolon_comment': + elif accessli[0] == "semicolon_comment": currentLinki.comment = accessli[1].strip() - elif accessli[0]=='accesstag': + elif accessli[0] == "accesstag": currentLinki.accessType = accessli[1].strip() else: # Got something unexpected: - WranglerLogger.critical("** SHOULD NOT BE HERE: %s (%s)" % (accessli[0], accessli[1])) + WranglerLogger.critical( + "** SHOULD NOT BE HERE: %s (%s)" % (accessli[0], accessli[1]) + ) return rows - + def convertSupplinksData(self): - """ Convert the parsed tree of data into a usable python list of Supplink objects - returns list of strings and Supplink objects + """Convert the parsed tree of data into a usable python list of Supplink objects + returns list of strings and Supplink objects """ rows = [] currentSupplink = None @@ -678,34 +710,41 @@ def convertSupplinksData(self): for supplink in self.tfp.supplinks: - # Supplink records are lists - if currentSupplink: rows.append(currentSupplink) - currentSupplink = Supplink() # Create new dictionary for this PNR - + # Supplink records are lists + if currentSupplink: + rows.append(currentSupplink) + currentSupplink = Supplink() # Create new dictionary for this PNR + for supplink_attr in supplink: - if supplink_attr[0] == 'supplink_attr': - if supplink_attr[2][0][0]=='supplink_attr_name': + if supplink_attr[0] == "supplink_attr": + if supplink_attr[2][0][0] == "supplink_attr_name": currentSupplink[supplink_attr[2][0][1]] = supplink_attr[2][1][1] - elif supplink_attr[2][0][0]=='npair_attr_name': + elif supplink_attr[2][0][0] == "npair_attr_name": currentSupplink.setId(supplink_attr[2][1][1]) else: - WranglerLogger.critical("** SHOULD NOT BE HERE: %s (%s)" % (supplink[0], supplink[1])) + WranglerLogger.critical( + "** SHOULD NOT BE HERE: %s (%s)" + % (supplink[0], supplink[1]) + ) raise elif supplink_attr[0] == "semicolon_comment": currentSupplink.comment = supplink_attr[1].strip() - elif supplink_attr[0] == 'smcw': + elif supplink_attr[0] == "smcw": currentSupplink.comment = supplink_attr[1].strip() else: - WranglerLogger.critical("** SHOULD NOT BE HERE: %s (%s)" % (supplink[0], supplink[1])) + WranglerLogger.critical( + "** SHOULD NOT BE HERE: %s (%s)" % (supplink[0], supplink[1]) + ) raise - + # Save last link too - if currentSupplink: rows.append(currentSupplink) + if currentSupplink: + rows.append(currentSupplink) return rows def convertFaresystemData(self): - """ Convert the parsed tree of data into a usable python list of Faresystem objects - returns list of strings and Faresystem objects + """Convert the parsed tree of data into a usable python list of Faresystem objects + returns list of strings and Faresystem objects """ rows = {} currentFaresystem = None @@ -713,35 +752,37 @@ def convertFaresystemData(self): for faresystem in self.tfp.faresystems: # faresystem records are lists - if currentFaresystem: rows[currentFaresystem.getId()] = currentFaresystem + if currentFaresystem: + rows[currentFaresystem.getId()] = currentFaresystem currentFaresystem = Faresystem() for fs_attr in faresystem: - if fs_attr[0] == 'faresystem_attr': - if fs_attr[2][0][0]=='faresystem_attr_name': + if fs_attr[0] == "faresystem_attr": + if fs_attr[2][0][0] == "faresystem_attr_name": currentFaresystem[fs_attr[2][0][1]] = fs_attr[2][1][1] # for now, save this as FAREFROMFS => "0,0,1.0,0," etc - elif fs_attr[2][0][0]=='faresystem_fff': + elif fs_attr[2][0][0] == "faresystem_fff": # fs_attr[2] = [('faresystem_fff', 'FAREFROMFS', []), - # ('floatseq', '0,0,0,0,..,0,0', [('floatnum', '0', []), ('floatnum', '0', []), .. + # ('floatseq', '0,0,0,0,..,0,0', [('floatnum', '0', []), ('floatnum', '0', []), .. currentFaresystem[fs_attr[2][0][1]] = fs_attr[2][1][1] elif fs_attr[0] == "semicolon_comment": currentFaresystem.comment = fs_attr[1].strip() - elif fs_attr[0] == 'smcw': + elif fs_attr[0] == "smcw": currentFaresystem.comment = fs_attr[1].strip() else: WranglerLogger.critical("** SHOULD NOT BE HERE: %s".format(fs_attr)) raise # save last faresystem too - if currentFaresystem: rows[currentFaresystem.getId()] = currentFaresystem + if currentFaresystem: + rows[currentFaresystem.getId()] = currentFaresystem return rows def convertPTSystemData(self): - """ Convert the parsed tree of data into a PTSystem object - returns a PTSystem object + """Convert the parsed tree of data into a PTSystem object + returns a PTSystem object """ pts = PTSystem() @@ -750,10 +791,12 @@ def convertPTSystemData(self): curve_dict = collections.OrderedDict() for attr in crvdef: # just handle curve attributes - if attr[0] !="crv_attr": continue + if attr[0] != "crv_attr": + continue key = attr[2][0][1] val = attr[2][1][1] - if key == "NUMBER": curve_num = int(val) + if key == "NUMBER": + curve_num = int(val) curve_dict[key] = val pts.waitCurveDefs[curve_num] = curve_dict @@ -762,53 +805,66 @@ def convertPTSystemData(self): curve_dict = collections.OrderedDict() for attr in crvdef: # just handle curve attributes - if attr[0] !="crv_attr": continue + if attr[0] != "crv_attr": + continue key = attr[2][0][1] val = attr[2][1][1] - if key == "NUMBER": curve_num = int(val) + if key == "NUMBER": + curve_num = int(val) curve_dict[key] = val pts.crowdCurveDefs[curve_num] = curve_dict for operator in self.tfp.operators: - op_num = None + op_num = None op_dict = collections.OrderedDict() for attr in operator: # just handle opmode attributes - if attr[0] !="opmode_attr": continue + if attr[0] != "opmode_attr": + continue key = attr[2][0][1] val = attr[2][1][1] - if key == "NUMBER": op_num = int(val) - op_dict[key] = val # leave as string + if key == "NUMBER": + op_num = int(val) + op_dict[key] = val # leave as string pts.operators[op_num] = op_dict for mode in self.tfp.modes: - mode_num = None + mode_num = None mode_dict = collections.OrderedDict() for attr in mode: # just handle opmode attributes - if attr[0] !="opmode_attr": continue + if attr[0] != "opmode_attr": + continue key = attr[2][0][1] val = attr[2][1][1] - if key == "NUMBER": mode_num = int(val) - mode_dict[key] = val # leave as string + if key == "NUMBER": + mode_num = int(val) + mode_dict[key] = val # leave as string pts.modes[mode_num] = mode_dict for vehicletype in self.tfp.vehicletypes: - vt_num = None + vt_num = None vt_dict = collections.OrderedDict() for attr in vehicletype: # just handle vehtype attributes - if attr[0] != "vehtype_attr": continue + if attr[0] != "vehtype_attr": + continue key = attr[2][0][1] val = attr[2][1][1] - if key == "NUMBER": vt_num = int(val) - vt_dict[key] = val # leave as string + if key == "NUMBER": + vt_num = int(val) + vt_dict[key] = val # leave as string pts.vehicleTypes[vt_num] = vt_dict - if len(pts.waitCurveDefs) > 0 or len(pts.crowdCurveDefs) > 0 or len(pts.operators) > 0 or len(pts.modes) > 0 or len(pts.vehicleTypes) > 0: + if ( + len(pts.waitCurveDefs) > 0 + or len(pts.crowdCurveDefs) > 0 + or len(pts.operators) > 0 + or len(pts.modes) > 0 + or len(pts.vehicleTypes) > 0 + ): return pts return None - diff --git a/Wrangler/ZACLink.py b/Wrangler/ZACLink.py index b58f9f6..85018a5 100644 --- a/Wrangler/ZACLink.py +++ b/Wrangler/ZACLink.py @@ -1,22 +1,24 @@ -__all__ = ['ZACLink'] +__all__ = ["ZACLink"] + class ZACLink(dict): - """ ZAC support Link. - 'link' property is the node-pair for this link (e.g. 24133-34133) - 'comment' is any end-of-line comment for this link - (must include the leading semicolon) - All other attributes are stored in a dictionary (e.g. thislink['MODE']='17') + """ZAC support Link. + 'link' property is the node-pair for this link (e.g. 24133-34133) + 'comment' is any end-of-line comment for this link + (must include the leading semicolon) + All other attributes are stored in a dictionary (e.g. thislink['MODE']='17') """ + def __init__(self): dict.__init__(self) - self.id='' - self.comment='' + self.id = "" + self.comment = "" def __repr__(self): s = "ZONEACCESS link=%s " % (self.id,) # Deal w/all link attributes - fields = ['%s=%s' % (k,v) for k,v in self.items()] + fields = ["%s=%s" % (k, v) for k, v in self.items()] s += " ".join(fields) s += self.comment diff --git a/Wrangler/__init__.py b/Wrangler/__init__.py index afd116f..f29f7da 100644 --- a/Wrangler/__init__.py +++ b/Wrangler/__init__.py @@ -8,9 +8,13 @@ from .Supplink import Supplink # add ..\_static for dataTable import -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..","_static"))) # for dataTable +sys.path.append( + os.path.abspath( + os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "_static") + ) +) # for dataTable -from .TransitAssignmentData import TransitAssignmentData ## +from .TransitAssignmentData import TransitAssignmentData ## from .TransitCapacity import TransitCapacity from .TransitLine import TransitLine from .TransitLink import TransitLink @@ -22,21 +26,38 @@ from .HwySpecsRTP import HwySpecsRTP -__all__ = ['NetworkException', 'setupLogging', 'WranglerLogger', - 'Network', 'TransitAssignmentData', 'TransitNetwork', 'TransitLine', 'TransitParser', - 'Node', 'TransitLink', 'Linki', 'PNRLink', 'Supplink', 'HighwayNetwork', 'HwySpecsRTP', - 'TransitCapacity', 'Faresystem', 'PTSystem' +__all__ = [ + "NetworkException", + "setupLogging", + "WranglerLogger", + "Network", + "TransitAssignmentData", + "TransitNetwork", + "TransitLine", + "TransitParser", + "Node", + "TransitLink", + "Linki", + "PNRLink", + "Supplink", + "HighwayNetwork", + "HwySpecsRTP", + "TransitCapacity", + "Faresystem", + "PTSystem", ] -if __name__ == '__main__': +if __name__ == "__main__": LOG_FILENAME = "Wrangler_main_%s.info.LOG" % time.strftime("%Y%b%d.%H%M%S") setupLogging(LOG_FILENAME, LOG_FILENAME.replace("info", "debug")) - + net = Network() - net.cloneAndApplyProject(projectname="Muni_TEP") - net.cloneAndApplyProject(projectname="Muni_CentralSubway", tag="1-latest", modelyear=2030) - net.cloneAndApplyProject(projectname="BART_eBART") + net.cloneAndApplyProject(projectname="Muni_TEP") + net.cloneAndApplyProject( + projectname="Muni_CentralSubway", tag="1-latest", modelyear=2030 + ) + net.cloneAndApplyProject(projectname="BART_eBART") net.write(name="muni", writeEmptyFiles=False) diff --git a/__init__.py b/__init__.py index 2c81991..80e1693 100644 --- a/__init__.py +++ b/__init__.py @@ -1,3 +1,3 @@ -__all__ = ["Wrangler"] -__version__ = 2.0 +__all__ = ["Wrangler"] +__version__ = 2.0 version = __version__ diff --git a/_static/Cube/CubeNet.py b/_static/Cube/CubeNet.py index c77a136..97793b8 100644 --- a/_static/Cube/CubeNet.py +++ b/_static/Cube/CubeNet.py @@ -13,42 +13,50 @@ CUBE_COMPUTER = "vanness" CUBE_SUCCESS = re.compile("\s*(VOYAGER)\s+(ReturnCode)\s*=\s*([01])\s+") + def getCubeHostnames(): """ Cube hostnames in Y:\COMMPATH\HostnamesWithCube.txt """ hostnames = [] - fqdn = getfqdn().lower() # fully qualified domain name + fqdn = getfqdn().lower() # fully qualified domain name # at mtc, assume cube license is available - if fqdn.endswith("mtc.ca.gov"): return [ gethostname().lower() ] + if fqdn.endswith("mtc.ca.gov"): + return [gethostname().lower()] f = open(r"Y:\COMMPATH\HostnamesWithCube.txt") for line in f: - if line[0] == "#": continue + if line[0] == "#": + continue hostnames.append(line.split()[0]) # use the first token of non-comment lines f.close() return hostnames - -def export_cubenet_to_csvs(file, extra_link_vars=[], extra_node_vars=[], - links_csv=None, nodes_csv=None): + + +def export_cubenet_to_csvs( + file, extra_link_vars=[], extra_node_vars=[], links_csv=None, nodes_csv=None +): """ Export cube network to csv files If *links_csv* and *nodes_csv* filenames passed, will use those. Otherwise, will output into %TEMP%\link.csv and %TEMP%\node.csv - + options: extra_link_vars, extra_node_vars: list extra variables to export """ import subprocess - script = os.path.join(os.path.dirname(os.path.abspath(__file__)),"exportHwyfromPy.s") - - #set environment variables + + script = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "exportHwyfromPy.s" + ) + + # set environment variables env = copy.copy(os.environ) - - env['CUBENET']=file - env['PATH'] = os.environ['PATH'] # inherit this - + + env["CUBENET"] = file + env["PATH"] = os.environ["PATH"] # inherit this + if links_csv: env["CUBELINK_CSV"] = links_csv else: @@ -57,62 +65,84 @@ def export_cubenet_to_csvs(file, extra_link_vars=[], extra_node_vars=[], env["CUBENODE_CSV"] = nodes_csv else: env["CUBENODE_CSV"] = os.path.join(os.environ["TEMP"], "node.csv") - - if len(extra_link_vars)>0: - extra_vars_str="," - extra_vars_str+=extra_vars_str.join(extra_link_vars) - env['XTRALINKVAR']=extra_vars_str + + if len(extra_link_vars) > 0: + extra_vars_str = "," + extra_vars_str += extra_vars_str.join(extra_link_vars) + env["XTRALINKVAR"] = extra_vars_str else: - env['XTRALINKVAR']='' + env["XTRALINKVAR"] = "" - if len(extra_node_vars)>0: - extra_vars_str="," - extra_vars_str+=extra_vars_str.join(extra_node_vars) - env['XTRANODEVAR']=extra_vars_str + if len(extra_node_vars) > 0: + extra_vars_str = "," + extra_vars_str += extra_vars_str.join(extra_node_vars) + env["XTRANODEVAR"] = extra_vars_str else: - env['XTRANODEVAR']=' ' - - #run it on CUBE_COMPUTER; cube is installed there + env["XTRANODEVAR"] = " " + + # run it on CUBE_COMPUTER; cube is installed there filedir = os.path.dirname(os.path.abspath(file)) hostname = gethostname().lower() # retry in case of a license error NUM_RETRIES = 5 - for attempt in range(1,NUM_RETRIES+1): + for attempt in range(1, NUM_RETRIES + 1): cube_stdout = [] license_error = False if hostname not in getCubeHostnames(): if links_csv == None or nodes_csv == None: - print("export_cubenet_to_csvs requires a links_csv and nodes_csv output file if dispatching to {} (temp won't work)".format(CUBE_COMPUTER)) + print( + "export_cubenet_to_csvs requires a links_csv and nodes_csv output file if dispatching to {} (temp won't work)".format( + CUBE_COMPUTER + ) + ) sys.exit(2) - + env["MACHINES"] = CUBE_COMPUTER - + cmd = r'y:\champ\util\bin\dispatch-one.bat "runtpp ' + script + '"' print(cmd) - proc = subprocess.Popen( cmd, cwd = filedir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) + proc = subprocess.Popen( + cmd, + cwd=filedir, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env, + ) for line in proc.stdout: - if type(line)==bytes: line = line.decode() # convert to string, not byetes - line = line.strip('\r\n') + if type(line) == bytes: + line = line.decode() # convert to string, not byetes + line = line.strip("\r\n") print("stdout: {}".format(line)) cube_stdout.append(line) - if line=="RUNTPP: Licensing error": license_error = True + if line == "RUNTPP: Licensing error": + license_error = True else: - cmd = 'runtpp.exe ' + script + cmd = "runtpp.exe " + script print(cmd) print(filedir) - - proc = subprocess.Popen( cmd, cwd = filedir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) + + proc = subprocess.Popen( + cmd, + cwd=filedir, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env, + ) for line in proc.stdout: - if type(line)==bytes: line = line.decode() # convert to string, not byetes - line = line.strip('\r\n') + if type(line) == bytes: + line = line.decode() # convert to string, not byetes + line = line.strip("\r\n") print("stdout: {}".format(line)) cube_stdout.append(line) - if line=="RUNTPP: Licensing error": license_error = True + if line == "RUNTPP: Licensing error": + license_error = True - print("EXPORTING CUBE NETWORK: {}".format(env['CUBENET'])) - print("...adding variables {}, {}:".format(env['XTRALINKVAR'], env['XTRANODEVAR'])) + print("EXPORTING CUBE NETWORK: {}".format(env["CUBENET"])) + print( + "...adding variables {}, {}:".format(env["XTRALINKVAR"], env["XTRANODEVAR"]) + ) print("...running script: \n {}".format(script)) # retry on license error @@ -126,90 +156,110 @@ def export_cubenet_to_csvs(file, extra_link_vars=[], extra_node_vars=[], print("Retrying {} ...".format(attempt)) time.sleep(1) continue - + retStderr = [] for line in proc.stderr: - if type(line)==bytes: line = line.decode() # convert to string, not byetes - line = line.strip('\r\n') + if type(line) == bytes: + line = line.decode() # convert to string, not byetes + line = line.strip("\r\n") print("stderr: {}".format(line)) - retcode = proc.wait() + retcode = proc.wait() - if (retcode != 0) and len(cube_stdout)>0: + if (retcode != 0) and len(cube_stdout) > 0: # retcode may be wrong -- check last stdout print("checking cube_stdout[-1]: {}".format(cube_stdout[-1])) # print("match: {}".format(re.match(retcode,cube_stdout[-1]))) - if re.match(CUBE_SUCCESS,cube_stdout[-1]): - print("Overriding cuberet {} with 0 due to last cubeStdout line".format(retcode)) + if re.match(CUBE_SUCCESS, cube_stdout[-1]): + print( + "Overriding cuberet {} with 0 due to last cubeStdout line".format( + retcode + ) + ) retcode = 0 # success -- stop looping - if retcode == 0: break + if retcode == 0: + break # not license error -- break - if retcode != 0: raise - + if retcode != 0: + raise print("Received {} from [{}]".format(retcode, cmd)) - print("Exported network to: {}, {}".format(env["CUBELINK_CSV"], env["CUBENODE_CSV"])) + print( + "Exported network to: {}, {}".format(env["CUBELINK_CSV"], env["CUBENODE_CSV"]) + ) + - -def import_cube_nodes_links_from_csvs(cubeNetFile, - extra_link_vars=[], extra_node_vars=[], - links_csv=None, nodes_csv=None, - exportIfExists=True): +def import_cube_nodes_links_from_csvs( + cubeNetFile, + extra_link_vars=[], + extra_node_vars=[], + links_csv=None, + nodes_csv=None, + exportIfExists=True, +): """ Imports cube network from network file and returns (nodes_dict, links_dict). - + Nodes_dict maps node numbers to [X, Y, vars given by *extra_node_vars*] - + Links_dict maps (a,b) to [DISTANCE, *extra_link_vars*] """ if not links_csv: - links_csv=os.path.join(os.environ['TEMP'],"node.csv") + links_csv = os.path.join(os.environ["TEMP"], "node.csv") if not nodes_csv: - nodes_csv=os.path.join(os.environ['TEMP'],"link.csv") + nodes_csv = os.path.join(os.environ["TEMP"], "link.csv") # don't export if - if (not exportIfExists and links_csv and nodes_csv and - os.path.exists(links_csv) and os.path.exists(nodes_csv)): - pass # don't need to do anything + if ( + not exportIfExists + and links_csv + and nodes_csv + and os.path.exists(links_csv) + and os.path.exists(nodes_csv) + ): + pass # don't need to do anything else: - export_cubenet_to_csvs(cubeNetFile,extra_link_vars, extra_node_vars, links_csv=links_csv, nodes_csv=nodes_csv) + export_cubenet_to_csvs( + cubeNetFile, + extra_link_vars, + extra_node_vars, + links_csv=links_csv, + nodes_csv=nodes_csv, + ) - # Open node file and read nodes - nodes_dict = {} - F=open(nodes_csv,mode='r') + nodes_dict = {} + F = open(nodes_csv, mode="r") for rec in F: - r=rec.strip().split(',') - n=int(r[0]) - x=float(r[1]) - y=float(r[2]) - node_array = [x,y] + r = rec.strip().split(",") + n = int(r[0]) + x = float(r[1]) + y = float(r[2]) + node_array = [x, y] node_array.extend(r[3:]) - + nodes_dict[n] = node_array F.close() - + # Open link file and read links links_dict = {} - F=open(links_csv,mode='r') + F = open(links_csv, mode="r") for rec in F: - r=rec.strip().split(',') - - #add standard fields - a=int(r[0]) - b=int(r[1]) - dist=float(r[2]) - - #add additional fields + r = rec.strip().split(",") + + # add standard fields + a = int(r[0]) + b = int(r[1]) + dist = float(r[2]) + + # add additional fields link_array = [dist] link_array.extend(r[3:]) - - links_dict[(a,b)] = link_array + + links_dict[(a, b)] = link_array F.close() return (nodes_dict, links_dict) - - diff --git a/_static/Cube/__init__.py b/_static/Cube/__init__.py index a7fc113..bec5b0e 100644 --- a/_static/Cube/__init__.py +++ b/_static/Cube/__init__.py @@ -7,4 +7,4 @@ from .CubeNet import export_cubenet_to_csvs, import_cube_nodes_links_from_csvs -__all__ = ['export_cubenet_to_csvs', 'import_cube_nodes_links_from_csvs'] \ No newline at end of file +__all__ = ["export_cubenet_to_csvs", "import_cube_nodes_links_from_csvs"] diff --git a/_static/__init__.py b/_static/__init__.py index 80578e3..8f43211 100644 --- a/_static/__init__.py +++ b/_static/__init__.py @@ -1,3 +1,3 @@ from .dataTable import DataTable -__all__ = [ 'DataTable' ] \ No newline at end of file +__all__ = ["DataTable"] diff --git a/_static/dataTable.py b/_static/dataTable.py index 39beb91..583626b 100644 --- a/_static/dataTable.py +++ b/_static/dataTable.py @@ -1,7 +1,8 @@ from itertools import count, tee + try: from itertools import izip -except ImportError: # will be 3.x series +except ImportError: # will be 3.x series izip = zip from collections import defaultdict @@ -9,48 +10,60 @@ import numpy as np import csv, decimal, datetime, sys -from struct import unpack, pack, calcsize +from struct import unpack, pack, calcsize print("Importing ", __file__) + class DataTableError(Exception): pass + class FieldTypeError(DataTableError): pass + class DataTableKeyError(DataTableError): pass + class DataTableValueError(DataTableError): pass + class DataTable(object): """A DataTable wrapper around a numpy array class""" def __init__(self, numRecords, header=None, fieldNames=None, numpyFieldTypes=None): - """Construct a new DataTable. + """Construct a new DataTable. Inputs: numRecords : a positive integer dtype : a dictionary containing the names and types of the fields """ if header: fieldNames, numpyFieldTypes = convertDbfToNumpyDataTypes(header) - npDtype = np.dtype({"names":self.fixFieldNames(fieldNames), "formats":numpyFieldTypes}) + npDtype = np.dtype( + {"names": self.fixFieldNames(fieldNames), "formats": numpyFieldTypes} + ) self.header = header elif fieldNames and numpyFieldTypes: - npDtype = np.dtype({"names":self.fixFieldNames(fieldNames), "formats":numpyFieldTypes}) + npDtype = np.dtype( + {"names": self.fixFieldNames(fieldNames), "formats": numpyFieldTypes} + ) self.header = () else: - raise ValueError("You have to provide either the header or the field " - "names along with their types to instantiate a new " - "data table") - + raise ValueError( + "You have to provide either the header or the field " + "names along with their types to instantiate a new " + "data table" + ) + self.fields = np.zeros((numRecords,), npDtype) self._index = dict(zip(range(self.fields.size), range(self.fields.size))) self._hasIndex = False self._indexFunction = None -# self._updateAttributes() + + # self._updateAttributes() def fixFieldNames(self, fieldNames): """ @@ -58,24 +71,27 @@ def fixFieldNames(self, fieldNames): 1) every name is 10 chars or less 2) every name is unique (errors if not) """ - returnlist = [] + returnlist = [] for fieldname in fieldNames: # first truncate to 10 - if len(fieldname) > 10: fieldname = fieldname[:10] - + if len(fieldname) > 10: + fieldname = fieldname[:10] + # check uniqueness if fieldname in returnlist: - raise DataTableKeyError("Two fields are both called %s - unsupported." % fieldname) - + raise DataTableKeyError( + "Two fields are both called %s - unsupported." % fieldname + ) + # python3: convert bytestring to string if type(fieldname) != type("string"): fieldname = fieldname.decode() returnlist.append(fieldname) - + # print "fixFieldNames: fieldNames=(%d) %s returnlist=(%d) %s" % (len(fieldNames), str(fieldNames), len(returnlist), str(returnlist)) return returnlist - + def __str__(self): """Return the numpy representation of the table""" return str(self.fields) @@ -88,17 +104,17 @@ def __getitem__(self, key): raise DataTableKeyError("Key %s does not exist" % str(key)) def __contains__(self, key): - + try: self.__getitem__(key) except DataTableKeyError: return False return True - + def __setitem__(self, key, value): """Alow the user to set a row or a field in a row the same way one would use if the datatable was a dictionary""" -# raise DataTableError("There is a bug here") + # raise DataTableError("There is a bug here") try: # print("key=[{}] type={}".format(key, type(key))) # print("value=[{}] type={}".format(value, type(value))) @@ -121,7 +137,7 @@ def __len__(self): def getNumpyArray(self): """Return the underlying numpy array""" return self.fields - + def _updateAttributes(self): """Set the field names as attributes""" for name in self.fields.dtype.names: @@ -135,8 +151,8 @@ def getFieldNames(self): """Return the field names of the datatable""" return self.fields.dtype.names - def setIndex(self, fieldName = None, indexFunction = None): - """Define a fieldName the values of which will serve as the index + def setIndex(self, fieldName=None, indexFunction=None): + """Define a fieldName the values of which will serve as the index of the table. Alternativly, you can define a fucntion that takes a row as an input and returns a value serving as the index""" # print("dataTable.setIndex(fieldName={}, indexFunction={})".format(fieldName, indexFunction)) @@ -147,7 +163,7 @@ def setIndex(self, fieldName = None, indexFunction = None): newIndex = self._createIndex(fieldName) self._index = newIndex - #TODO you can simplify this + # TODO you can simplify this self._hasIndex = True self._indexField = fieldName self._indexFunction = None @@ -169,53 +185,64 @@ def _createIndex(self, fieldName=None, indexFunction=None): indices for accessing table elements""" newIndex = {} if fieldName: - #check the uniqueness of the fields values + # check the uniqueness of the fields values if len(set(self.fields[fieldName])) != self.getNumRecords(): - raise DataTableError("The field: %s contains non unique values and therefore" - "canot be set as the index" % fieldName) + raise DataTableError( + "The field: %s contains non unique values and therefore" + "canot be set as the index" % fieldName + ) for i, record in enumerate(self): newIndex[record[fieldName]] = i elif indexFunction: for i, record in enumerate(self): newIndex[indexFunction(record)] = i - #check if the generated keys are unique + # check if the generated keys are unique if not len(set(newIndex.keys())) == self.getNumRecords(): numKeys = defaultdict(int) for record in self: numKeys[indexFunction(record)] += 1 - duplicateKeys = [str(key) for key, count in numKeys.iteritems() if count > 1] - raise DataTableError("The provided index function does not generate" - "unique keys and therefore cannot be applied.\nDuplicate keys %s" - % str(duplicateKeys)) + duplicateKeys = [ + str(key) for key, count in numKeys.iteritems() if count > 1 + ] + raise DataTableError( + "The provided index function does not generate" + "unique keys and therefore cannot be applied.\nDuplicate keys %s" + % str(duplicateKeys) + ) else: - raise DataTableError("A fieldName or an indexFunction have to be" - "provided to index the features") + raise DataTableError( + "A fieldName or an indexFunction have to be" + "provided to index the features" + ) return newIndex - + def getFieldInfo(self): """Return a string with info about field names and their data types""" raise DataTableError("Not implemented yet") - + def addField(self, newFieldName=None, dtype=None): """Add a field to the existing ones""" - #TODO the header needs to be updated + # TODO the header needs to be updated self.header = () fnames = list(self.getFieldNames()) - ftypes = [self.fields.dtype[fname] for fname in fnames] + ftypes = [self.fields.dtype[fname] for fname in fnames] fnames.append(newFieldName) ftypes.append(dtype) - dt = np.zeros((self.getNumRecords(),), dtype={"names":fnames, "formats":ftypes}) - #copy the data + dt = np.zeros( + (self.getNumRecords(),), dtype={"names": fnames, "formats": ftypes} + ) + # copy the data for fname in fnames: if fname is newFieldName: continue dt[fname] = self.fields[fname] - + self.fields = dt -# self._updateAttributes() + + # self._updateAttributes() def addIntegerField(self, fieldName): """Add an interger field to the table with the provided field name""" @@ -227,10 +254,10 @@ def addDoubleField(self, fieldName): self.addField(newFieldName=fieldName, dtype="d") def addStringField(self, fieldName, numCharacters): - """Add a string field with the given name and number + """Add a string field with the given name and number of charaters length""" self.addField(newFieldName=fieldName, dtype="S%d" % numCharacters) - + def sort(self, fieldNames): """Sort the records based on the index?""" self.fields.sort(order=fieldNames) @@ -242,7 +269,7 @@ def sort(self, fieldNames): def writeAsDbf(self, fileName): """Write the table in a dbf file""" - + if self.header == (): raise ValueError("Not implemented yet") dbfWriter = DbfDictWriter(fileName, self.header, self.getNumRecords()) @@ -251,7 +278,8 @@ def writeAsDbf(self, fileName): def writeAsCsv(self, fileName): """Write the table as a csv file""" - import csv + import csv + dialect = csv.excel dialect.lineterminator = "\n" outputStream = open(fileName, "w") @@ -261,12 +289,14 @@ def writeAsCsv(self, fileName): writer.writerow(record) outputStream.close() + class FieldType(object): """Contains information about the data type of each field""" - TYPE_INT = b"N" + + TYPE_INT = b"N" TYPE_DECIMAL = b"N" - TYPE_STRING = b"C" - TYPE_FLOAT = b"F" + TYPE_STRING = b"C" + TYPE_FLOAT = b"F" TYPES = [TYPE_INT, TYPE_DECIMAL, TYPE_STRING, TYPE_FLOAT] @@ -274,7 +304,7 @@ class FieldType(object): LENGTH_DECIMAL = 15 LENGTH_DECIMAL_ = 4 LENGTH_STRING = 50 - + __slots__ = ("name", "type", "length", "numDecimals") def __init__(self, name, _type, length, numDecimals): @@ -283,10 +313,10 @@ def __init__(self, name, _type, length, numDecimals): # convert string to byte if type(_type) == str: - _type = _type.encode('utf-8') + _type = _type.encode("utf-8") if _type not in FieldType.TYPES: - raise FieldTypeError('Unknown field type {} {}'.format(_type, type(_type))) + raise FieldTypeError("Unknown field type {} {}".format(_type, type(_type))) self.type = _type self.length = length @@ -295,19 +325,20 @@ def __init__(self, name, _type, length, numDecimals): def __repr__(self): return str((self.name, self.type, self.length, self.numDecimals)) - + def toTuple(self): return (self.name, self.type, self.length, self.numDecimals) + def dbfToNumpyDataType(fieldType): """Accept a field type object and return the name and the type(format) of the corresponding numpy data type""" # truncate field names to 10 - if len(fieldType.name) > 10: fieldType.name = fieldType.name[:10] - - if fieldType.type == FieldType.TYPE_INT or \ - fieldType.type == fieldType.TYPE_DECIMAL: + if len(fieldType.name) > 10: + fieldType.name = fieldType.name[:10] + + if fieldType.type == FieldType.TYPE_INT or fieldType.type == fieldType.TYPE_DECIMAL: if fieldType.numDecimals == 0: return fieldType.name, "i" else: @@ -315,13 +346,16 @@ def dbfToNumpyDataType(fieldType): elif fieldType.type == FieldType.TYPE_FLOAT: return fieldType.name, "d" elif fieldType.type == FieldType.TYPE_STRING: - return fieldType.name, "S"+str(fieldType.length) + return fieldType.name, "S" + str(fieldType.length) else: - raise DbfToNumpyError("The field %s has the following type that I do not recognize: %s " - % (fieldType.name, str(fieldType.type))) + raise DbfToNumpyError( + "The field %s has the following type that I do not recognize: %s " + % (fieldType.name, str(fieldType.type)) + ) + def convertDbfToNumpyDataTypes(fieldTypes): - """Accept a sequence of FieldType objects and return the field names and their numpy + """Accept a sequence of FieldType objects and return the field names and their numpy types""" names = [] @@ -335,101 +369,119 @@ def convertDbfToNumpyDataTypes(fieldTypes): # xrange() is not in python3; use iter(range()) if sys.version_info >= (3, 0): + def xrange(*args, **kwargs): return iter(range(*args, **kwargs)) class DbfDictReader(object): """Iterator over the records of a DBF III file - for each record in the file an OrderedDict is returned + for each record in the file an OrderedDict is returned """ + def __init__(self, bfstream): """Input: a binary sream""" self.bfstream = bfstream - self.numrec, lenheader = unpack(' self._numRecords: - raise DbfWriteError("The number of records that can be writen to the file " - "%s cannot exceed %d" % (self._fileName, self._numRecords)) + raise DbfWriteError( + "The number of records that can be writen to the file " + "%s cannot exceed %d" % (self._fileName, self._numRecords) + ) self._bfstream.write(b" ") for fType in self.header: name = fType.name @@ -500,34 +556,41 @@ def writeRecord(self, record): size = fType.length deci = fType.numDecimals value = record.__getitem__(name) - if (fType.type == FieldType.TYPE_INT or - fType.type == FieldType.TYPE_DECIMAL or - fType.type == FieldType.TYPE_FLOAT): + if ( + fType.type == FieldType.TYPE_INT + or fType.type == FieldType.TYPE_DECIMAL + or fType.type == FieldType.TYPE_FLOAT + ): if deci == 0: - fmtstr = "%" + str(size)+"d" + fmtstr = "%" + str(size) + "d" value = fmtstr % value else: fmtstr = "%" + str(size) + "." + str(deci) + "f" value = fmtstr % value - elif fType.type == 'D': - value = value.strftime('%Y%m%d') - elif fType.type == 'L': + elif fType.type == "D": + value = value.strftime("%Y%m%d") + elif fType.type == "L": value = str(value)[0].upper() else: - value = value[:size].ljust(size, b' ') - - if len(value) != size: print("Mismatch for {}; {} != {}; val={}".format(name, len(value), size, str(value))) + value = value[:size].ljust(size, b" ") + + if len(value) != size: + print( + "Mismatch for {}; {} != {}; val={}".format( + name, len(value), size, str(value) + ) + ) assert len(value) == size - if type(value)==str: value=value.encode('utf-8') + if type(value) == str: + value = value.encode("utf-8") self._bfstream.write(value) self._currentRecord += 1 if self._currentRecord == self._numRecords: - self._bfstream.write(b'\x1A') + self._bfstream.write(b"\x1A") self._bfstream.close() - + def __del__(self): - #TODO should I close the binary stream? + # TODO should I close the binary stream? pass - diff --git a/_static/odict.py b/_static/odict.py index a98b18e..86872a0 100644 --- a/_static/odict.py +++ b/_static/odict.py @@ -17,18 +17,21 @@ """A dict that keeps keys in insertion order""" from __future__ import generators -__author__ = ('Nicola Larosa ,' - 'Michael Foord ') +__author__ = ( + "Nicola Larosa ," + "Michael Foord " +) __docformat__ = "restructuredtext en" -__revision__ = '$Id: odict.py 129 2005-09-12 18:15:28Z teknico $' +__revision__ = "$Id: odict.py 129 2005-09-12 18:15:28Z teknico $" -__version__ = '0.2.2' +__version__ = "0.2.2" -__all__ = ['OrderedDict', 'SequenceOrderedDict'] +__all__ = ["OrderedDict", "SequenceOrderedDict"] import sys + INTP_VER = sys.version_info[:2] if INTP_VER < (2, 2): raise RuntimeError("Python v.2.2 or later required") @@ -41,43 +44,44 @@ except ImportError: SliceType = slice + class OrderedDict(dict): """ A class of dictionary that keeps the insertion order of keys. - + All appropriate methods return keys, items, or values in an ordered way. - + All normal dictionary methods are available. Update and comparison is restricted to other OrderedDict objects. - + Various sequence methods are available, including the ability to explicitly mutate the key ordering. - + __contains__ tests: - + >>> d = OrderedDict(((1, 3),)) >>> 1 in d 1 >>> 4 in d 0 - + __getitem__ tests: - + >>> OrderedDict(((1, 3), (3, 2), (2, 1)))[2] 1 >>> OrderedDict(((1, 3), (3, 2), (2, 1)))[4] Traceback (most recent call last): KeyError: 4 - + __len__ tests: - + >>> len(OrderedDict()) 0 >>> len(OrderedDict(((1, 3), (3, 2), (2, 1)))) 3 - + get tests: - + >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) >>> d.get(1) 3 @@ -87,9 +91,9 @@ class OrderedDict(dict): 5 >>> d OrderedDict([(1, 3), (3, 2), (2, 1)]) - + has_key tests: - + >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) >>> d.has_key(1) 1 @@ -101,11 +105,11 @@ def __init__(self, init_val=(), strict=False): """ Create a new ordered dictionary. Cannot init from a normal dict, nor from kwargs, since items order is undefined in those cases. - + If the ``strict`` keyword argument is ``True`` (``False`` is the default) then when doing slice assignment - the ``OrderedDict`` you are assigning from *must not* contain any keys in the remaining dict. - + >>> OrderedDict() OrderedDict([]) >>> OrderedDict({1: 1}) @@ -126,12 +130,12 @@ def __init__(self, init_val=(), strict=False): dict.update(self, init_val) elif isinstance(init_val, dict): # we lose compatibility with other ordered dict types this way - raise TypeError('undefined order, cannot get items from dict') + raise TypeError("undefined order, cannot get items from dict") else: self._sequence = [] self.update(init_val) -### Special methods ### + ### Special methods ### def __delitem__(self, key): """ @@ -180,7 +184,7 @@ def __eq__(self, other): if isinstance(other, OrderedDict): # FIXME: efficiency? # Generate both item lists for each compare - return (self.items() == other.items()) + return self.items() == other.items() else: return False @@ -197,10 +201,10 @@ def __lt__(self, other): TypeError: Can only compare with other OrderedDicts """ if not isinstance(other, OrderedDict): - raise TypeError('Can only compare with other OrderedDicts') + raise TypeError("Can only compare with other OrderedDicts") # FIXME: efficiency? # Generate both item lists for each compare - return (self.items() < other.items()) + return self.items() < other.items() def __le__(self, other): """ @@ -218,10 +222,10 @@ def __le__(self, other): True """ if not isinstance(other, OrderedDict): - raise TypeError('Can only compare with other OrderedDicts') + raise TypeError("Can only compare with other OrderedDicts") # FIXME: efficiency? # Generate both item lists for each compare - return (self.items() <= other.items()) + return self.items() <= other.items() def __ne__(self, other): """ @@ -259,10 +263,10 @@ def __gt__(self, other): TypeError: Can only compare with other OrderedDicts """ if not isinstance(other, OrderedDict): - raise TypeError('Can only compare with other OrderedDicts') + raise TypeError("Can only compare with other OrderedDicts") # FIXME: efficiency? # Generate both item lists for each compare - return (self.items() > other.items()) + return self.items() > other.items() def __ge__(self, other): """ @@ -280,15 +284,15 @@ def __ge__(self, other): True """ if not isinstance(other, OrderedDict): - raise TypeError('Can only compare with other OrderedDicts') + raise TypeError("Can only compare with other OrderedDicts") # FIXME: efficiency? # Generate both item lists for each compare - return (self.items() >= other.items()) + return self.items() >= other.items() def __repr__(self): """ Used for __repr__ and __str__ - + >>> r1 = repr(OrderedDict((('a', 'b'), ('c', 'd'), ('e', 'f')))) >>> r1 "OrderedDict([('a', 'b'), ('c', 'd'), ('e', 'f')])" @@ -300,8 +304,10 @@ def __repr__(self): >>> r2 == str(OrderedDict((('a', 'b'), ('e', 'f'), ('c', 'd')))) True """ - return '%s([%s])' % (self.__class__.__name__, ', '.join( - ['(%r, %r)' % (key, self[key]) for key in self._sequence])) + return "%s([%s])" % ( + self.__class__.__name__, + ", ".join(["(%r, %r)" % (key, self[key]) for key in self._sequence]), + ) def __setitem__(self, key, val): """ @@ -326,7 +332,7 @@ def __setitem__(self, key, val): >>> d[1:3] = OrderedDict(((1, 2), (5, 6), (7, 8))) >>> d OrderedDict([(0, 1), (1, 2), (5, 6), (7, 8), (3, 4)]) - + >>> a = OrderedDict(((0, 1), (1, 2), (2, 3)), strict=True) >>> a[3] = 4 >>> a @@ -350,12 +356,12 @@ def __setitem__(self, key, val): >>> a[::-1] = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)]) >>> a OrderedDict([(3, 4), (2, 3), (1, 2), (0, 1)]) - + >>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)]) >>> d[:1] = 3 Traceback (most recent call last): TypeError: slice assignment requires an OrderedDict - + >>> d = OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4)]) >>> d[:1] = OrderedDict([(9, 8)]) >>> d @@ -364,7 +370,7 @@ def __setitem__(self, key, val): if isinstance(key, SliceType): if not isinstance(val, OrderedDict): # FIXME: allow a list of tuples? - raise TypeError('slice assignment requires an OrderedDict') + raise TypeError("slice assignment requires an OrderedDict") keys = self._sequence[key] # NOTE: Could use ``range(*key.indices(len(self._sequence)))`` indexes = range(len(self._sequence))[key] @@ -379,21 +385,23 @@ def __setitem__(self, key, val): for k in newkeys: if k in self: if self.strict: - raise ValueError('slice assignment must be from ' - 'unique keys') + raise ValueError( + "slice assignment must be from " "unique keys" + ) else: # NOTE: This removes duplicate keys *first* # so start position might have changed? del self[k] - self._sequence = (self._sequence[:pos] + newkeys + - self._sequence[pos:]) + self._sequence = self._sequence[:pos] + newkeys + self._sequence[pos:] dict.update(self, val) else: # extended slice - length of new slice must be the same # as the one being replaced if len(keys) != len(val): - raise ValueError('attempt to assign sequence of size %s ' - 'to extended slice of size %s' % (len(val), len(keys))) + raise ValueError( + "attempt to assign sequence of size %s " + "to extended slice of size %s" % (len(val), len(keys)) + ) # FIXME: efficiency? del self[key] item_list = zip(indexes, val.items()) @@ -402,8 +410,7 @@ def __setitem__(self, key, val): item_list.sort() for pos, (newkey, newval) in item_list: if self.strict and newkey in self: - raise ValueError('slice assignment must be from unique' - ' keys') + raise ValueError("slice assignment must be from unique" " keys") self.insert(pos, newkey, newval) else: if key not in self: @@ -436,9 +443,12 @@ def __setattr__(self, name, value): Implemented so that accesses to ``sequence`` raise a warning and are diverted to the new ``setkeys`` method. """ - if name == 'sequence': - warnings.warn('Use of the sequence attribute is deprecated.' - ' Use the keys method instead.', DeprecationWarning) + if name == "sequence": + warnings.warn( + "Use of the sequence attribute is deprecated." + " Use the keys method instead.", + DeprecationWarning, + ) # NOTE: doesn't return anything self.setkeys(value) else: @@ -449,14 +459,17 @@ def __setattr__(self, name, value): def __getattr__(self, name): """ Implemented so that access to ``sequence`` raises a warning. - + >>> d = OrderedDict() >>> d.sequence [] """ - if name == 'sequence': - warnings.warn('Use of the sequence attribute is deprecated.' - ' Use the keys method instead.', DeprecationWarning) + if name == "sequence": + warnings.warn( + "Use of the sequence attribute is deprecated." + " Use the keys method instead.", + DeprecationWarning, + ) # NOTE: Still (currently) returns a direct reference. Need to # because code that uses sequence will expect to be able to # mutate it in place. @@ -468,7 +481,7 @@ def __getattr__(self, name): def __deepcopy__(self, memo): """ To allow deepcopy to work with OrderedDict. - + >>> from copy import deepcopy >>> a = OrderedDict([(1, 1), (2, 2), (3, 3)]) >>> a['test'] = {} @@ -481,10 +494,10 @@ def __deepcopy__(self, memo): False """ from copy import deepcopy - return self.__class__(deepcopy(self.items(), memo), self.strict) + return self.__class__(deepcopy(self.items(), memo), self.strict) -### Read-only methods ### + ### Read-only methods ### def copy(self): """ @@ -495,9 +508,9 @@ def copy(self): def items(self): """ - ``items`` returns a list of tuples representing all the + ``items`` returns a list of tuples representing all the ``(key, value)`` pairs in the dictionary. - + >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) >>> d.items() [(1, 3), (3, 2), (2, 1)] @@ -510,7 +523,7 @@ def items(self): def keys(self): """ Return a list of keys in the ``OrderedDict``. - + >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) >>> d.keys() [1, 3, 2] @@ -520,10 +533,10 @@ def keys(self): def values(self, values=None): """ Return a list of all the values in the OrderedDict. - + Optionally you can pass in a list of values, which will replace the current list. The value list must be the same len as the OrderedDict. - + >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) >>> d.values() [3, 2, 1] @@ -543,11 +556,13 @@ def iteritems(self): Traceback (most recent call last): StopIteration """ + def make_iter(self=self): keys = self.iterkeys() while True: key = keys.next() yield (key, self[key]) + return make_iter() def iterkeys(self): @@ -580,13 +595,15 @@ def itervalues(self): Traceback (most recent call last): StopIteration """ + def make_iter(self=self): keys = self.iterkeys() while True: yield self[keys.next()] + return make_iter() -### Read-write methods ### + ### Read-write methods ### def clear(self): """ @@ -601,7 +618,7 @@ def clear(self): def pop(self, key, *args): """ No dict.pop in Python 2.2, gotta reimplement it - + >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) >>> d.pop(3) 2 @@ -617,7 +634,9 @@ def pop(self, key, *args): TypeError: pop expected at most 2 arguments, got 3 """ if len(args) > 1: - raise TypeError('pop expected at most 2 arguments, got %s'.format(len(args) + 1)) + raise TypeError( + "pop expected at most 2 arguments, got %s".format(len(args) + 1) + ) if key in self: val = self[key] del self[key] @@ -632,7 +651,7 @@ def popitem(self, i=-1): """ Delete and return an item specified by index, not a random one as in dict. The index is -1 by default (the last item). - + >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) >>> d.popitem() (2, 1) @@ -648,14 +667,14 @@ def popitem(self, i=-1): IndexError: popitem(): index 2 not valid """ if not self._sequence: - raise KeyError('popitem(): dictionary is empty') + raise KeyError("popitem(): dictionary is empty") try: key = self._sequence[i] except IndexError: - raise IndexError('popitem(): index %s not valid' % i) + raise IndexError("popitem(): index %s not valid" % i) return (key, self.pop(key)) - def setdefault(self, key, defval = None): + def setdefault(self, key, defval=None): """ >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) >>> d.setdefault(1) @@ -678,7 +697,7 @@ def setdefault(self, key, defval = None): def update(self, from_od): """ Update from another OrderedDict or sequence of (key, value) pairs - + >>> d = OrderedDict(((1, 0), (0, 1))) >>> d.update(OrderedDict(((1, 3), (3, 2), (2, 1)))) >>> d @@ -695,7 +714,7 @@ def update(self, from_od): self[key] = val elif isinstance(from_od, dict): # we lose compatibility with other ordered dict types this way - raise TypeError('undefined order, cannot get items from dict') + raise TypeError("undefined order, cannot get items from dict") else: # FIXME: efficiency? # sequence of 2-item sequences, or error @@ -703,18 +722,20 @@ def update(self, from_od): try: key, val = item except TypeError: - raise TypeError('cannot convert dictionary update' - ' sequence element "%s" to a 2-item sequence' % item) + raise TypeError( + "cannot convert dictionary update" + ' sequence element "%s" to a 2-item sequence' % item + ) self[key] = val def rename(self, old_key, new_key): """ Rename the key for a given value, without modifying sequence order. - + For the case where new_key already exists this raise an exception, since if new_key exists, it is ambiguous as to what happens to the associated values, and the position of new_key in the sequence. - + >>> od = OrderedDict() >>> od['a'] = 1 >>> od['b'] = 2 @@ -736,7 +757,7 @@ def rename(self, old_key, new_key): if new_key in self: raise ValueError("New key already exists: %r" % new_key) # rename sequence entry - value = self[old_key] + value = self[old_key] old_idx = self._sequence.index(old_key) self._sequence[old_idx] = new_key # rename internal dict entry @@ -746,10 +767,10 @@ def rename(self, old_key, new_key): def setitems(self, items): """ This method allows you to set the items in the dict. - + It takes a list of tuples - of the same sort returned by the ``items`` method. - + >>> d = OrderedDict() >>> d.setitems(((3, 1), (2, 3), (1, 2))) >>> d @@ -764,10 +785,10 @@ def setkeys(self, keys): ``setkeys`` all ows you to pass in a new list of keys which will replace the current set. This must contain the same set of keys, but need not be in the same order. - + If you pass in new keys that don't match, a ``KeyError`` will be raised. - + >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) >>> d.keys() [1, 3, 2] @@ -785,7 +806,7 @@ def setkeys(self, keys): kcopy.sort() self._sequence.sort() if kcopy != self._sequence: - raise KeyError('Keylist is not the same as current keylist.') + raise KeyError("Keylist is not the same as current keylist.") # NOTE: This makes the _sequence attribute a new object, instead # of changing it in place. # FIXME: efficiency? @@ -795,9 +816,9 @@ def setvalues(self, values): """ You can pass in a list of values, which will replace the current list. The value list must be the same len as the OrderedDict. - + (Or a ``ValueError`` is raised.) - + >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) >>> d.setvalues((1, 2, 3)) >>> d @@ -808,16 +829,15 @@ def setvalues(self, values): """ if len(values) != len(self): # FIXME: correct error to raise? - raise ValueError('Value list is not the same length as the ' - 'OrderedDict.') + raise ValueError("Value list is not the same length as the " "OrderedDict.") self.update(zip(self, values)) -### Sequence Methods ### + ### Sequence Methods ### def index(self, key): """ Return the position of the specified key in the OrderedDict. - + >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) >>> d.index(3) 1 @@ -830,10 +850,10 @@ def index(self, key): def insert(self, index, key, value): """ Takes ``index``, ``key``, and ``value`` as arguments. - + Sets ``key`` to ``value``, so that ``key`` is at position ``index`` in the OrderedDict. - + >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) >>> d.insert(0, 4, 0) >>> d @@ -854,7 +874,7 @@ def insert(self, index, key, value): def reverse(self): """ Reverse the order of the OrderedDict. - + >>> d = OrderedDict(((1, 3), (3, 2), (2, 1))) >>> d.reverse() >>> d @@ -865,10 +885,10 @@ def reverse(self): def sort(self, *args, **kwargs): """ Sort the key order in the OrderedDict. - + This method takes the same arguments as the ``list.sort`` method on your version of Python. - + >>> d = OrderedDict(((4, 1), (2, 2), (3, 3), (1, 4))) >>> d.sort() >>> d @@ -876,11 +896,12 @@ def sort(self, *args, **kwargs): """ self._sequence.sort(*args, **kwargs) + class Keys(object): # FIXME: should this object be a subclass of list? """ Custom object for accessing the keys of an OrderedDict. - + Can be called like the normal ``OrderedDict.keys`` method, but also supports indexing and sequence methods. """ @@ -901,7 +922,7 @@ def __setitem__(self, index, name): """ You cannot assign to keys, but you can do slice assignment to re-order them. - + You can only do slice assignment if the new set of keys is a reordering of the original set. """ @@ -910,8 +931,10 @@ def __setitem__(self, index, name): # check length is the same indexes = range(len(self._main._sequence))[index] if len(indexes) != len(name): - raise ValueError('attempt to assign sequence of size %s ' - 'to slice of size %s' % (len(name), len(indexes))) + raise ValueError( + "attempt to assign sequence of size %s " + "to slice of size %s" % (len(name), len(indexes)) + ) # check they are the same keys # FIXME: Use set old_keys = self._main._sequence[index] @@ -919,59 +942,108 @@ def __setitem__(self, index, name): old_keys.sort() new_keys.sort() if old_keys != new_keys: - raise KeyError('Keylist is not the same as current keylist.') + raise KeyError("Keylist is not the same as current keylist.") orig_vals = [self._main[k] for k in name] del self._main[index] vals = zip(indexes, name, orig_vals) vals.sort() for i, k, v in vals: if self._main.strict and k in self._main: - raise ValueError('slice assignment must be from ' - 'unique keys') + raise ValueError("slice assignment must be from " "unique keys") self._main.insert(i, k, v) else: - raise ValueError('Cannot assign to keys') + raise ValueError("Cannot assign to keys") ### following methods pinched from UserList and adapted ### - def __repr__(self): return repr(self._main._sequence) + def __repr__(self): + return repr(self._main._sequence) # FIXME: do we need to check if we are comparing with another ``Keys`` # object? (like the __cast method of UserList) - def __lt__(self, other): return self._main._sequence < other - def __le__(self, other): return self._main._sequence <= other - def __eq__(self, other): return self._main._sequence == other - def __ne__(self, other): return self._main._sequence != other - def __gt__(self, other): return self._main._sequence > other - def __ge__(self, other): return self._main._sequence >= other + def __lt__(self, other): + return self._main._sequence < other + + def __le__(self, other): + return self._main._sequence <= other + + def __eq__(self, other): + return self._main._sequence == other + + def __ne__(self, other): + return self._main._sequence != other + + def __gt__(self, other): + return self._main._sequence > other + + def __ge__(self, other): + return self._main._sequence >= other + # FIXME: do we need __cmp__ as well as rich comparisons? - def __cmp__(self, other): return cmp(self._main._sequence, other) - - def __contains__(self, item): return item in self._main._sequence - def __len__(self): return len(self._main._sequence) - def __iter__(self): return self._main.iterkeys() - def count(self, item): return self._main._sequence.count(item) - def index(self, item, *args): return self._main._sequence.index(item, *args) - def reverse(self): self._main._sequence.reverse() - def sort(self, *args, **kwds): self._main._sequence.sort(*args, **kwds) - def __mul__(self, n): return self._main._sequence*n + def __cmp__(self, other): + return cmp(self._main._sequence, other) + + def __contains__(self, item): + return item in self._main._sequence + + def __len__(self): + return len(self._main._sequence) + + def __iter__(self): + return self._main.iterkeys() + + def count(self, item): + return self._main._sequence.count(item) + + def index(self, item, *args): + return self._main._sequence.index(item, *args) + + def reverse(self): + self._main._sequence.reverse() + + def sort(self, *args, **kwds): + self._main._sequence.sort(*args, **kwds) + + def __mul__(self, n): + return self._main._sequence * n + __rmul__ = __mul__ - def __add__(self, other): return self._main._sequence + other - def __radd__(self, other): return other + self._main._sequence + + def __add__(self, other): + return self._main._sequence + other + + def __radd__(self, other): + return other + self._main._sequence ## following methods not implemented for keys ## - def __delitem__(self, i): raise TypeError('Can\'t delete items from keys') - def __iadd__(self, other): raise TypeError('Can\'t add in place to keys') - def __imul__(self, n): raise TypeError('Can\'t multiply keys in place') - def append(self, item): raise TypeError('Can\'t append items to keys') - def insert(self, i, item): raise TypeError('Can\'t insert items into keys') - def pop(self, i=-1): raise TypeError('Can\'t pop items from keys') - def remove(self, item): raise TypeError('Can\'t remove items from keys') - def extend(self, other): raise TypeError('Can\'t extend keys') + def __delitem__(self, i): + raise TypeError("Can't delete items from keys") + + def __iadd__(self, other): + raise TypeError("Can't add in place to keys") + + def __imul__(self, n): + raise TypeError("Can't multiply keys in place") + + def append(self, item): + raise TypeError("Can't append items to keys") + + def insert(self, i, item): + raise TypeError("Can't insert items into keys") + + def pop(self, i=-1): + raise TypeError("Can't pop items from keys") + + def remove(self, item): + raise TypeError("Can't remove items from keys") + + def extend(self, other): + raise TypeError("Can't extend keys") + class Items(object): """ Custom object for accessing the items of an OrderedDict. - + Can be called like the normal ``OrderedDict.items`` method, but also supports indexing and sequence methods. """ @@ -1001,8 +1073,7 @@ def __setitem__(self, index, item): orig = self._main.keys[index] key, value = item if self._main.strict and key in self and (key != orig): - raise ValueError('slice assignment must be from ' - 'unique keys') + raise ValueError("slice assignment must be from " "unique keys") # delete the current one del self._main[self._main._sequence[index]] self._main.insert(index, key, value) @@ -1018,29 +1089,63 @@ def __delitem__(self, i): del self._main[key] ### following methods pinched from UserList and adapted ### - def __repr__(self): return repr(self._main.items()) + def __repr__(self): + return repr(self._main.items()) # FIXME: do we need to check if we are comparing with another ``Items`` # object? (like the __cast method of UserList) - def __lt__(self, other): return self._main.items() < other - def __le__(self, other): return self._main.items() <= other - def __eq__(self, other): return self._main.items() == other - def __ne__(self, other): return self._main.items() != other - def __gt__(self, other): return self._main.items() > other - def __ge__(self, other): return self._main.items() >= other - def __cmp__(self, other): return cmp(self._main.items(), other) - - def __contains__(self, item): return item in self._main.items() - def __len__(self): return len(self._main._sequence) # easier :-) - def __iter__(self): return self._main.iteritems() - def count(self, item): return self._main.items().count(item) - def index(self, item, *args): return self._main.items().index(item, *args) - def reverse(self): self._main.reverse() - def sort(self, *args, **kwds): self._main.sort(*args, **kwds) - def __mul__(self, n): return self._main.items()*n + def __lt__(self, other): + return self._main.items() < other + + def __le__(self, other): + return self._main.items() <= other + + def __eq__(self, other): + return self._main.items() == other + + def __ne__(self, other): + return self._main.items() != other + + def __gt__(self, other): + return self._main.items() > other + + def __ge__(self, other): + return self._main.items() >= other + + def __cmp__(self, other): + return cmp(self._main.items(), other) + + def __contains__(self, item): + return item in self._main.items() + + def __len__(self): + return len(self._main._sequence) # easier :-) + + def __iter__(self): + return self._main.iteritems() + + def count(self, item): + return self._main.items().count(item) + + def index(self, item, *args): + return self._main.items().index(item, *args) + + def reverse(self): + self._main.reverse() + + def sort(self, *args, **kwds): + self._main.sort(*args, **kwds) + + def __mul__(self, n): + return self._main.items() * n + __rmul__ = __mul__ - def __add__(self, other): return self._main.items() + other - def __radd__(self, other): return other + self._main.items() + + def __add__(self, other): + return self._main.items() + other + + def __radd__(self, other): + return other + self._main.items() def append(self, item): """Add an item to the end.""" @@ -1061,7 +1166,7 @@ def remove(self, item): try: assert value == self._main[key] except (KeyError, AssertionError): - raise ValueError('ValueError: list.remove(x): x not in list') + raise ValueError("ValueError: list.remove(x): x not in list") else: del self._main[key] @@ -1076,12 +1181,14 @@ def __iadd__(self, other): ## following methods not implemented for items ## - def __imul__(self, n): raise TypeError('Can\'t multiply items in place') + def __imul__(self, n): + raise TypeError("Can't multiply items in place") + class Values(object): """ Custom object for accessing the values of an OrderedDict. - + Can be called like the normal ``OrderedDict.values`` method, but also supports indexing and sequence methods. """ @@ -1103,15 +1210,17 @@ def __getitem__(self, index): def __setitem__(self, index, value): """ Set the value at position i to value. - + You can only do slice assignment to values if you supply a sequence of equal length to the slice you are replacing. """ if isinstance(index, SliceType): keys = self._main._sequence[index] if len(keys) != len(value): - raise ValueError('attempt to assign sequence of size %s ' - 'to slice of size %s' % (len(name), len(keys))) + raise ValueError( + "attempt to assign sequence of size %s " + "to slice of size %s" % (len(name), len(keys)) + ) # FIXME: efficiency? Would be better to calculate the indexes # directly from the slice object # NOTE: the new keys can collide with existing keys (or even @@ -1122,23 +1231,46 @@ def __setitem__(self, index, value): self._main[self._main._sequence[index]] = value ### following methods pinched from UserList and adapted ### - def __repr__(self): return repr(self._main.values()) + def __repr__(self): + return repr(self._main.values()) # FIXME: do we need to check if we are comparing with another ``Values`` # object? (like the __cast method of UserList) - def __lt__(self, other): return self._main.values() < other - def __le__(self, other): return self._main.values() <= other - def __eq__(self, other): return self._main.values() == other - def __ne__(self, other): return self._main.values() != other - def __gt__(self, other): return self._main.values() > other - def __ge__(self, other): return self._main.values() >= other - def __cmp__(self, other): return cmp(self._main.values(), other) - - def __contains__(self, item): return item in self._main.values() - def __len__(self): return len(self._main._sequence) # easier :-) - def __iter__(self): return self._main.itervalues() - def count(self, item): return self._main.values().count(item) - def index(self, item, *args): return self._main.values().index(item, *args) + def __lt__(self, other): + return self._main.values() < other + + def __le__(self, other): + return self._main.values() <= other + + def __eq__(self, other): + return self._main.values() == other + + def __ne__(self, other): + return self._main.values() != other + + def __gt__(self, other): + return self._main.values() > other + + def __ge__(self, other): + return self._main.values() >= other + + def __cmp__(self, other): + return cmp(self._main.values(), other) + + def __contains__(self, item): + return item in self._main.values() + + def __len__(self): + return len(self._main._sequence) # easier :-) + + def __iter__(self): + return self._main.itervalues() + + def count(self, item): + return self._main.values().count(item) + + def index(self, item, *args): + return self._main.values().index(item, *args) def reverse(self): """Reverse the values""" @@ -1153,31 +1285,53 @@ def sort(self, *args, **kwds): vals.sort(*args, **kwds) self[:] = vals - def __mul__(self, n): return self._main.values()*n + def __mul__(self, n): + return self._main.values() * n + __rmul__ = __mul__ - def __add__(self, other): return self._main.values() + other - def __radd__(self, other): return other + self._main.values() + + def __add__(self, other): + return self._main.values() + other + + def __radd__(self, other): + return other + self._main.values() ## following methods not implemented for values ## - def __delitem__(self, i): raise TypeError('Can\'t delete items from values') - def __iadd__(self, other): raise TypeError('Can\'t add in place to values') - def __imul__(self, n): raise TypeError('Can\'t multiply values in place') - def append(self, item): raise TypeError('Can\'t append items to values') - def insert(self, i, item): raise TypeError('Can\'t insert items into values') - def pop(self, i=-1): raise TypeError('Can\'t pop items from values') - def remove(self, item): raise TypeError('Can\'t remove items from values') - def extend(self, other): raise TypeError('Can\'t extend values') + def __delitem__(self, i): + raise TypeError("Can't delete items from values") + + def __iadd__(self, other): + raise TypeError("Can't add in place to values") + + def __imul__(self, n): + raise TypeError("Can't multiply values in place") + + def append(self, item): + raise TypeError("Can't append items to values") + + def insert(self, i, item): + raise TypeError("Can't insert items into values") + + def pop(self, i=-1): + raise TypeError("Can't pop items from values") + + def remove(self, item): + raise TypeError("Can't remove items from values") + + def extend(self, other): + raise TypeError("Can't extend values") + class SequenceOrderedDict(OrderedDict): """ Experimental version of OrderedDict that has a custom object for ``keys``, ``values``, and ``items``. - + These are callable sequence objects that work as methods, or can be manipulated directly as sequences. - + Test for ``keys``, ``items`` and ``values``. - + >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4))) >>> d SequenceOrderedDict([(1, 2), (2, 3), (3, 4)]) @@ -1297,7 +1451,7 @@ class SequenceOrderedDict(OrderedDict): >>> d.values = (1, 2, 3) >>> d SequenceOrderedDict([(1, 1), (2, 2), (3, 3)]) - + >>> d = SequenceOrderedDict(((1, 2), (2, 3), (3, 4))) >>> d SequenceOrderedDict([(1, 2), (2, 3), (3, 4)]) @@ -1371,14 +1525,14 @@ def __init__(self, init_val=(), strict=True): self.values = Values(self) self.items = Items(self) self._att_dict = { - 'keys': self.setkeys, - 'items': self.setitems, - 'values': self.setvalues, + "keys": self.setkeys, + "items": self.setitems, + "values": self.setvalues, } def __setattr__(self, name, value): """Protect keys, items, and values.""" - if not '_att_dict' in self.__dict__: + if not "_att_dict" in self.__dict__: object.__setattr__(self, name, value) else: try: @@ -1388,17 +1542,20 @@ def __setattr__(self, name, value): else: fun(value) -if __name__ == '__main__': + +if __name__ == "__main__": if INTP_VER < (2, 3): raise RuntimeError("Tests require Python v.2.3 or later") # turn off warnings for tests - warnings.filterwarnings('ignore') + warnings.filterwarnings("ignore") # run the code tests in doctest format import doctest - m = sys.modules.get('__main__') + + m = sys.modules.get("__main__") globs = m.__dict__.copy() - globs.update({ - 'INTP_VER': INTP_VER, - }) + globs.update( + { + "INTP_VER": INTP_VER, + } + ) doctest.testmod(m, globs=globs) - diff --git a/scripts/build_network_mtc.py b/scripts/build_network_mtc.py index dc9cd40..558d2f0 100644 --- a/scripts/build_network_mtc.py +++ b/scripts/build_network_mtc.py @@ -1,4 +1,4 @@ -import argparse,collections,copy,datetime,os,pandas,shutil,sys,time +import argparse, collections, copy, datetime, os, pandas, shutil, sys, time import Wrangler # Based on NetworkWrangler\scripts\build_network.py @@ -38,7 +38,7 @@ # OPTIONAL. If PIVOT_DIR is specified, MANDATORY. Specifies year for PIVOT_DIR. PIVOT_YEAR = 2015 -# MANDATORY. Set this to the directory in which to write your outputs. +# MANDATORY. Set this to the directory in which to write your outputs. # "hwy" and "trn" subdirectories will be created here. OUT_DIR = None @@ -53,10 +53,10 @@ # OPTIONAL. The default route network project directory is Y:\networks. If # projects are stored in another directory, then use this variable to specify it. # For example: Y:\networks\projects -NETWORK_BASE_DIR = None +NETWORK_BASE_DIR = None NETWORK_PROJECT_SUBDIR = None -NETWORK_SEED_SUBDIR = None -NETWORK_PLAN_SUBDIR = None +NETWORK_SEED_SUBDIR = None +NETWORK_PLAN_SUBDIR = None # OPTIONAL. A list of project names which have been previously applied in the # PIVOT_DIR network that projects in this project might rely on. For example @@ -68,8 +68,8 @@ # the TAG. This is meant for developing a network project. TEST_PROJECTS = None -TRN_MODES = ['trn'] -NET_MODES = ['hwy'] + TRN_MODES +TRN_MODES = ["trn"] +NET_MODES = ["hwy"] + TRN_MODES THIS_FILE = os.path.realpath(__file__) ############################################################################### @@ -80,31 +80,34 @@ # # ############################################################################### def getProjectNameAndDir(project): - if type(project) == type({'this is':'a dict'}): - name = project['name'] + if type(project) == type({"this is": "a dict"}): + name = project["name"] else: name = project - (path,name) = os.path.split(name) - return (path,name) + (path, name) = os.path.split(name) + return (path, name) + def getNetworkListIndex(project, networks): for proj in networks: - (path,name) = getProjectNameAndDir(proj) - if project == name or project == os.path.join(path,name): + (path, name) = getProjectNameAndDir(proj) + if project == name or project == os.path.join(path, name): return networks.index(proj) return None + def getProjectMatchLevel(left, right): - (left_path,left_name) = getProjectNameAndDir(left) - (right_path,right_name) = getProjectNameAndDir(right) + (left_path, left_name) = getProjectNameAndDir(left) + (right_path, right_name) = getProjectNameAndDir(right) match = 0 - if os.path.join(left_path,left_name) == os.path.join(right_path,right_name): + if os.path.join(left_path, left_name) == os.path.join(right_path, right_name): match = 2 elif left_name == right_name: match = 1 - #Wrangler.WranglerLogger.debug("Match level %d for %s and %s" % (match, os.path.join(left_path,left_name), os.path.join(right_path,right_name))) + # Wrangler.WranglerLogger.debug("Match level %d for %s and %s" % (match, os.path.join(left_path,left_name), os.path.join(right_path,right_name))) return match + def getProjectYear(PROJECTS, my_proj, netmode): """ PROJECTS is an OrderedDict, year -> netmode -> [ project list ] @@ -114,14 +117,15 @@ def getProjectYear(PROJECTS, my_proj, netmode): for year in PROJECTS.keys(): for proj_idx in range(len(PROJECTS[year][netmode])): proj = PROJECTS[year][netmode][proj_idx] - if type(proj) is dict and my_proj == proj['name']: - return "{}.{}.{:0>2d}".format(year,netmode,proj_idx+1) - elif proj == my_proj: - return "{}.{}.{:0>2d}".format(year,netmode,proj_idx+1) + if type(proj) is dict and my_proj == proj["name"]: + return "{}.{}.{:0>2d}".format(year, netmode, proj_idx + 1) + elif proj == my_proj: + return "{}.{}.{:0>2d}".format(year, netmode, proj_idx + 1) return -1 -def checkRequirements(REQUIREMENTS, PROJECTS, req_type='prereq'): - if req_type not in ('prereq','coreq','conflict'): + +def checkRequirements(REQUIREMENTS, PROJECTS, req_type="prereq"): + if req_type not in ("prereq", "coreq", "conflict"): return (None, None) # Wrangler.WranglerLogger.debug("checkRequirements called with requirements=[{}] projects=[{}] req_typ={}".format(REQUIREMENTS, PROJECTS, req_type)) @@ -133,27 +137,43 @@ def checkRequirements(REQUIREMENTS, PROJECTS, req_type='prereq'): for project in REQUIREMENTS[netmode].keys(): project_year = getProjectYear(PROJECTS, project, netmode) if (type(project_year) == int) and (project_year == -1): - Wrangler.WranglerLogger.warning('Cannot find the {} project {} to check its requirements'.format(netmode, project)) + Wrangler.WranglerLogger.warning( + "Cannot find the {} project {} to check its requirements".format( + netmode, project + ) + ) continue # raise? - Wrangler.WranglerLogger.info('Checking {} project {} ({}) for {}'.format(netmode, project, project_year, req_type)) + Wrangler.WranglerLogger.info( + "Checking {} project {} ({}) for {}".format( + netmode, project, project_year, req_type + ) + ) for req_netmode in REQUIREMENTS[netmode][project].keys(): - req_proj_list = REQUIREMENTS[netmode][project][req_netmode] + req_proj_list = REQUIREMENTS[netmode][project][req_netmode] req_proj_years = {} for req_proj in req_proj_list: req_project_year = getProjectYear(PROJECTS, req_proj, req_netmode) - Wrangler.WranglerLogger.debug('req_project_year=[{}]'.format(req_project_year)) + Wrangler.WranglerLogger.debug( + "req_project_year=[{}]".format(req_project_year) + ) # prereq - if req_type=="prereq": + if req_type == "prereq": if (type(req_project_year) == int) and (req_project_year < 0): is_ok = False # required project must be found - Wrangler.WranglerLogger.warning("required project not found") + Wrangler.WranglerLogger.warning( + "required project not found" + ) if req_project_year > project_year: is_ok = False # and implemented before or at the same time as the project - Wrangler.WranglerLogger.warning("required project year {} > project year {}".format(req_project_year, project_year)) + Wrangler.WranglerLogger.warning( + "required project year {} > project year {}".format( + req_project_year, project_year + ) + ) # save into proj_years req_proj_years[req_proj] = req_project_year @@ -163,54 +183,72 @@ def checkRequirements(REQUIREMENTS, PROJECTS, req_type='prereq'): return (REQUIREMENTS, is_ok) -def writeRequirements(REQUIREMENTS, PROJECTS, req_type='prereq'): - if req_type=='prereq': - print_req = 'Pre-requisite' - elif req_type=='coreq': - print_req = 'Co-requisite' - elif req_type=='conflict': - print_req = 'Conflict' + +def writeRequirements(REQUIREMENTS, PROJECTS, req_type="prereq"): + if req_type == "prereq": + print_req = "Pre-requisite" + elif req_type == "coreq": + print_req = "Co-requisite" + elif req_type == "conflict": + print_req = "Conflict" else: return None Wrangler.WranglerLogger.info("Requirement verification - {}".format(print_req)) - Wrangler.WranglerLogger.info(" Year {:50} {:50} Year".format("Project",print_req+" " + "Project")) + Wrangler.WranglerLogger.info( + " Year {:50} {:50} Year".format( + "Project", print_req + " " + "Project" + ) + ) # REQUIREMENTS: netmode -> project -> netmode -> req_proj -> req_proj_year for netmode in REQUIREMENTS.keys(): for project in REQUIREMENTS[netmode].keys(): project_year = getProjectYear(PROJECTS, project, netmode) for req_netmode in REQUIREMENTS[netmode][project].keys(): for req_project in REQUIREMENTS[netmode][project][req_netmode].keys(): - Wrangler.WranglerLogger.info("{} {} {:50} {} {:50} {}".format(netmode, project_year, project, - req_netmode, req_project, REQUIREMENTS[netmode][project][req_netmode][req_project])) + Wrangler.WranglerLogger.info( + "{} {} {:50} {} {:50} {}".format( + netmode, + project_year, + project, + req_netmode, + req_project, + REQUIREMENTS[netmode][project][req_netmode][req_project], + ) + ) + def getProjectAttributes(project): # Start with TAG if not build mode, no kwargs - project_type = 'project' - tag = None - kwargs = {} + project_type = "project" + tag = None + kwargs = {} # Use project name, tags, kwargs from dictionary - if type(project)==type({'this is':'a dictionary'}): - project_name = project['name'] - if 'tag' in project: tag = project['tag'] - if 'type' in project: project_type = project['type'] - if 'kwargs' in project: kwargs = project['kwargs'] + if type(project) == type({"this is": "a dictionary"}): + project_name = project["name"] + if "tag" in project: + tag = project["tag"] + if "type" in project: + project_type = project["type"] + if "kwargs" in project: + kwargs = project["kwargs"] # Use Project name directly - elif type(project)==type("string"): + elif type(project) == type("string"): project_name = project # Other structures not recognized else: - Wrangler.WranglerLogger.fatal("Don't understand project %s" % str(project)) + Wrangler.WranglerLogger.fatal("Don't understand project %s" % str(project)) return (project_name, project_type, tag, kwargs) + def preCheckRequirementsForAllProjects(networks, continue_on_warning): - PRE_REQS = {'hwy':{},'trn':{}} - CO_REQS = {'hwy':{},'trn':{}} - CONFLICTS = {'hwy':{},'trn':{}} + PRE_REQS = {"hwy": {}, "trn": {}} + CO_REQS = {"hwy": {}, "trn": {}} + CONFLICTS = {"hwy": {}, "trn": {}} # Network Loop #1: check out all the projects, check if they're stale, check if they're the head repository. Build completed # project list so we can check pre-reqs, etc, in loop #2. @@ -221,138 +259,215 @@ def preCheckRequirementsForAllProjects(networks, continue_on_warning): for model_year in NETWORK_PROJECTS.keys(): for project in NETWORK_PROJECTS[model_year][netmode]: (project_name, projType, tag, kwargs) = getProjectAttributes(project) - if tag == None: tag = TAG + if tag == None: + tag = TAG # test mode - don't use TAG for TEST_PROJECTS - if BUILD_MODE=="test" and type(TEST_PROJECTS)==type(['List']): + if BUILD_MODE == "test" and type(TEST_PROJECTS) == type(["List"]): if project_name in TEST_PROJECTS: - Wrangler.WranglerLogger.debug("Skipping tag [%s] because test mode and [%s] is in TEST_PROJECTS" % - (TAG, project_name)) + Wrangler.WranglerLogger.debug( + "Skipping tag [%s] because test mode and [%s] is in TEST_PROJECTS" + % (TAG, project_name) + ) tag = None Wrangler.WranglerLogger.debug("Project name = %s" % project_name) cloned_SHA1 = None # if project = "dir1/dir2" assume dir1 is git, dir2 is the projectsubdir - (head,tail) = os.path.split(project_name) + (head, tail) = os.path.split(project_name) if head: - cloned_SHA1 = networks[netmode].cloneProject(networkdir=head, projectsubdir=tail, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR, **kwargs) - (prereqs, coreqs, conflicts) = networks[netmode].getReqs(networkdir=head, projectsubdir=tail, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR) + cloned_SHA1 = networks[netmode].cloneProject( + networkdir=head, + projectsubdir=tail, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + **kwargs + ) + (prereqs, coreqs, conflicts) = networks[netmode].getReqs( + networkdir=head, + projectsubdir=tail, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + ) else: - cloned_SHA1 = networks[netmode].cloneProject(networkdir=project_name, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR, **kwargs) - (prereqs, coreqs, conflicts) = networks[netmode].getReqs(networkdir=project_name, projectsubdir=tail, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR) + cloned_SHA1 = networks[netmode].cloneProject( + networkdir=project_name, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + **kwargs + ) + (prereqs, coreqs, conflicts) = networks[netmode].getReqs( + networkdir=project_name, + projectsubdir=tail, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + ) # find out if the applied project is behind HEAD # get the HEAD SHA1 cmd = r"git show-ref --head master" - if projType=='project': + if projType == "project": join_subdir = Wrangler.Network.NETWORK_PROJECT_SUBDIR - if projType=='seed': + if projType == "seed": join_subdir = Wrangler.Network.NETWORK_SEED_SUBDIR - cmd_dir = os.path.join(Wrangler.Network.NETWORK_BASE_DIR, join_subdir, project_name) - (retcode, retStdout, retStderr) = networks[netmode]._runAndLog(cmd, run_dir = cmd_dir) + cmd_dir = os.path.join( + Wrangler.Network.NETWORK_BASE_DIR, join_subdir, project_name + ) + (retcode, retStdout, retStderr) = networks[netmode]._runAndLog( + cmd, run_dir=cmd_dir + ) # Wrangler.WranglerLogger.debug("results of [%s]: %s %s %s" % (cmd, str(retcode), str(retStdout), str(retStderr))) - if retcode != 0: # this shouldn't happen -- wouldn't cloneAndApply have failed? - Wrangler.WranglerLogger.fatal("Couldn't run cmd [%s] in [%s]: stdout=[%s] stderr=[%s]" % \ - (cmd, cmd_dir, str(retStdout), str(retStderr))) + if ( + retcode != 0 + ): # this shouldn't happen -- wouldn't cloneAndApply have failed? + Wrangler.WranglerLogger.fatal( + "Couldn't run cmd [%s] in [%s]: stdout=[%s] stderr=[%s]" + % (cmd, cmd_dir, str(retStdout), str(retStderr)) + ) sys.exit(2) head_SHA1 = retStdout[0].split()[0] - + # if they're different, log more information and get approval (if not in test mode) if cloned_SHA1 != head_SHA1: - Wrangler.WranglerLogger.warning("Using non-head version of project of %s" % project_name) - Wrangler.WranglerLogger.warning(" Applying version [%s], Head is [%s]" % (cloned_SHA1, head_SHA1)) - + Wrangler.WranglerLogger.warning( + "Using non-head version of project of %s" % project_name + ) + Wrangler.WranglerLogger.warning( + " Applying version [%s], Head is [%s]" + % (cloned_SHA1, head_SHA1) + ) + cmd = "git log %s..%s" % (cloned_SHA1, head_SHA1) - (retcode, retStdout, retStderr) = networks[netmode]._runAndLog(cmd, run_dir = cmd_dir) - Wrangler.WranglerLogger.warning(" The following commits are not included:") + (retcode, retStdout, retStderr) = networks[netmode]._runAndLog( + cmd, run_dir=cmd_dir + ) + Wrangler.WranglerLogger.warning( + " The following commits are not included:" + ) for line in retStdout: Wrangler.WranglerLogger.warning(" %s" % line) - + # test mode => warn is sufficient # non-test mode => get explicit approval if continue_on_warning: - Wrangler.WranglerLogger.warninging("Continuing (continue_on_warning)") - elif BUILD_MODE !="test": + Wrangler.WranglerLogger.warninging( + "Continuing (continue_on_warning)" + ) + elif BUILD_MODE != "test": Wrangler.WranglerLogger.warning(" Is this ok? (y/n) ") response = input("") Wrangler.WranglerLogger.debug(" response = [%s]" % response) if response.strip().lower()[0] != "y": sys.exit(2) - + # find out if the project is stale else: cmd = 'git show -s --format="%%ct" %s' % cloned_SHA1 - (retcode, retStdout, retStderr) = networks[netmode]._runAndLog(cmd, run_dir = cmd_dir) - applied_commit_date = datetime.datetime.fromtimestamp(int(retStdout[0])) + (retcode, retStdout, retStderr) = networks[netmode]._runAndLog( + cmd, run_dir=cmd_dir + ) + applied_commit_date = datetime.datetime.fromtimestamp( + int(retStdout[0]) + ) applied_commit_age = datetime.datetime.now() - applied_commit_date - + # if older than x years, holler STALE_YEARS = 5 - if applied_commit_age > datetime.timedelta(days=365*STALE_YEARS): - Wrangler.WranglerLogger.warning(" This project was last updated %.1f years ago (over %d), on %s" % \ - (applied_commit_age.days/365.0, - STALE_YEARS, applied_commit_date.strftime("%x"))) + if applied_commit_age > datetime.timedelta(days=365 * STALE_YEARS): + Wrangler.WranglerLogger.warning( + " This project was last updated %.1f years ago (over %d), on %s" + % ( + applied_commit_age.days / 365.0, + STALE_YEARS, + applied_commit_date.strftime("%x"), + ) + ) if continue_on_warning: - Wrangler.WranglerLogger.warninging("Continuing (continue_on_warning)") - elif BUILD_MODE !="test": + Wrangler.WranglerLogger.warninging( + "Continuing (continue_on_warning)" + ) + elif BUILD_MODE != "test": Wrangler.WranglerLogger.warning(" Is this ok? (y/n) ") response = input("") - Wrangler.WranglerLogger.debug(" response = [%s]" % response) + Wrangler.WranglerLogger.debug( + " response = [%s]" % response + ) if response.strip().lower() not in ["y", "yes"]: sys.exit(2) - + clonedcount += 1 - + # format: netmode -> project -> { netmode: [requirements] } - if len(prereqs ) > 0: PRE_REQS[ netmode][project_name] = prereqs - if len(coreqs ) > 0: CO_REQS[ netmode][project_name] = coreqs - if len(conflicts) > 0: CONFLICTS[netmode][project_name] = conflicts + if len(prereqs) > 0: + PRE_REQS[netmode][project_name] = prereqs + if len(coreqs) > 0: + CO_REQS[netmode][project_name] = coreqs + if len(conflicts) > 0: + CONFLICTS[netmode][project_name] = conflicts # Check requirements - prFile = 'prereqs.csv' - crFile = 'coreqs.csv' - cfFile = 'conflicts.csv' + prFile = "prereqs.csv" + crFile = "coreqs.csv" + cfFile = "conflicts.csv" # Check prereqs - (PRE_REQS, allPrereqsFound) = checkRequirements(PRE_REQS, NETWORK_PROJECTS, req_type='prereq') - if len(PRE_REQS['trn'])>0 or len(PRE_REQS['hwy'])>0: - writeRequirements(PRE_REQS, NETWORK_PROJECTS, req_type='prereq') + (PRE_REQS, allPrereqsFound) = checkRequirements( + PRE_REQS, NETWORK_PROJECTS, req_type="prereq" + ) + if len(PRE_REQS["trn"]) > 0 or len(PRE_REQS["hwy"]) > 0: + writeRequirements(PRE_REQS, NETWORK_PROJECTS, req_type="prereq") if allPrereqsFound: - Wrangler.WranglerLogger.debug('All PRE-REQUISITES were found. Are the PRE-REQUISITES matches correct? (y/n)') + Wrangler.WranglerLogger.debug( + "All PRE-REQUISITES were found. Are the PRE-REQUISITES matches correct? (y/n)" + ) else: - Wrangler.WranglerLogger.debug('!!!WARNING!!! Some PRE-REQUISITES were not found or ordered correctly. Continue anyway? (y/n)') + Wrangler.WranglerLogger.debug( + "!!!WARNING!!! Some PRE-REQUISITES were not found or ordered correctly. Continue anyway? (y/n)" + ) response = input("") Wrangler.WranglerLogger.debug(" response = [%s]" % response) if response.strip().lower() not in ["y", "yes"]: sys.exit(2) # Check coreqs - (CO_REQS, allCoreqsFound) = checkRequirements(CO_REQS, NETWORK_PROJECTS, req_type='coreq') - if len(CO_REQS['trn'])>0 or len(CO_REQS['hwy'])>0: - writeRequirements(CO_REQS, NETWORK_PROJECTS, req_type='coreq') + (CO_REQS, allCoreqsFound) = checkRequirements( + CO_REQS, NETWORK_PROJECTS, req_type="coreq" + ) + if len(CO_REQS["trn"]) > 0 or len(CO_REQS["hwy"]) > 0: + writeRequirements(CO_REQS, NETWORK_PROJECTS, req_type="coreq") if allCoreqsFound: - Wrangler.WranglerLogger.debug('All CO-REQUISITES were found. Are the CO-REQUISITE matches correct? (y/n)') + Wrangler.WranglerLogger.debug( + "All CO-REQUISITES were found. Are the CO-REQUISITE matches correct? (y/n)" + ) else: - Wrangler.WranglerLogger.debug('!!!WARNING!!! Some CO-REQUISITES were not found. Continue anyway? (y/n)') + Wrangler.WranglerLogger.debug( + "!!!WARNING!!! Some CO-REQUISITES were not found. Continue anyway? (y/n)" + ) response = input("") Wrangler.WranglerLogger.debug(" response = [%s]" % response) if response.strip().lower() not in ["y", "yes"]: sys.exit(2) # Check conflicts - (CONFLICTS, anyConflictFound) = checkRequirements(CONFLICTS, NETWORK_PROJECTS, req_type='conflict') - if len(CONFLICTS['trn'])>0 or len(CONFLICTS['hwy'])>0: - writeRequirements(CONFLICTS, NETWORK_PROJECTS, 'conflict') + (CONFLICTS, anyConflictFound) = checkRequirements( + CONFLICTS, NETWORK_PROJECTS, req_type="conflict" + ) + if len(CONFLICTS["trn"]) > 0 or len(CONFLICTS["hwy"]) > 0: + writeRequirements(CONFLICTS, NETWORK_PROJECTS, "conflict") if anyConflictFound: - Wrangler.WranglerLogger.debug('!!!WARNING!!! Conflicting projects were found. Continue anyway? (y/n)') + Wrangler.WranglerLogger.debug( + "!!!WARNING!!! Conflicting projects were found. Continue anyway? (y/n)" + ) else: - Wrangler.WranglerLogger.debug('No conflicting projects were found. Enter \'y\' to continue.') + Wrangler.WranglerLogger.debug( + "No conflicting projects were found. Enter 'y' to continue." + ) response = input("") Wrangler.WranglerLogger.debug(" response = [%s]" % response) if response.strip().lower() not in ["y", "yes"]: @@ -360,136 +475,185 @@ def preCheckRequirementsForAllProjects(networks, continue_on_warning): # Wrangler.WranglerLogger.debug("NETWORK_PROJECTS=%s NET_MODES=%s" % (str(NETWORK_PROJECTS), str(NET_MODES))) + ############################################################################### -if __name__ == '__main__': - parser = argparse.ArgumentParser(description=USAGE, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument("--configword", help="optional word for network specification script") - parser.add_argument("--model_type", choices=[Wrangler.Network.MODEL_TYPE_TM1, Wrangler.Network.MODEL_TYPE_TM2], - default=Wrangler.Network.MODEL_TYPE_TM1) - parser.add_argument("--continue_on_warning", help="Don't prompt the user to continue if there are warnings; just warn and continue", action="store_true") +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=USAGE, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + "--configword", help="optional word for network specification script" + ) + parser.add_argument( + "--model_type", + choices=[Wrangler.Network.MODEL_TYPE_TM1, Wrangler.Network.MODEL_TYPE_TM2], + default=Wrangler.Network.MODEL_TYPE_TM1, + ) + parser.add_argument( + "--continue_on_warning", + help="Don't prompt the user to continue if there are warnings; just warn and continue", + action="store_true", + ) parser.add_argument("project_name", help="required project name, for example NGF") parser.add_argument("--scenario", help="optional SCENARIO name") - parser.add_argument("net_spec", metavar="network_specification.py", help="Script which defines required variables indicating how to build the network") - parser.add_argument("--NGF_netvariant", choices=["BlueprintSegmented", "Mock", "Pathway1", "Pathway2"], help="Specify which network variant network to create.") + parser.add_argument( + "net_spec", + metavar="network_specification.py", + help="Script which defines required variables indicating how to build the network", + ) + parser.add_argument( + "--NGF_netvariant", + choices=["BlueprintSegmented", "Mock", "Pathway1", "Pathway2"], + help="Specify which network variant network to create.", + ) args = parser.parse_args() - NOW = time.strftime("%Y%b%d.%H%M%S") - BUILD_MODE = None # regular - if (args.model_type == Wrangler.Network.MODEL_TYPE_TM1) & (args.project_name != 'NGF'): - PIVOT_DIR = r"M:\\Application\\Model One\\Networks\\TM1_2015_Base_Network" + NOW = time.strftime("%Y%b%d.%H%M%S") + BUILD_MODE = None # regular + if (args.model_type == Wrangler.Network.MODEL_TYPE_TM1) & ( + args.project_name != "NGF" + ): + PIVOT_DIR = r"M:\\Application\\Model One\\Networks\\TM1_2015_Base_Network" TRANSIT_CAPACITY_DIR = os.path.join(PIVOT_DIR, "trn") NETWORK_BASE_DIR = r"M:\\Application\\Model One\\NetworkProjects" - TRN_SUBDIR = "trn" - TRN_NET_NAME = "Transit_Lines" - HWY_SUBDIR = "hwy" - HWY_NET_NAME = "freeflow.net" - elif (args.model_type == Wrangler.Network.MODEL_TYPE_TM1) & (args.project_name == 'NGF'): - PIVOT_DIR = r"L:\Application\Model_One\NextGenFwys\INPUT_DEVELOPMENT\Networks\NGF_Networks_NoProject_03\net_2035_NextGenFwy_NoProject_03" - PIVOT_YEAR = 2035 + TRN_SUBDIR = "trn" + TRN_NET_NAME = "Transit_Lines" + HWY_SUBDIR = "hwy" + HWY_NET_NAME = "freeflow.net" + elif (args.model_type == Wrangler.Network.MODEL_TYPE_TM1) & ( + args.project_name == "NGF" + ): + PIVOT_DIR = r"L:\Application\Model_One\NextGenFwys\INPUT_DEVELOPMENT\Networks\NGF_Networks_NoProject_03\net_2035_NextGenFwy_NoProject_03" + PIVOT_YEAR = 2035 TRANSIT_CAPACITY_DIR = os.path.join(PIVOT_DIR, "trn") NETWORK_BASE_DIR = r"M:\Application\Model One\NetworkProjects" - TRN_SUBDIR = "trn" - TRN_NET_NAME = "transitLines" - HWY_SUBDIR = "hwy" - HWY_NET_NAME = "freeflow.net" + TRN_SUBDIR = "trn" + TRN_NET_NAME = "transitLines" + HWY_SUBDIR = "hwy" + HWY_NET_NAME = "freeflow.net" elif args.model_type == Wrangler.Network.MODEL_TYPE_TM2: - PIVOT_DIR = os.path.join(os.environ["USERPROFILE"], "Box","Modeling and Surveys","Development","Travel Model Two Development","Model Inputs","2015_revised_mazs") + PIVOT_DIR = os.path.join( + os.environ["USERPROFILE"], + "Box", + "Modeling and Surveys", + "Development", + "Travel Model Two Development", + "Model Inputs", + "2015_revised_mazs", + ) TRANSIT_CAPACITY_DIR = None NETWORK_BASE_DIR = r"M:\\Application\\Model Two\\NetworkProjects" - TRN_SUBDIR = "trn" - TRN_NET_NAME = "transitLines" - HWY_SUBDIR = "hwy" - HWY_NET_NAME = "mtc_final_network_base.net" + TRN_SUBDIR = "trn" + TRN_NET_NAME = "transitLines" + HWY_SUBDIR = "hwy" + HWY_NET_NAME = "mtc_final_network_base.net" # Read the configuration - NETWORK_CONFIG = args.net_spec - PROJECT = args.project_name - if args.scenario: SCENARIO = args.scenario - if args.project_name == 'NGF': - SCENARIO = args.NGF_netvariant + NETWORK_CONFIG = args.net_spec + PROJECT = args.project_name + if args.scenario: + SCENARIO = args.scenario + if args.project_name == "NGF": + SCENARIO = args.NGF_netvariant NET_VARIANT = args.NGF_netvariant - OUT_DIR = "{}_network_".format(SCENARIO) + "{}" + OUT_DIR = "{}_network_".format(SCENARIO) + "{}" + + LOG_FILENAME = "build%snetwork_%s_%s_%s.info.LOG" % ( + "TEST" if BUILD_MODE == "test" else "", + PROJECT, + SCENARIO, + NOW, + ) + Wrangler.setupLogging(LOG_FILENAME, LOG_FILENAME.replace("info", "debug")) - LOG_FILENAME = "build%snetwork_%s_%s_%s.info.LOG" % ("TEST" if BUILD_MODE=="test" else "", PROJECT, SCENARIO, NOW) - Wrangler.setupLogging(LOG_FILENAME, - LOG_FILENAME.replace("info", "debug")) - exec(open(NETWORK_CONFIG).read()) # Verify mandatory fields are set - if PROJECT==None: + if PROJECT == None: print("PROJECT not set in %s" % NETWORK_CONFIG) sys.exit(2) - if SCENARIO==None: + if SCENARIO == None: print("SCENARIO not set in %s" % NETWORK_CONFIG) # sys.exit(2) - if TAG==None: + if TAG == None: print("TAG not set in %s" % NETWORK_CONFIG) sys.exit(2) - if OUT_DIR==None: + if OUT_DIR == None: print("OUT_DIR not set in %s" % NETWORK_CONFIG) sys.exit(2) - if NETWORK_PROJECTS==None: + if NETWORK_PROJECTS == None: print("NETWORK_PROJECTS not set in %s" % NETWORK_CONFIG) sys.exit(2) if TRANSIT_CAPACITY_DIR: - Wrangler.TransitNetwork.capacity = Wrangler.TransitCapacity(directory=TRANSIT_CAPACITY_DIR) + Wrangler.TransitNetwork.capacity = Wrangler.TransitCapacity( + directory=TRANSIT_CAPACITY_DIR + ) # Create a scratch directory to check out project repos into SCRATCH_SUBDIR = "scratch" - TEMP_SUBDIR = "Wrangler_tmp_" + NOW - if not os.path.exists(SCRATCH_SUBDIR): os.mkdir(SCRATCH_SUBDIR) + TEMP_SUBDIR = "Wrangler_tmp_" + NOW + if not os.path.exists(SCRATCH_SUBDIR): + os.mkdir(SCRATCH_SUBDIR) os.chdir(SCRATCH_SUBDIR) - if args.project_name == 'NGF': - os.environ["CHAMP_node_names"] = "M:\\Application\\Model One\\Networks\\TM1_2015_Base_Network\\Node Description.xls" + if args.project_name == "NGF": + os.environ[ + "CHAMP_node_names" + ] = "M:\\Application\\Model One\\Networks\\TM1_2015_Base_Network\\Node Description.xls" print() else: - os.environ["CHAMP_node_names"] = os.path.join(PIVOT_DIR,"Node Description.xls") + os.environ["CHAMP_node_names"] = os.path.join(PIVOT_DIR, "Node Description.xls") networks = { - 'hwy' :Wrangler.HighwayNetwork(modelType=args.model_type, modelVersion=1.0, - basenetworkpath=os.path.join(PIVOT_DIR,"hwy"), - networkBaseDir=NETWORK_BASE_DIR, - networkProjectSubdir=NETWORK_PROJECT_SUBDIR, - networkSeedSubdir=NETWORK_SEED_SUBDIR, - networkPlanSubdir=NETWORK_PLAN_SUBDIR, - isTiered=True if PIVOT_DIR else False, - tag=TAG, - tempdir=TEMP_SUBDIR, - networkName="hwy", - tierNetworkName=HWY_NET_NAME), - 'trn':Wrangler.TransitNetwork( modelType=args.model_type, modelVersion=1.0, - basenetworkpath=os.path.join(PIVOT_DIR,"trn"), - networkBaseDir=NETWORK_BASE_DIR, - networkProjectSubdir=NETWORK_PROJECT_SUBDIR, - networkSeedSubdir=NETWORK_SEED_SUBDIR, - networkPlanSubdir=NETWORK_PLAN_SUBDIR, - isTiered=True if PIVOT_DIR else False, - networkName=TRN_NET_NAME) + "hwy": Wrangler.HighwayNetwork( + modelType=args.model_type, + modelVersion=1.0, + basenetworkpath=os.path.join(PIVOT_DIR, "hwy"), + networkBaseDir=NETWORK_BASE_DIR, + networkProjectSubdir=NETWORK_PROJECT_SUBDIR, + networkSeedSubdir=NETWORK_SEED_SUBDIR, + networkPlanSubdir=NETWORK_PLAN_SUBDIR, + isTiered=True if PIVOT_DIR else False, + tag=TAG, + tempdir=TEMP_SUBDIR, + networkName="hwy", + tierNetworkName=HWY_NET_NAME, + ), + "trn": Wrangler.TransitNetwork( + modelType=args.model_type, + modelVersion=1.0, + basenetworkpath=os.path.join(PIVOT_DIR, "trn"), + networkBaseDir=NETWORK_BASE_DIR, + networkProjectSubdir=NETWORK_PROJECT_SUBDIR, + networkSeedSubdir=NETWORK_SEED_SUBDIR, + networkPlanSubdir=NETWORK_PLAN_SUBDIR, + isTiered=True if PIVOT_DIR else False, + networkName=TRN_NET_NAME, + ), } # For projects applied in a pivot network (because they won't show up in the current project list) if APPLIED_PROJECTS != None: for proj in APPLIED_PROJECTS: - networks['hwy'].appliedProjects[proj]=TAG - + networks["hwy"].appliedProjects[proj] = TAG # Wrangler.WranglerLogger.debug("NETWORK_PROJECTS=%s NET_MODES=%s" % (str(NETWORK_PROJECTS), str(NET_MODES))) if args.skip_precheck_requirements: - Wrangler.WranglerLogger.info("skip_precheck_requirements passed so skipping preCheckRequirementsForAllProjects()") + Wrangler.WranglerLogger.info( + "skip_precheck_requirements passed so skipping preCheckRequirementsForAllProjects()" + ) else: preCheckRequirementsForAllProjects(networks, args.continue_on_warning) # create the subdir for SET_CAPCLASS with set_capclass.job as apply.s - SET_CAPCLASS = "set_capclass" + SET_CAPCLASS = "set_capclass" SET_CAPCLASS_DIR = os.path.join(TEMP_SUBDIR, SET_CAPCLASS) os.makedirs(SET_CAPCLASS_DIR) - source_file = os.path.join(os.path.dirname(THIS_FILE), "set_capclass.job") - shutil.copyfile( source_file, os.path.join(SET_CAPCLASS_DIR, "apply.s")) + source_file = os.path.join(os.path.dirname(THIS_FILE), "set_capclass.job") + shutil.copyfile(source_file, os.path.join(SET_CAPCLASS_DIR, "apply.s")) # Network Loop #2: Now that everything has been checked, build the networks. for YEAR in NETWORK_PROJECTS.keys(): @@ -497,56 +661,89 @@ def preCheckRequirementsForAllProjects(networks, continue_on_warning): appliedcount = 0 for netmode in NET_MODES: - Wrangler.WranglerLogger.info("Building {} {} networks".format(YEAR, netmode)) + Wrangler.WranglerLogger.info( + "Building {} {} networks".format(YEAR, netmode) + ) for project in projects_for_year[netmode]: (project_name, projType, tag, kwargs) = getProjectAttributes(project) - if tag == None: tag = TAG - - Wrangler.WranglerLogger.info("Applying project [{}] of type [{}] with tag [{}] and kwargs[{}]".format(project_name, projType, tag, kwargs)) - if projType=='plan': + if tag == None: + tag = TAG + + Wrangler.WranglerLogger.info( + "Applying project [{}] of type [{}] with tag [{}] and kwargs[{}]".format( + project_name, projType, tag, kwargs + ) + ) + if projType == "plan": continue applied_SHA1 = None - cloned_SHA1 = networks[netmode].cloneProject(networkdir=project_name, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR, **kwargs) - (parentdir, networkdir, gitdir, projectsubdir) = networks[netmode].getClonedProjectArgs(project_name, None, projType, TEMP_SUBDIR) - - applied_SHA1 = networks[netmode].applyProject(parentdir, networkdir, gitdir, projectsubdir, **kwargs) + cloned_SHA1 = networks[netmode].cloneProject( + networkdir=project_name, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + **kwargs + ) + (parentdir, networkdir, gitdir, projectsubdir) = networks[ + netmode + ].getClonedProjectArgs(project_name, None, projType, TEMP_SUBDIR) + + applied_SHA1 = networks[netmode].applyProject( + parentdir, networkdir, gitdir, projectsubdir, **kwargs + ) appliedcount += 1 # if hwy project has set_capclass override, copy it to set_capclass/apply.s - set_capclass_override = os.path.join(TEMP_SUBDIR, project_name, "set_capclass.job") + set_capclass_override = os.path.join( + TEMP_SUBDIR, project_name, "set_capclass.job" + ) if os.path.exists(set_capclass_override): dest_file = os.path.join(SET_CAPCLASS_DIR, "apply.s") shutil.copyfile(set_capclass_override, dest_file) - Wrangler.WranglerLogger.info("Copied override {} to {}".format(set_capclass_override, dest_file)) + Wrangler.WranglerLogger.info( + "Copied override {} to {}".format( + set_capclass_override, dest_file + ) + ) if appliedcount == 0: - Wrangler.WranglerLogger.info("No applied projects for this year -- skipping output") + Wrangler.WranglerLogger.info( + "No applied projects for this year -- skipping output" + ) continue # Initialize output subdirectories up a level (not in scratch) - hwypath=os.path.join("..", OUT_DIR.format(YEAR),HWY_SUBDIR) - if not os.path.exists(hwypath): os.makedirs(hwypath) - trnpath = os.path.join("..", OUT_DIR.format(YEAR),TRN_SUBDIR) - if not os.path.exists(trnpath): os.makedirs(trnpath) + hwypath = os.path.join("..", OUT_DIR.format(YEAR), HWY_SUBDIR) + if not os.path.exists(hwypath): + os.makedirs(hwypath) + trnpath = os.path.join("..", OUT_DIR.format(YEAR), TRN_SUBDIR) + if not os.path.exists(trnpath): + os.makedirs(trnpath) - networks['hwy'].write(path=hwypath,name=HWY_NET_NAME,suppressQuery=True, - suppressValidation=True) # MTC TM1 doesn't have turn penalties + networks["hwy"].write( + path=hwypath, name=HWY_NET_NAME, suppressQuery=True, suppressValidation=True + ) # MTC TM1 doesn't have turn penalties # os.environ["CHAMP_node_names"] = os.path.join(PIVOT_DIR,"Node Description.xls") - hwy_abs_path = os.path.abspath( os.path.join(hwypath, HWY_NET_NAME) ) - networks['trn'].write(path=trnpath, - name="transitLines", - writeEmptyFiles = False, - suppressQuery = True, - suppressValidation = False, - cubeNetFileForValidation = hwy_abs_path) + hwy_abs_path = os.path.abspath(os.path.join(hwypath, HWY_NET_NAME)) + networks["trn"].write( + path=trnpath, + name="transitLines", + writeEmptyFiles=False, + suppressQuery=True, + suppressValidation=False, + cubeNetFileForValidation=hwy_abs_path, + ) # Write the transit capacity configuration - Wrangler.TransitNetwork.capacity.writeTransitVehicleToCapacity(directory = trnpath) - Wrangler.TransitNetwork.capacity.writeTransitLineToVehicle(directory = trnpath) - Wrangler.TransitNetwork.capacity.writeTransitPrefixToVehicle(directory = trnpath) - - Wrangler.WranglerLogger.debug("Successfully completed running %s" % os.path.abspath(__file__)) + Wrangler.TransitNetwork.capacity.writeTransitVehicleToCapacity( + directory=trnpath + ) + Wrangler.TransitNetwork.capacity.writeTransitLineToVehicle(directory=trnpath) + Wrangler.TransitNetwork.capacity.writeTransitPrefixToVehicle(directory=trnpath) + + Wrangler.WranglerLogger.debug( + "Successfully completed running %s" % os.path.abspath(__file__) + ) diff --git a/scripts/build_network_mtc_add_project.py b/scripts/build_network_mtc_add_project.py index 4e89a72..818f1ad 100644 --- a/scripts/build_network_mtc_add_project.py +++ b/scripts/build_network_mtc_add_project.py @@ -1,4 +1,4 @@ -import argparse,collections,copy,datetime,os,pandas,re,shutil,sys,time +import argparse, collections, copy, datetime, os, pandas, re, shutil, sys, time import Wrangler USAGE = """ @@ -12,9 +12,11 @@ the network project. Default use case is PPA. """ -PPA_DIR = "L:\\RTP2021_PPA\\Projects" -NODE_NAMES = "M:\\Application\\Model One\\Networks\\TM1_2015_Base_Network\\Node Description.xls" -THIS_FILE = os.path.realpath(__file__) +PPA_DIR = "L:\\RTP2021_PPA\\Projects" +NODE_NAMES = ( + "M:\\Application\\Model One\\Networks\\TM1_2015_Base_Network\\Node Description.xls" +) +THIS_FILE = os.path.realpath(__file__) # for transit network validation output os.environ["CHAMP_node_names"] = NODE_NAMES @@ -34,104 +36,172 @@ def findBaseDirectory(future): for dirname in dir_list: match = model_id_re.match(dirname) - if match == None: continue + if match == None: + continue future_code = match.group(1) run_version = match.group(2) - if future=="CleanAndGreen" and future_code=="CG": + if future == "CleanAndGreen" and future_code == "CG": return_dirs.append(dirname) - elif future=="RisingTides" and future_code=="RT": + elif future == "RisingTides" and future_code == "RT": return_dirs.append(dirname) - elif future=="BackToTheFuture" and future_code=="BF": + elif future == "BackToTheFuture" and future_code == "BF": return_dirs.append(dirname) if len(return_dirs) == 0: - raise Exception("No base directories found") + raise Exception("No base directories found") print("The following base directory options were found: ") for dirname in return_dirs: - print(" -> {}".format(dirname)) - print("Which base directory do you want to use? (No response means {}) ".format(return_dirs[-1])) + print(" -> {}".format(dirname)) + print( + "Which base directory do you want to use? (No response means {}) ".format( + return_dirs[-1] + ) + ) response = raw_input("") print(" response = [%s]" % response) response = response.strip() if response == "": - return return_dirs[-1] + return return_dirs[-1] if response in return_dirs: - return response + return response raise Exception("Didn't understand response [{}]".format(response)) + def determineProjectDirectory(OUTPUT_DIR, BASE_DIR, project_short_id): - dir_list = sorted(os.listdir(OUTPUT_DIR)) - dir_re_str = r"^{}_{}_(\d\d)$".format(re.escape(BASE_DIR), re.escape(project_short_id)) - dir_re = re.compile(dir_re_str) + dir_list = sorted(os.listdir(OUTPUT_DIR)) + dir_re_str = r"^{}_{}_(\d\d)$".format( + re.escape(BASE_DIR), re.escape(project_short_id) + ) + dir_re = re.compile(dir_re_str) existing_suffixes = [] print(dir_re_str) for dirname in dir_list: - print(dirname) - match = dir_re.match(dirname) - if match == None: continue - suffix = match.group(1) - existing_suffixes.append(int(suffix)) - - print("Found existing project directories with suffixes: {}".format(existing_suffixes)) + print(dirname) + match = dir_re.match(dirname) + if match == None: + continue + suffix = match.group(1) + existing_suffixes.append(int(suffix)) + + print( + "Found existing project directories with suffixes: {}".format(existing_suffixes) + ) proposed_suffix = 0 - if len(existing_suffixes) > 0: proposed_suffix = existing_suffixes[-1] + 1 - print("Which suffix number do you want to use? (No response means {}) ".format(proposed_suffix)) + if len(existing_suffixes) > 0: + proposed_suffix = existing_suffixes[-1] + 1 + print( + "Which suffix number do you want to use? (No response means {}) ".format( + proposed_suffix + ) + ) response = input("") print(" response = [%s]" % response) response_suffix = proposed_suffix if response != "": - response_suffix = int(response.strip()) + response_suffix = int(response.strip()) if response_suffix in existing_suffixes: - raise Exception("Cannot use suffix that is already in use") + raise Exception("Cannot use suffix that is already in use") - project_dir = os.path.join(OUTPUT_DIR, "{}_{}_{:02d}".format(BASE_DIR, project_short_id, response_suffix)) + project_dir = os.path.join( + OUTPUT_DIR, "{}_{}_{:02d}".format(BASE_DIR, project_short_id, response_suffix) + ) print("OUTPUT_FUTURE_DIR is {}".format(project_dir)) return project_dir -if __name__ == '__main__': - parser = argparse.ArgumentParser(description=USAGE, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument("future", choices=["CleanAndGreen", "RisingTides", "BackToTheFuture", "all","None","FinalBlueprint","TransitRecovery"], help="Specify which Future Scenario for which to create networks") - parser.add_argument("--hwy", dest='hwy', action='store_true', help="Pass if project is a roadway project") - parser.add_argument("--trn", dest='trn', action='store_true', help="Pass if project is a transit project") - parser.add_argument("--input_network", dest='input_network', help="Pass input network path if desired; otherwise, PPA path is assumed") - parser.add_argument("--output_network", dest='output_network', help="Pass output network path if desired; otPherwise, PPA path is assumed") - parser.add_argument("--input_projects", dest='input_projects', help="Pass directory for network projects; if none passed, M:\\Application\\Model One\\NetworkProjects is assumed") - parser.add_argument("--kwarg", dest='kwarg', help="To pass keyword args to project apply(), pass keyword and value", nargs=2) - parser.add_argument("--kwarg2", dest='kwarg2', help="To pass keyword args to project apply(), pass keyword and value", nargs=2) - parser.add_argument("--tag", dest='tag', help="tags for project") - parser.add_argument("project_short_id", help="Short ID of project, to be used for directory") + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=USAGE, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + "future", + choices=[ + "CleanAndGreen", + "RisingTides", + "BackToTheFuture", + "all", + "None", + "FinalBlueprint", + "TransitRecovery", + ], + help="Specify which Future Scenario for which to create networks", + ) + parser.add_argument( + "--hwy", + dest="hwy", + action="store_true", + help="Pass if project is a roadway project", + ) + parser.add_argument( + "--trn", + dest="trn", + action="store_true", + help="Pass if project is a transit project", + ) + parser.add_argument( + "--input_network", + dest="input_network", + help="Pass input network path if desired; otherwise, PPA path is assumed", + ) + parser.add_argument( + "--output_network", + dest="output_network", + help="Pass output network path if desired; otPherwise, PPA path is assumed", + ) + parser.add_argument( + "--input_projects", + dest="input_projects", + help="Pass directory for network projects; if none passed, M:\\Application\\Model One\\NetworkProjects is assumed", + ) + parser.add_argument( + "--kwarg", + dest="kwarg", + help="To pass keyword args to project apply(), pass keyword and value", + nargs=2, + ) + parser.add_argument( + "--kwarg2", + dest="kwarg2", + help="To pass keyword args to project apply(), pass keyword and value", + nargs=2, + ) + parser.add_argument("--tag", dest="tag", help="tags for project") + parser.add_argument( + "project_short_id", help="Short ID of project, to be used for directory" + ) parser.add_argument("project", help="Project to add", nargs="+") args = parser.parse_args() if not args.hwy and not args.trn: - print("Project must be roadway, transit or both. Please specify --hwy and/or --trn") + print( + "Project must be roadway, transit or both. Please specify --hwy and/or --trn" + ) sys.exit(2) - - PROJECT = "PPA" + PROJECT = "PPA" if args.future == "FinalBlueprint": - PROJECT = "FBP" + PROJECT = "FBP" elif args.future == "TransitRecovery": - PROJECT = "TRR" + PROJECT = "TRR" if args.input_projects: NETWORK_BASE_DIR = args.input_projects else: NETWORK_BASE_DIR = r"M:\\Application\\Model One\\NetworkProjects" - HWY_NET_NAME = "freeflow.net" - TRN_NET_NAME = "transitLines" + HWY_NET_NAME = "freeflow.net" + TRN_NET_NAME = "transitLines" if args.future == "all": FUTURES = ["CleanAndGreen", "RisingTides", "BackToTheFuture"] @@ -139,18 +209,20 @@ def determineProjectDirectory(OUTPUT_DIR, BASE_DIR, project_short_id): FUTURES = [args.future] for SCENARIO in FUTURES: - NOW = time.strftime("%Y%b%d.%H%M%S") + NOW = time.strftime("%Y%b%d.%H%M%S") if args.input_network: - print("Using input network {}".format(args.input_network)) - BASE_DIR = args.input_network - PIVOT_DIR = args.input_network + print("Using input network {}".format(args.input_network)) + BASE_DIR = args.input_network + PIVOT_DIR = args.input_network else: - BASE_DIR = findBaseDirectory(SCENARIO) # e.g. 2050_TM150_PPA_BF_00 - PIVOT_DIR = os.path.join(PPA_DIR, BASE_DIR, "INPUT") # full path of input network + BASE_DIR = findBaseDirectory(SCENARIO) # e.g. 2050_TM150_PPA_BF_00 + PIVOT_DIR = os.path.join( + PPA_DIR, BASE_DIR, "INPUT" + ) # full path of input network TRANSIT_CAPACITY_DIR = os.path.join(PIVOT_DIR, "trn") # setup kwargs to pass - kwargs = {} + kwargs = {} if args.kwarg: # assume "all" is special as a kwarg value and should be replaced with the SCENARIO if args.kwarg[1] == "all": @@ -159,144 +231,210 @@ def determineProjectDirectory(OUTPUT_DIR, BASE_DIR, project_short_id): kwargs[args.kwarg[0]] = '"{}"'.format(args.kwarg[1]) # second optional kwarg if args.kwarg2: - kwargs[args.kwarg2[0]] = '"{}"'.format(args.kwarg2[1]) + kwargs[args.kwarg2[0]] = '"{}"'.format(args.kwarg2[1]) if args.output_network: print("Using output network {}".format(args.output_network)) - OUTPUT_DIR = args.output_network + OUTPUT_DIR = args.output_network # make OUTPUT_DIR - if not os.path.exists(OUTPUT_DIR): os.mkdir(OUTPUT_DIR) + if not os.path.exists(OUTPUT_DIR): + os.mkdir(OUTPUT_DIR) OUTPUT_FUTURE_DIR = OUTPUT_DIR # move into it so the scratch is here os.chdir(OUTPUT_DIR) else: - OUTPUT_DIR = os.path.join(PPA_DIR, args.project_short_id) + OUTPUT_DIR = os.path.join(PPA_DIR, args.project_short_id) # make OUTPUT_DIR - if not os.path.exists(OUTPUT_DIR): os.mkdir(OUTPUT_DIR) + if not os.path.exists(OUTPUT_DIR): + os.mkdir(OUTPUT_DIR) - OUTPUT_FUTURE_DIR = determineProjectDirectory(OUTPUT_DIR, BASE_DIR, args.project_short_id) + OUTPUT_FUTURE_DIR = determineProjectDirectory( + OUTPUT_DIR, BASE_DIR, args.project_short_id + ) os.mkdir(OUTPUT_FUTURE_DIR) # move into it so the scratch is here os.chdir(OUTPUT_DIR) # put log file into the run dir - LOG_FILENAME = "addproject_{}_{}_{}_{}.info.LOG".format(PROJECT, SCENARIO, args.project_short_id, NOW) - Wrangler.setupLogging(os.path.join(OUTPUT_FUTURE_DIR, LOG_FILENAME), - os.path.join(OUTPUT_FUTURE_DIR, LOG_FILENAME.replace("info", "debug"))) + LOG_FILENAME = "addproject_{}_{}_{}_{}.info.LOG".format( + PROJECT, SCENARIO, args.project_short_id, NOW + ) + Wrangler.setupLogging( + os.path.join(OUTPUT_FUTURE_DIR, LOG_FILENAME), + os.path.join(OUTPUT_FUTURE_DIR, LOG_FILENAME.replace("info", "debug")), + ) Wrangler.WranglerLogger.info("Input args: {}".format(args)) Wrangler.WranglerLogger.info("Using base directory {}".format(PIVOT_DIR)) # Create a scratch directory to check out project repos into - SCRATCH_SUBDIR = "scratch" - TEMP_SUBDIR = "Wrangler_tmp_" + NOW - if not os.path.exists(SCRATCH_SUBDIR): os.mkdir(SCRATCH_SUBDIR) + SCRATCH_SUBDIR = "scratch" + TEMP_SUBDIR = "Wrangler_tmp_" + NOW + if not os.path.exists(SCRATCH_SUBDIR): + os.mkdir(SCRATCH_SUBDIR) os.chdir(SCRATCH_SUBDIR) networks = { - 'hwy' :Wrangler.HighwayNetwork(modelType=Wrangler.Network.MODEL_TYPE_TM1, modelVersion=1.0, - basenetworkpath=os.path.join(PIVOT_DIR,"hwy"), - networkBaseDir=NETWORK_BASE_DIR, - networkProjectSubdir=None, - networkSeedSubdir=None, - networkPlanSubdir=None, - isTiered=True, - tag=None, - tempdir=TEMP_SUBDIR, - networkName="hwy", - tierNetworkName=HWY_NET_NAME), - 'trn':Wrangler.TransitNetwork( modelType=Wrangler.Network.MODEL_TYPE_TM1, modelVersion=1.0, - basenetworkpath=os.path.join(PIVOT_DIR,"trn"), - networkBaseDir=NETWORK_BASE_DIR, - networkProjectSubdir=None, - networkSeedSubdir=None, - networkPlanSubdir=None, - isTiered=True, - networkName=TRN_NET_NAME) + "hwy": Wrangler.HighwayNetwork( + modelType=Wrangler.Network.MODEL_TYPE_TM1, + modelVersion=1.0, + basenetworkpath=os.path.join(PIVOT_DIR, "hwy"), + networkBaseDir=NETWORK_BASE_DIR, + networkProjectSubdir=None, + networkSeedSubdir=None, + networkPlanSubdir=None, + isTiered=True, + tag=None, + tempdir=TEMP_SUBDIR, + networkName="hwy", + tierNetworkName=HWY_NET_NAME, + ), + "trn": Wrangler.TransitNetwork( + modelType=Wrangler.Network.MODEL_TYPE_TM1, + modelVersion=1.0, + basenetworkpath=os.path.join(PIVOT_DIR, "trn"), + networkBaseDir=NETWORK_BASE_DIR, + networkProjectSubdir=None, + networkSeedSubdir=None, + networkPlanSubdir=None, + isTiered=True, + networkName=TRN_NET_NAME, + ), } if TRANSIT_CAPACITY_DIR: - Wrangler.TransitNetwork.capacity = Wrangler.TransitCapacity(directory=TRANSIT_CAPACITY_DIR) + Wrangler.TransitNetwork.capacity = Wrangler.TransitCapacity( + directory=TRANSIT_CAPACITY_DIR + ) - - for netmode in ["hwy","trn"]: + for netmode in ["hwy", "trn"]: # if applying project if (netmode == "hwy" and args.hwy) or (netmode == "trn" and args.trn): # iterate through projects specified, since args.project is a list for my_project in args.project: - Wrangler.WranglerLogger.info("Applying project [%s] of type [%s]" % (my_project, netmode)) - cloned_SHA1 = networks[netmode].cloneProject(networkdir=my_project, tag=args.tag, - projtype="project", tempdir=TEMP_SUBDIR, **kwargs) - (parentdir, networkdir, gitdir, projectsubdir) = networks[netmode].getClonedProjectArgs(my_project, None, "project", TEMP_SUBDIR) - - applied_SHA1 = networks[netmode].applyProject(parentdir, networkdir, gitdir, projectsubdir, **kwargs) + Wrangler.WranglerLogger.info( + "Applying project [%s] of type [%s]" % (my_project, netmode) + ) + cloned_SHA1 = networks[netmode].cloneProject( + networkdir=my_project, + tag=args.tag, + projtype="project", + tempdir=TEMP_SUBDIR, + **kwargs + ) + (parentdir, networkdir, gitdir, projectsubdir) = networks[ + netmode + ].getClonedProjectArgs(my_project, None, "project", TEMP_SUBDIR) + + applied_SHA1 = networks[netmode].applyProject( + parentdir, networkdir, gitdir, projectsubdir, **kwargs + ) # write networks - final_path = os.path.join(OUTPUT_FUTURE_DIR,netmode) - if not os.path.exists(final_path): os.makedirs(final_path) + final_path = os.path.join(OUTPUT_FUTURE_DIR, netmode) + if not os.path.exists(final_path): + os.makedirs(final_path) - if netmode=="hwy": + if netmode == "hwy": # create the subdir for SET_CAPCLASS with set_capclass.job as apply.s - SET_CAPCLASS = "set_capclass" + SET_CAPCLASS = "set_capclass" SET_CAPCLASS_DIR = os.path.join(TEMP_SUBDIR, SET_CAPCLASS) - if not os.path.isdir(SET_CAPCLASS_DIR): os.makedirs(SET_CAPCLASS_DIR) - source_file = os.path.join(os.path.dirname(THIS_FILE), "set_capclass.job") - shutil.copyfile( source_file, os.path.join(SET_CAPCLASS_DIR, "apply.s")) + if not os.path.isdir(SET_CAPCLASS_DIR): + os.makedirs(SET_CAPCLASS_DIR) + source_file = os.path.join( + os.path.dirname(THIS_FILE), "set_capclass.job" + ) + shutil.copyfile(source_file, os.path.join(SET_CAPCLASS_DIR, "apply.s")) # if hwy project has set_capclass override, copy it to set_capclass/apply.s for my_project in args.project: - set_capclass_override = os.path.join(TEMP_SUBDIR, my_project, "set_capclass.job") + set_capclass_override = os.path.join( + TEMP_SUBDIR, my_project, "set_capclass.job" + ) if os.path.exists(set_capclass_override): dest_file = os.path.join(SET_CAPCLASS_DIR, "apply.s") shutil.copyfile(set_capclass_override, dest_file) - Wrangler.WranglerLogger.info("Copied override {} to {}".format(set_capclass_override, dest_file)) + Wrangler.WranglerLogger.info( + "Copied override {} to {}".format( + set_capclass_override, dest_file + ) + ) # apply set_capclass before writing any hwy network try: - applied_SHA1 = networks[netmode].applyProject(parentdir=TEMP_SUBDIR, networkdir=SET_CAPCLASS, - gitdir=os.path.join(TEMP_SUBDIR, SET_CAPCLASS)) + applied_SHA1 = networks[netmode].applyProject( + parentdir=TEMP_SUBDIR, + networkdir=SET_CAPCLASS, + gitdir=os.path.join(TEMP_SUBDIR, SET_CAPCLASS), + ) except Wrangler.NetworkException as ne: - Wrangler.WranglerLogger.debug("set_capclass exception: {}".format(ne.args[0])) - # this is expected -- since we're using a hack and this isn't a git project - if ne.args[0].startswith("Git log failed"): - pass - else: - raise ne + Wrangler.WranglerLogger.debug( + "set_capclass exception: {}".format(ne.args[0]) + ) + # this is expected -- since we're using a hack and this isn't a git project + if ne.args[0].startswith("Git log failed"): + pass + else: + raise ne # create the subdir for ERROR_CHECK with check_for_errors.job as apply.s - ERROR_CHECK = "check_for_errors" - ERROR_CHECK_DIR = os.path.join(TEMP_SUBDIR, ERROR_CHECK) - if not os.path.isdir(ERROR_CHECK_DIR): os.makedirs(ERROR_CHECK_DIR) - source_file = os.path.join(os.path.dirname(THIS_FILE), "check_for_errors.job") - shutil.copyfile( source_file, os.path.join(ERROR_CHECK_DIR, "apply.s")) + ERROR_CHECK = "check_for_errors" + ERROR_CHECK_DIR = os.path.join(TEMP_SUBDIR, ERROR_CHECK) + if not os.path.isdir(ERROR_CHECK_DIR): + os.makedirs(ERROR_CHECK_DIR) + source_file = os.path.join( + os.path.dirname(THIS_FILE), "check_for_errors.job" + ) + shutil.copyfile(source_file, os.path.join(ERROR_CHECK_DIR, "apply.s")) # apply check_for_errors before writing any hwy network try: - applied_SHA1 = networks[netmode].applyProject(parentdir=TEMP_SUBDIR, networkdir=ERROR_CHECK, - gitdir=os.path.join(TEMP_SUBDIR, ERROR_CHECK)) + applied_SHA1 = networks[netmode].applyProject( + parentdir=TEMP_SUBDIR, + networkdir=ERROR_CHECK, + gitdir=os.path.join(TEMP_SUBDIR, ERROR_CHECK), + ) except Wrangler.NetworkException as ne: - Wrangler.WranglerLogger.debug("check_for_errors exception: {}".format(ne.args[0])) - # this is expected -- since we're using a hack and this isn't a git project - if ne.args[0].startswith("Git log failed"): - pass - else: - raise ne - - networks['hwy'].write(path=final_path,name=HWY_NET_NAME,suppressQuery=True, - suppressValidation=True) # MTC doesn't have turn penalties + Wrangler.WranglerLogger.debug( + "check_for_errors exception: {}".format(ne.args[0]) + ) + # this is expected -- since we're using a hack and this isn't a git project + if ne.args[0].startswith("Git log failed"): + pass + else: + raise ne + + networks["hwy"].write( + path=final_path, + name=HWY_NET_NAME, + suppressQuery=True, + suppressValidation=True, + ) # MTC doesn't have turn penalties else: - networks['trn'].write(path=final_path, - name=TRN_NET_NAME, - writeEmptyFiles = False, - suppressQuery = False, - suppressValidation = False, - cubeNetFileForValidation = os.path.join(os.path.abspath(OUTPUT_FUTURE_DIR), "hwy", HWY_NET_NAME)) + networks["trn"].write( + path=final_path, + name=TRN_NET_NAME, + writeEmptyFiles=False, + suppressQuery=False, + suppressValidation=False, + cubeNetFileForValidation=os.path.join( + os.path.abspath(OUTPUT_FUTURE_DIR), "hwy", HWY_NET_NAME + ), + ) # Write the transit capacity configuration - Wrangler.TransitNetwork.capacity.writeTransitVehicleToCapacity(directory = final_path) - Wrangler.TransitNetwork.capacity.writeTransitLineToVehicle(directory = final_path) - Wrangler.TransitNetwork.capacity.writeTransitPrefixToVehicle(directory = final_path) - - Wrangler.WranglerLogger.debug("Successfully completed running %s" % os.path.abspath(__file__)) + Wrangler.TransitNetwork.capacity.writeTransitVehicleToCapacity( + directory=final_path + ) + Wrangler.TransitNetwork.capacity.writeTransitLineToVehicle( + directory=final_path + ) + Wrangler.TransitNetwork.capacity.writeTransitPrefixToVehicle( + directory=final_path + ) + + Wrangler.WranglerLogger.debug( + "Successfully completed running %s" % os.path.abspath(__file__) + ) diff --git a/scripts/build_network_mtc_blueprint.py b/scripts/build_network_mtc_blueprint.py index ce6c81b..e778af9 100644 --- a/scripts/build_network_mtc_blueprint.py +++ b/scripts/build_network_mtc_blueprint.py @@ -1,4 +1,4 @@ -import argparse,collections,copy,datetime,os,pandas,shutil,sys,time +import argparse, collections, copy, datetime, os, pandas, shutil, sys, time import Wrangler # Based on NetworkWrangler\scripts\build_network.py @@ -52,10 +52,10 @@ # OPTIONAL. The default route network project directory is Y:\networks. If # projects are stored in another directory, then use this variable to specify it. # For example: Y:\networks\projects -NETWORK_BASE_DIR = None +NETWORK_BASE_DIR = None NETWORK_PROJECT_SUBDIR = None -NETWORK_SEED_SUBDIR = None -NETWORK_PLAN_SUBDIR = None +NETWORK_SEED_SUBDIR = None +NETWORK_PLAN_SUBDIR = None # OPTIONAL. A list of project names which have been previously applied in the # PIVOT_DIR network that projects in this project might rely on. For example @@ -67,8 +67,8 @@ # the TAG. This is meant for developing a network project. TEST_PROJECTS = None -TRN_MODES = ['trn'] -NET_MODES = ['hwy'] + TRN_MODES +TRN_MODES = ["trn"] +NET_MODES = ["hwy"] + TRN_MODES THIS_FILE = os.path.realpath(__file__) ############################################################################### @@ -79,31 +79,34 @@ # # ############################################################################### def getProjectNameAndDir(project): - if type(project) == type({'this is':'a dict'}): - name = project['name'] + if type(project) == type({"this is": "a dict"}): + name = project["name"] else: name = project - (path,name) = os.path.split(name) - return (path,name) + (path, name) = os.path.split(name) + return (path, name) + def getNetworkListIndex(project, networks): for proj in networks: - (path,name) = getProjectNameAndDir(proj) - if project == name or project == os.path.join(path,name): + (path, name) = getProjectNameAndDir(proj) + if project == name or project == os.path.join(path, name): return networks.index(proj) return None + def getProjectMatchLevel(left, right): - (left_path,left_name) = getProjectNameAndDir(left) - (right_path,right_name) = getProjectNameAndDir(right) + (left_path, left_name) = getProjectNameAndDir(left) + (right_path, right_name) = getProjectNameAndDir(right) match = 0 - if os.path.join(left_path,left_name) == os.path.join(right_path,right_name): + if os.path.join(left_path, left_name) == os.path.join(right_path, right_name): match = 2 elif left_name == right_name: match = 1 - #Wrangler.WranglerLogger.debug("Match level %d for %s and %s" % (match, os.path.join(left_path,left_name), os.path.join(right_path,right_name))) + # Wrangler.WranglerLogger.debug("Match level %d for %s and %s" % (match, os.path.join(left_path,left_name), os.path.join(right_path,right_name))) return match + def getProjectYear(PROJECTS, my_proj, netmode): """ PROJECTS is an OrderedDict, year -> netmode -> [ project list ] @@ -113,14 +116,15 @@ def getProjectYear(PROJECTS, my_proj, netmode): for year in PROJECTS.keys(): for proj_idx in range(len(PROJECTS[year][netmode])): proj = PROJECTS[year][netmode][proj_idx] - if type(proj) is dict and my_proj == proj['name']: - return "{}.{}.{:0>2d}".format(year,netmode,proj_idx+1) + if type(proj) is dict and my_proj == proj["name"]: + return "{}.{}.{:0>2d}".format(year, netmode, proj_idx + 1) elif proj == my_proj: - return "{}.{}.{:0>2d}".format(year,netmode,proj_idx+1) + return "{}.{}.{:0>2d}".format(year, netmode, proj_idx + 1) return -1 -def checkRequirements(REQUIREMENTS, PROJECTS, req_type='prereq'): - if req_type not in ('prereq','coreq','conflict'): + +def checkRequirements(REQUIREMENTS, PROJECTS, req_type="prereq"): + if req_type not in ("prereq", "coreq", "conflict"): return (None, None) # Wrangler.WranglerLogger.debug("checkRequirements called with requirements=[{}] projects=[{}] req_typ={}".format(REQUIREMENTS, PROJECTS, req_type)) @@ -132,26 +136,40 @@ def checkRequirements(REQUIREMENTS, PROJECTS, req_type='prereq'): for project in REQUIREMENTS[netmode].keys(): project_year = getProjectYear(PROJECTS, project, netmode) if project_year == -1: - Wrangler.WranglerLogger.warning('Cannot find the {} project {} to check its requirements'.format(netmode, project)) + Wrangler.WranglerLogger.warning( + "Cannot find the {} project {} to check its requirements".format( + netmode, project + ) + ) continue # raise? - Wrangler.WranglerLogger.info('Checking {} project {} ({}) for {}'.format(netmode, project, project_year, req_type)) + Wrangler.WranglerLogger.info( + "Checking {} project {} ({}) for {}".format( + netmode, project, project_year, req_type + ) + ) for req_netmode in REQUIREMENTS[netmode][project].keys(): - req_proj_list = REQUIREMENTS[netmode][project][req_netmode] + req_proj_list = REQUIREMENTS[netmode][project][req_netmode] req_proj_years = {} for req_proj in req_proj_list: req_project_year = getProjectYear(PROJECTS, req_proj, req_netmode) # prereq - if req_type=="prereq": + if req_type == "prereq": if (type(req_project_year) == int) and (req_project_year < 0): is_ok = False # required project must be found - Wrangler.WranglerLogger.warning("required project not found") + Wrangler.WranglerLogger.warning( + "required project not found" + ) if req_project_year > project_year: is_ok = False # and implemented before or at the same time as the project - Wrangler.WranglerLogger.warning("required project year {} > project year {}".format(req_project_year, project_year)) + Wrangler.WranglerLogger.warning( + "required project year {} > project year {}".format( + req_project_year, project_year + ) + ) # save into proj_years req_proj_years[req_proj] = req_project_year @@ -161,54 +179,72 @@ def checkRequirements(REQUIREMENTS, PROJECTS, req_type='prereq'): return (REQUIREMENTS, is_ok) -def writeRequirements(REQUIREMENTS, PROJECTS, req_type='prereq'): - if req_type=='prereq': - print_req = 'Pre-requisite' - elif req_type=='coreq': - print_req = 'Co-requisite' - elif req_type=='conflict': - print_req = 'Conflict' + +def writeRequirements(REQUIREMENTS, PROJECTS, req_type="prereq"): + if req_type == "prereq": + print_req = "Pre-requisite" + elif req_type == "coreq": + print_req = "Co-requisite" + elif req_type == "conflict": + print_req = "Conflict" else: return None Wrangler.WranglerLogger.info("Requirement verification - {}".format(print_req)) - Wrangler.WranglerLogger.info(" Year {:50} {:50} Year".format("Project",print_req+" " + "Project")) + Wrangler.WranglerLogger.info( + " Year {:50} {:50} Year".format( + "Project", print_req + " " + "Project" + ) + ) # REQUIREMENTS: netmode -> project -> netmode -> req_proj -> req_proj_year for netmode in REQUIREMENTS.keys(): for project in REQUIREMENTS[netmode].keys(): project_year = getProjectYear(PROJECTS, project, netmode) for req_netmode in REQUIREMENTS[netmode][project].keys(): for req_project in REQUIREMENTS[netmode][project][req_netmode].keys(): - Wrangler.WranglerLogger.info("{} {} {:50} {} {:50} {}".format(netmode, project_year, project, - req_netmode, req_project, REQUIREMENTS[netmode][project][req_netmode][req_project])) + Wrangler.WranglerLogger.info( + "{} {} {:50} {} {:50} {}".format( + netmode, + project_year, + project, + req_netmode, + req_project, + REQUIREMENTS[netmode][project][req_netmode][req_project], + ) + ) + def getProjectAttributes(project): # Start with TAG if not build mode, no kwargs - project_type = 'project' - tag = None - kwargs = {} + project_type = "project" + tag = None + kwargs = {} # Use project name, tags, kwargs from dictionary - if type(project)==type({'this is':'a dictionary'}): - project_name = project['name'] - if 'tag' in project: tag = project['tag'] - if 'type' in project: project_type = project['type'] - if 'kwargs' in project: kwargs = project['kwargs'] + if type(project) == type({"this is": "a dictionary"}): + project_name = project["name"] + if "tag" in project: + tag = project["tag"] + if "type" in project: + project_type = project["type"] + if "kwargs" in project: + kwargs = project["kwargs"] # Use Project name directly - elif type(project)==type("string"): + elif type(project) == type("string"): project_name = project # Other structures not recognized else: - Wrangler.WranglerLogger.fatal("Don't understand project %s" % str(project)) + Wrangler.WranglerLogger.fatal("Don't understand project %s" % str(project)) return (project_name, project_type, tag, kwargs) + def preCheckRequirementsForAllProjects(networks, continue_on_warning): - PRE_REQS = {'hwy':{},'trn':{}} - CO_REQS = {'hwy':{},'trn':{}} - CONFLICTS = {'hwy':{},'trn':{}} + PRE_REQS = {"hwy": {}, "trn": {}} + CO_REQS = {"hwy": {}, "trn": {}} + CONFLICTS = {"hwy": {}, "trn": {}} # Network Loop #1: check out all the projects, check if they're stale, check if they're the head repository. Build completed # project list so we can check pre-reqs, etc, in loop #2. @@ -219,64 +255,107 @@ def preCheckRequirementsForAllProjects(networks, continue_on_warning): for model_year in NETWORK_PROJECTS.keys(): for project in NETWORK_PROJECTS[model_year][netmode]: (project_name, projType, tag, kwargs) = getProjectAttributes(project) - if tag == None: tag = TAG + if tag == None: + tag = TAG # test mode - don't use TAG for TEST_PROJECTS - if BUILD_MODE=="test" and type(TEST_PROJECTS)==type(['List']): + if BUILD_MODE == "test" and type(TEST_PROJECTS) == type(["List"]): if project_name in TEST_PROJECTS: - Wrangler.WranglerLogger.debug("Skipping tag [%s] because test mode and [%s] is in TEST_PROJECTS" % - (TAG, project_name)) + Wrangler.WranglerLogger.debug( + "Skipping tag [%s] because test mode and [%s] is in TEST_PROJECTS" + % (TAG, project_name) + ) tag = None Wrangler.WranglerLogger.debug("Project name = %s" % project_name) cloned_SHA1 = None # if project = "dir1/dir2" assume dir1 is git, dir2 is the projectsubdir - (head,tail) = os.path.split(project_name) + (head, tail) = os.path.split(project_name) if head: - cloned_SHA1 = networks[netmode].cloneProject(networkdir=head, projectsubdir=tail, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR, **kwargs) - (prereqs, coreqs, conflicts) = networks[netmode].getReqs(networkdir=head, projectsubdir=tail, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR) + cloned_SHA1 = networks[netmode].cloneProject( + networkdir=head, + projectsubdir=tail, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + **kwargs + ) + (prereqs, coreqs, conflicts) = networks[netmode].getReqs( + networkdir=head, + projectsubdir=tail, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + ) else: - cloned_SHA1 = networks[netmode].cloneProject(networkdir=project_name, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR, **kwargs) - (prereqs, coreqs, conflicts) = networks[netmode].getReqs(networkdir=project_name, projectsubdir=tail, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR) + cloned_SHA1 = networks[netmode].cloneProject( + networkdir=project_name, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + **kwargs + ) + (prereqs, coreqs, conflicts) = networks[netmode].getReqs( + networkdir=project_name, + projectsubdir=tail, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + ) # find out if the applied project is behind HEAD # get the HEAD SHA1 cmd = r"git show-ref --head master" - if projType=='project': + if projType == "project": join_subdir = Wrangler.Network.NETWORK_PROJECT_SUBDIR - if projType=='seed': + if projType == "seed": join_subdir = Wrangler.Network.NETWORK_SEED_SUBDIR - cmd_dir = os.path.join(Wrangler.Network.NETWORK_BASE_DIR, join_subdir, project_name) - (retcode, retStdout, retStderr) = networks[netmode]._runAndLog(cmd, run_dir = cmd_dir) + cmd_dir = os.path.join( + Wrangler.Network.NETWORK_BASE_DIR, join_subdir, project_name + ) + (retcode, retStdout, retStderr) = networks[netmode]._runAndLog( + cmd, run_dir=cmd_dir + ) # Wrangler.WranglerLogger.debug("results of [%s]: %s %s %s" % (cmd, str(retcode), str(retStdout), str(retStderr))) - if retcode != 0: # this shouldn't happen -- wouldn't cloneAndApply have failed? - Wrangler.WranglerLogger.fatal("Couldn't run cmd [%s] in [%s]: stdout=[%s] stderr=[%s]" % \ - (cmd, cmd_dir, str(retStdout), str(retStderr))) + if ( + retcode != 0 + ): # this shouldn't happen -- wouldn't cloneAndApply have failed? + Wrangler.WranglerLogger.fatal( + "Couldn't run cmd [%s] in [%s]: stdout=[%s] stderr=[%s]" + % (cmd, cmd_dir, str(retStdout), str(retStderr)) + ) sys.exit(2) head_SHA1 = retStdout[0].split()[0] # if they're different, log more information and get approval (if not in test mode) if cloned_SHA1 != head_SHA1: - Wrangler.WranglerLogger.warning("Using non-head version of project of %s" % project_name) - Wrangler.WranglerLogger.warning(" Applying version [%s], Head is [%s]" % (cloned_SHA1, head_SHA1)) + Wrangler.WranglerLogger.warning( + "Using non-head version of project of %s" % project_name + ) + Wrangler.WranglerLogger.warning( + " Applying version [%s], Head is [%s]" + % (cloned_SHA1, head_SHA1) + ) cmd = "git log %s..%s" % (cloned_SHA1, head_SHA1) - (retcode, retStdout, retStderr) = networks[netmode]._runAndLog(cmd, run_dir = cmd_dir) - Wrangler.WranglerLogger.warning(" The following commits are not included:") + (retcode, retStdout, retStderr) = networks[netmode]._runAndLog( + cmd, run_dir=cmd_dir + ) + Wrangler.WranglerLogger.warning( + " The following commits are not included:" + ) for line in retStdout: Wrangler.WranglerLogger.warning(" %s" % line) # test mode => warn is sufficient # non-test mode => get explicit approval if continue_on_warning: - Wrangler.WranglerLogger.warning("Continuing (continue_on_warning)") - elif BUILD_MODE !="test" and not continue_on_warning: + Wrangler.WranglerLogger.warning( + "Continuing (continue_on_warning)" + ) + elif BUILD_MODE != "test" and not continue_on_warning: Wrangler.WranglerLogger.warning(" Is this ok? (y/n) ") response = input("") Wrangler.WranglerLogger.debug(" response = [%s]" % response) @@ -286,71 +365,105 @@ def preCheckRequirementsForAllProjects(networks, continue_on_warning): # find out if the project is stale else: cmd = 'git show -s --format="%%ct" %s' % cloned_SHA1 - (retcode, retStdout, retStderr) = networks[netmode]._runAndLog(cmd, run_dir = cmd_dir) - applied_commit_date = datetime.datetime.fromtimestamp(int(retStdout[0])) + (retcode, retStdout, retStderr) = networks[netmode]._runAndLog( + cmd, run_dir=cmd_dir + ) + applied_commit_date = datetime.datetime.fromtimestamp( + int(retStdout[0]) + ) applied_commit_age = datetime.datetime.now() - applied_commit_date # if older than STALE_YEARS year, holler STALE_YEARS = 5 - if applied_commit_age > datetime.timedelta(days=365*STALE_YEARS): - Wrangler.WranglerLogger.warning(" This project was last updated %.1f years ago (over %d), on %s" % \ - (applied_commit_age.days/365.0, - STALE_YEARS, applied_commit_date.strftime("%x"))) + if applied_commit_age > datetime.timedelta(days=365 * STALE_YEARS): + Wrangler.WranglerLogger.warning( + " This project was last updated %.1f years ago (over %d), on %s" + % ( + applied_commit_age.days / 365.0, + STALE_YEARS, + applied_commit_date.strftime("%x"), + ) + ) if continue_on_warning: - Wrangler.WranglerLogger.warning("Continuing (continue_on_warning)") - elif BUILD_MODE !="test": + Wrangler.WranglerLogger.warning( + "Continuing (continue_on_warning)" + ) + elif BUILD_MODE != "test": Wrangler.WranglerLogger.warning(" Is this ok? (y/n) ") response = input("") - Wrangler.WranglerLogger.debug(" response = [%s]" % response) + Wrangler.WranglerLogger.debug( + " response = [%s]" % response + ) if response.strip().lower() not in ["y", "yes"]: sys.exit(2) clonedcount += 1 # format: netmode -> project -> { netmode: [requirements] } - if len(prereqs ) > 0: PRE_REQS[ netmode][project_name] = prereqs - if len(coreqs ) > 0: CO_REQS[ netmode][project_name] = coreqs - if len(conflicts) > 0: CONFLICTS[netmode][project_name] = conflicts + if len(prereqs) > 0: + PRE_REQS[netmode][project_name] = prereqs + if len(coreqs) > 0: + CO_REQS[netmode][project_name] = coreqs + if len(conflicts) > 0: + CONFLICTS[netmode][project_name] = conflicts # Check requirements - prFile = 'prereqs.csv' - crFile = 'coreqs.csv' - cfFile = 'conflicts.csv' + prFile = "prereqs.csv" + crFile = "coreqs.csv" + cfFile = "conflicts.csv" # Check prereqs - (PRE_REQS, allPrereqsFound) = checkRequirements(PRE_REQS, NETWORK_PROJECTS, req_type='prereq') - if len(PRE_REQS['trn'])>0 or len(PRE_REQS['hwy'])>0: - writeRequirements(PRE_REQS, NETWORK_PROJECTS, req_type='prereq') + (PRE_REQS, allPrereqsFound) = checkRequirements( + PRE_REQS, NETWORK_PROJECTS, req_type="prereq" + ) + if len(PRE_REQS["trn"]) > 0 or len(PRE_REQS["hwy"]) > 0: + writeRequirements(PRE_REQS, NETWORK_PROJECTS, req_type="prereq") if allPrereqsFound: - Wrangler.WranglerLogger.debug('All PRE-REQUISITES were found. Are the PRE-REQUISITES matches correct? (y/n)') + Wrangler.WranglerLogger.debug( + "All PRE-REQUISITES were found. Are the PRE-REQUISITES matches correct? (y/n)" + ) else: - Wrangler.WranglerLogger.debug('!!!WARNING!!! Some PRE-REQUISITES were not found or ordered correctly. Continue anyway? (y/n)') + Wrangler.WranglerLogger.debug( + "!!!WARNING!!! Some PRE-REQUISITES were not found or ordered correctly. Continue anyway? (y/n)" + ) response = input("") Wrangler.WranglerLogger.debug(" response = [%s]" % response) if response.strip().lower() not in ["y", "yes"]: sys.exit(2) # Check coreqs - (CO_REQS, allCoreqsFound) = checkRequirements(CO_REQS, NETWORK_PROJECTS, req_type='coreq') - if len(CO_REQS['trn'])>0 or len(CO_REQS['hwy'])>0: - writeRequirements(CO_REQS, NETWORK_PROJECTS, req_type='coreq') + (CO_REQS, allCoreqsFound) = checkRequirements( + CO_REQS, NETWORK_PROJECTS, req_type="coreq" + ) + if len(CO_REQS["trn"]) > 0 or len(CO_REQS["hwy"]) > 0: + writeRequirements(CO_REQS, NETWORK_PROJECTS, req_type="coreq") if allCoreqsFound: - Wrangler.WranglerLogger.debug('All CO-REQUISITES were found. Are the CO-REQUISITE matches correct? (y/n)') + Wrangler.WranglerLogger.debug( + "All CO-REQUISITES were found. Are the CO-REQUISITE matches correct? (y/n)" + ) else: - Wrangler.WranglerLogger.debug('!!!WARNING!!! Some CO-REQUISITES were not found. Continue anyway? (y/n)') + Wrangler.WranglerLogger.debug( + "!!!WARNING!!! Some CO-REQUISITES were not found. Continue anyway? (y/n)" + ) response = input("") Wrangler.WranglerLogger.debug(" response = [%s]" % response) if response.strip().lower() not in ["y", "yes"]: sys.exit(2) # Check conflicts - (CONFLICTS, anyConflictFound) = checkRequirements(CONFLICTS, NETWORK_PROJECTS, req_type='conflict') - if len(CONFLICTS['trn'])>0 or len(CONFLICTS['hwy'])>0: - writeRequirements(CONFLICTS, NETWORK_PROJECTS, 'conflict') + (CONFLICTS, anyConflictFound) = checkRequirements( + CONFLICTS, NETWORK_PROJECTS, req_type="conflict" + ) + if len(CONFLICTS["trn"]) > 0 or len(CONFLICTS["hwy"]) > 0: + writeRequirements(CONFLICTS, NETWORK_PROJECTS, "conflict") if anyConflictFound: - Wrangler.WranglerLogger.debug('!!!WARNING!!! Conflicting projects were found. Continue anyway? (y/n)') + Wrangler.WranglerLogger.debug( + "!!!WARNING!!! Conflicting projects were found. Continue anyway? (y/n)" + ) else: - Wrangler.WranglerLogger.debug('No conflicting projects were found. Enter \'y\' to continue.') + Wrangler.WranglerLogger.debug( + "No conflicting projects were found. Enter 'y' to continue." + ) response = input("") Wrangler.WranglerLogger.debug(" response = [%s]" % response) if response.strip().lower() not in ["y", "yes"]: @@ -358,116 +471,165 @@ def preCheckRequirementsForAllProjects(networks, continue_on_warning): # Wrangler.WranglerLogger.debug("NETWORK_PROJECTS=%s NET_MODES=%s" % (str(NETWORK_PROJECTS), str(NET_MODES))) + ############################################################################### -if __name__ == '__main__': - parser = argparse.ArgumentParser(description=USAGE, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument("--configword", help="optional word for network specification script") - parser.add_argument("--model_type", choices=[Wrangler.Network.MODEL_TYPE_TM1, Wrangler.Network.MODEL_TYPE_TM2], - default=Wrangler.Network.MODEL_TYPE_TM1) - parser.add_argument("--continue_on_warning", help="Don't prompt the user to continue if there are warnings; just warn and continue", action="store_true") - parser.add_argument("--skip_precheck_requirements", help="Don't precheck network requirements, stale projects, non-HEAD projects, etc", action="store_true") - parser.add_argument("net_spec", metavar="network_specification.py", help="Script which defines required variables indicating how to build the network") - parser.add_argument("netvariant", choices=["Baseline", "Blueprint", "Alt1", "Alt2", "NextGenFwy","TIP2023"], help="Specify which network variant network to create.") +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=USAGE, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + "--configword", help="optional word for network specification script" + ) + parser.add_argument( + "--model_type", + choices=[Wrangler.Network.MODEL_TYPE_TM1, Wrangler.Network.MODEL_TYPE_TM2], + default=Wrangler.Network.MODEL_TYPE_TM1, + ) + parser.add_argument( + "--continue_on_warning", + help="Don't prompt the user to continue if there are warnings; just warn and continue", + action="store_true", + ) + parser.add_argument( + "--skip_precheck_requirements", + help="Don't precheck network requirements, stale projects, non-HEAD projects, etc", + action="store_true", + ) + parser.add_argument( + "net_spec", + metavar="network_specification.py", + help="Script which defines required variables indicating how to build the network", + ) + parser.add_argument( + "netvariant", + choices=["Baseline", "Blueprint", "Alt1", "Alt2", "NextGenFwy", "TIP2023"], + help="Specify which network variant network to create.", + ) args = parser.parse_args() - NOW = time.strftime("%Y%b%d.%H%M%S") - BUILD_MODE = None # regular + NOW = time.strftime("%Y%b%d.%H%M%S") + BUILD_MODE = None # regular if args.model_type == Wrangler.Network.MODEL_TYPE_TM1: - PIVOT_DIR = r"M:\\Application\\Model One\\Networks\\TM1_2015_Base_Network" + PIVOT_DIR = r"M:\\Application\\Model One\\Networks\\TM1_2015_Base_Network" TRANSIT_CAPACITY_DIR = os.path.join(PIVOT_DIR, "trn") NETWORK_BASE_DIR = r"M:\\Application\\Model One\\NetworkProjects" - TRN_SUBDIR = "trn" - TRN_NET_NAME = "Transit_Lines" - HWY_SUBDIR = "hwy" - HWY_NET_NAME = "freeflow.net" + TRN_SUBDIR = "trn" + TRN_NET_NAME = "Transit_Lines" + HWY_SUBDIR = "hwy" + HWY_NET_NAME = "freeflow.net" elif args.model_type == Wrangler.Network.MODEL_TYPE_TM2: - PIVOT_DIR = os.path.join(os.environ["USERPROFILE"], "Box","Modeling and Surveys","Development","Travel Model Two Development","Model Inputs","2015_revised_mazs") + PIVOT_DIR = os.path.join( + os.environ["USERPROFILE"], + "Box", + "Modeling and Surveys", + "Development", + "Travel Model Two Development", + "Model Inputs", + "2015_revised_mazs", + ) TRANSIT_CAPACITY_DIR = None NETWORK_BASE_DIR = r"M:\\Application\\Model Two\\NetworkProjects" - TRN_SUBDIR = "trn" - TRN_NET_NAME = "transitLines" - HWY_SUBDIR = "hwy" - HWY_NET_NAME = "mtc_final_network_base.net" + TRN_SUBDIR = "trn" + TRN_NET_NAME = "transitLines" + HWY_SUBDIR = "hwy" + HWY_NET_NAME = "mtc_final_network_base.net" PROJECT = "Blueprint" # Read the configuration NETWORK_CONFIG = args.net_spec - NET_VARIANT = args.netvariant + NET_VARIANT = args.netvariant # networks and log file will be in BlueprintNetworks if not os.path.exists("BlueprintNetworks"): os.mkdir("BlueprintNetworks") - LOG_FILENAME = "build%snetwork_%s_%s_%s.info.LOG" % ("TEST" if BUILD_MODE=="test" else "", PROJECT, NET_VARIANT, NOW) - Wrangler.setupLogging(os.path.join("BlueprintNetworks",LOG_FILENAME), - os.path.join("BlueprintNetworks",LOG_FILENAME.replace("info", "debug"))) + LOG_FILENAME = "build%snetwork_%s_%s_%s.info.LOG" % ( + "TEST" if BUILD_MODE == "test" else "", + PROJECT, + NET_VARIANT, + NOW, + ) + Wrangler.setupLogging( + os.path.join("BlueprintNetworks", LOG_FILENAME), + os.path.join("BlueprintNetworks", LOG_FILENAME.replace("info", "debug")), + ) exec(open(NETWORK_CONFIG).read()) # Verify mandatory fields are set - if PROJECT==None: + if PROJECT == None: print("PROJECT not set in %s" % NETWORK_CONFIG) sys.exit(2) - if TAG==None: + if TAG == None: print("TAG not set in %s" % NETWORK_CONFIG) sys.exit(2) - if NETWORK_PROJECTS==None: + if NETWORK_PROJECTS == None: print("NETWORK_PROJECTS not set in %s" % NETWORK_CONFIG) sys.exit(2) if TRANSIT_CAPACITY_DIR: - Wrangler.TransitNetwork.capacity = Wrangler.TransitCapacity(directory=TRANSIT_CAPACITY_DIR) + Wrangler.TransitNetwork.capacity = Wrangler.TransitCapacity( + directory=TRANSIT_CAPACITY_DIR + ) # Create a scratch directory to check out project repos into SCRATCH_SUBDIR = "scratch" - TEMP_SUBDIR = "Wrangler_tmp_" + NOW - if not os.path.exists(SCRATCH_SUBDIR): os.mkdir(SCRATCH_SUBDIR) + TEMP_SUBDIR = "Wrangler_tmp_" + NOW + if not os.path.exists(SCRATCH_SUBDIR): + os.mkdir(SCRATCH_SUBDIR) os.chdir(SCRATCH_SUBDIR) - os.environ["CHAMP_node_names"] = os.path.join(PIVOT_DIR,"Node Description.xls") + os.environ["CHAMP_node_names"] = os.path.join(PIVOT_DIR, "Node Description.xls") networks = { - 'hwy' :Wrangler.HighwayNetwork(modelType=args.model_type, modelVersion=1.0, - basenetworkpath=os.path.join(PIVOT_DIR,"hwy"), - networkBaseDir=NETWORK_BASE_DIR, - networkProjectSubdir=NETWORK_PROJECT_SUBDIR, - networkSeedSubdir=NETWORK_SEED_SUBDIR, - networkPlanSubdir=NETWORK_PLAN_SUBDIR, - isTiered=True if PIVOT_DIR else False, - tag=TAG, - tempdir=TEMP_SUBDIR, - networkName="hwy", - tierNetworkName=HWY_NET_NAME), - 'trn':Wrangler.TransitNetwork( modelType=args.model_type, modelVersion=1.0, - basenetworkpath=os.path.join(PIVOT_DIR,"trn"), - networkBaseDir=NETWORK_BASE_DIR, - networkProjectSubdir=NETWORK_PROJECT_SUBDIR, - networkSeedSubdir=NETWORK_SEED_SUBDIR, - networkPlanSubdir=NETWORK_PLAN_SUBDIR, - isTiered=True if PIVOT_DIR else False, - networkName=TRN_NET_NAME) + "hwy": Wrangler.HighwayNetwork( + modelType=args.model_type, + modelVersion=1.0, + basenetworkpath=os.path.join(PIVOT_DIR, "hwy"), + networkBaseDir=NETWORK_BASE_DIR, + networkProjectSubdir=NETWORK_PROJECT_SUBDIR, + networkSeedSubdir=NETWORK_SEED_SUBDIR, + networkPlanSubdir=NETWORK_PLAN_SUBDIR, + isTiered=True if PIVOT_DIR else False, + tag=TAG, + tempdir=TEMP_SUBDIR, + networkName="hwy", + tierNetworkName=HWY_NET_NAME, + ), + "trn": Wrangler.TransitNetwork( + modelType=args.model_type, + modelVersion=1.0, + basenetworkpath=os.path.join(PIVOT_DIR, "trn"), + networkBaseDir=NETWORK_BASE_DIR, + networkProjectSubdir=NETWORK_PROJECT_SUBDIR, + networkSeedSubdir=NETWORK_SEED_SUBDIR, + networkPlanSubdir=NETWORK_PLAN_SUBDIR, + isTiered=True if PIVOT_DIR else False, + networkName=TRN_NET_NAME, + ), } # For projects applied in a pivot network (because they won't show up in the current project list) if APPLIED_PROJECTS != None: for proj in APPLIED_PROJECTS: - networks['hwy'].appliedProjects[proj]=TAG - + networks["hwy"].appliedProjects[proj] = TAG # Wrangler.WranglerLogger.debug("NETWORK_PROJECTS=%s NET_MODES=%s" % (str(NETWORK_PROJECTS), str(NET_MODES))) if args.skip_precheck_requirements: - Wrangler.WranglerLogger.info("skip_precheck_requirements passed so skipping preCheckRequirementsForAllProjects()") + Wrangler.WranglerLogger.info( + "skip_precheck_requirements passed so skipping preCheckRequirementsForAllProjects()" + ) else: preCheckRequirementsForAllProjects(networks, args.continue_on_warning) # create the subdir for SET_CAPCLASS with set_capclass.job as apply.s - SET_CAPCLASS = "set_capclass" + SET_CAPCLASS = "set_capclass" SET_CAPCLASS_DIR = os.path.join(TEMP_SUBDIR, SET_CAPCLASS) os.makedirs(SET_CAPCLASS_DIR) - source_file = os.path.join(os.path.dirname(THIS_FILE), "set_capclass.job") - shutil.copyfile( source_file, os.path.join(SET_CAPCLASS_DIR, "apply.s")) + source_file = os.path.join(os.path.dirname(THIS_FILE), "set_capclass.job") + shutil.copyfile(source_file, os.path.join(SET_CAPCLASS_DIR, "apply.s")) networks_without_earthquake = {} @@ -477,123 +639,223 @@ def preCheckRequirementsForAllProjects(networks, continue_on_warning): appliedcount = 0 for netmode in NET_MODES: - Wrangler.WranglerLogger.info("Building {} {} networks".format(YEAR, netmode)) + Wrangler.WranglerLogger.info( + "Building {} {} networks".format(YEAR, netmode) + ) for project in projects_for_year[netmode]: (project_name, projType, tag, kwargs) = getProjectAttributes(project) - if tag == None: tag = TAG - - Wrangler.WranglerLogger.info("Applying project [{}] of type [{}] with tag [{}] and kwargs[{}]".format(project_name, projType, tag, kwargs)) - if projType=='plan': + if tag == None: + tag = TAG + + Wrangler.WranglerLogger.info( + "Applying project [{}] of type [{}] with tag [{}] and kwargs[{}]".format( + project_name, projType, tag, kwargs + ) + ) + if projType == "plan": continue applied_SHA1 = None - cloned_SHA1 = networks[netmode].cloneProject(networkdir=project_name, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR, **kwargs) - (parentdir, networkdir, gitdir, projectsubdir) = networks[netmode].getClonedProjectArgs(project_name, None, projType, TEMP_SUBDIR) - - applied_SHA1 = networks[netmode].applyProject(parentdir, networkdir, gitdir, projectsubdir, **kwargs) + cloned_SHA1 = networks[netmode].cloneProject( + networkdir=project_name, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + **kwargs + ) + (parentdir, networkdir, gitdir, projectsubdir) = networks[ + netmode + ].getClonedProjectArgs(project_name, None, projType, TEMP_SUBDIR) + + applied_SHA1 = networks[netmode].applyProject( + parentdir, networkdir, gitdir, projectsubdir, **kwargs + ) appliedcount += 1 # if hwy project has set_capclass override, copy it to set_capclass/apply.s - set_capclass_override = os.path.join(TEMP_SUBDIR, project_name, "set_capclass.job") + set_capclass_override = os.path.join( + TEMP_SUBDIR, project_name, "set_capclass.job" + ) if os.path.exists(set_capclass_override): dest_file = os.path.join(SET_CAPCLASS_DIR, "apply.s") shutil.copyfile(set_capclass_override, dest_file) - Wrangler.WranglerLogger.info("Copied override {} to {}".format(set_capclass_override, dest_file)) - + Wrangler.WranglerLogger.info( + "Copied override {} to {}".format( + set_capclass_override, dest_file + ) + ) if appliedcount == 0: - Wrangler.WranglerLogger.info("No applied projects for this year -- skipping output") + Wrangler.WranglerLogger.info( + "No applied projects for this year -- skipping output" + ) continue - if NET_VARIANT!="Baseline" and YEAR==2015: - Wrangler.WranglerLogger.info("Blueprint 2015 == Baseline 2015 -- skipping output") + if NET_VARIANT != "Baseline" and YEAR == 2015: + Wrangler.WranglerLogger.info( + "Blueprint 2015 == Baseline 2015 -- skipping output" + ) continue # Baseline AND YEAR >= 2035 get SLR, covered in next clause - if NET_VARIANT!="Baseline" or YEAR<2035: - - hwypath=os.path.join("..", "BlueprintNetworks", "net_{}_{}".format(YEAR, NET_VARIANT), HWY_SUBDIR) - if not os.path.exists(hwypath): os.makedirs(hwypath) - trnpath = os.path.join("..", "BlueprintNetworks", "net_{}_{}".format(YEAR, NET_VARIANT), TRN_SUBDIR) - if not os.path.exists(trnpath): os.makedirs(trnpath) + if NET_VARIANT != "Baseline" or YEAR < 2035: + + hwypath = os.path.join( + "..", + "BlueprintNetworks", + "net_{}_{}".format(YEAR, NET_VARIANT), + HWY_SUBDIR, + ) + if not os.path.exists(hwypath): + os.makedirs(hwypath) + trnpath = os.path.join( + "..", + "BlueprintNetworks", + "net_{}_{}".format(YEAR, NET_VARIANT), + TRN_SUBDIR, + ) + if not os.path.exists(trnpath): + os.makedirs(trnpath) # apply set_capclass before writing any hwy network - applied_SHA1 = networks['hwy'].applyProject(parentdir=TEMP_SUBDIR, networkdir=SET_CAPCLASS, - gitdir=os.path.join(TEMP_SUBDIR, SET_CAPCLASS), **kwargs) - - networks['hwy'].write(path=hwypath,name=HWY_NET_NAME,suppressQuery=True, - suppressValidation=True) # MTC doesn't have turn penalties - - networks['trn'].write(path=trnpath, - name="transitLines", - writeEmptyFiles = False, - suppressQuery = True if BUILD_MODE=="test" else False, - suppressValidation = False, - cubeNetFileForValidation = os.path.join(os.path.abspath(hwypath), HWY_NET_NAME)) - + applied_SHA1 = networks["hwy"].applyProject( + parentdir=TEMP_SUBDIR, + networkdir=SET_CAPCLASS, + gitdir=os.path.join(TEMP_SUBDIR, SET_CAPCLASS), + **kwargs + ) + + networks["hwy"].write( + path=hwypath, + name=HWY_NET_NAME, + suppressQuery=True, + suppressValidation=True, + ) # MTC doesn't have turn penalties + + networks["trn"].write( + path=trnpath, + name="transitLines", + writeEmptyFiles=False, + suppressQuery=True if BUILD_MODE == "test" else False, + suppressValidation=False, + cubeNetFileForValidation=os.path.join( + os.path.abspath(hwypath), HWY_NET_NAME + ), + ) # Write the transit capacity configuration - Wrangler.TransitNetwork.capacity.writeTransitVehicleToCapacity(directory = trnpath) - Wrangler.TransitNetwork.capacity.writeTransitLineToVehicle(directory = trnpath) - Wrangler.TransitNetwork.capacity.writeTransitPrefixToVehicle(directory = trnpath) + Wrangler.TransitNetwork.capacity.writeTransitVehicleToCapacity( + directory=trnpath + ) + Wrangler.TransitNetwork.capacity.writeTransitLineToVehicle( + directory=trnpath + ) + Wrangler.TransitNetwork.capacity.writeTransitPrefixToVehicle( + directory=trnpath + ) # build the Baseline, with Sea Level Rise effects - if NET_VARIANT=="Baseline" and YEAR>=2035: + if NET_VARIANT == "Baseline" and YEAR >= 2035: # Sea Level Rise effects # no inundation prior to 2035 # 1 foot between 2035 and 2045 # 2 foot in 2050 if YEAR >= 2035 and YEAR < 2050: - BP_SLR_PROJECT = {'name':"BP_Sea_Level_Rise_Inundation", 'kwargs':{'MODELYEAR':'2035'}} + BP_SLR_PROJECT = { + "name": "BP_Sea_Level_Rise_Inundation", + "kwargs": {"MODELYEAR": "2035"}, + } if YEAR == 2050: - BP_SLR_PROJECT = {'name':"BP_Sea_Level_Rise_Inundation", 'kwargs':{'MODELYEAR':'2050'}} + BP_SLR_PROJECT = { + "name": "BP_Sea_Level_Rise_Inundation", + "kwargs": {"MODELYEAR": "2050"}, + } # it would be nice if this were more automatic... - networks['hwy'].saveNetworkFiles(suffix="_pre_SLR", to_suffix=True) + networks["hwy"].saveNetworkFiles(suffix="_pre_SLR", to_suffix=True) networks_bp_baseline = {} - networks_bp_baseline['hwy'] = copy.deepcopy(networks['hwy']) - networks_bp_baseline['trn'] = copy.deepcopy(networks['trn']) + networks_bp_baseline["hwy"] = copy.deepcopy(networks["hwy"]) + networks_bp_baseline["trn"] = copy.deepcopy(networks["trn"]) for netmode in NET_MODES: - (project_name, projType, tag, kwargs) = getProjectAttributes(BP_SLR_PROJECT) + (project_name, projType, tag, kwargs) = getProjectAttributes( + BP_SLR_PROJECT + ) # Wrangler.WranglerLogger.debug("BP SLR Project {} has project_name=[{}] projType=[{}] tag=[{}] kwargs=[{}]".format(BP_SLR_PROJECT, # project_name, projType, tag, kwargs)) applied_SHA1 = None - copyloned_SHA1 = networks_bp_baseline[netmode].cloneProject(networkdir=project_name, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR, **kwargs) - (parentdir, networkdir, gitdir, projectsubdir) = networks_bp_baseline[netmode].getClonedProjectArgs(project_name, None, projType, TEMP_SUBDIR) - applied_SHA1 = networks_bp_baseline[netmode].applyProject(parentdir, networkdir, gitdir, projectsubdir, **kwargs) - - hwypath=os.path.join("..", "BlueprintNetworks", "net_{}_{}".format(YEAR, NET_VARIANT), HWY_SUBDIR) - if not os.path.exists(hwypath): os.makedirs(hwypath) - trnpath = os.path.join("..", "BlueprintNetworks", "net_{}_{}".format(YEAR, NET_VARIANT), TRN_SUBDIR) - if not os.path.exists(trnpath): os.makedirs(trnpath) - - applied_SHA1 = networks_bp_baseline['hwy'].applyProject(parentdir=TEMP_SUBDIR, networkdir=SET_CAPCLASS, - gitdir=os.path.join(TEMP_SUBDIR, SET_CAPCLASS)) - - networks_bp_baseline['hwy'].write(path=hwypath,name=HWY_NET_NAME,suppressQuery=True, - suppressValidation=True) # MTC doesn't have turn penalties - - networks_bp_baseline['trn'].write(path=trnpath, - name="transitLines", - writeEmptyFiles = False, - suppressQuery = True if BUILD_MODE=="test" else False, - suppressValidation = False, - cubeNetFileForValidation = os.path.join(os.path.abspath(hwypath), HWY_NET_NAME)) - + copyloned_SHA1 = networks_bp_baseline[netmode].cloneProject( + networkdir=project_name, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + **kwargs + ) + (parentdir, networkdir, gitdir, projectsubdir) = networks_bp_baseline[ + netmode + ].getClonedProjectArgs(project_name, None, projType, TEMP_SUBDIR) + applied_SHA1 = networks_bp_baseline[netmode].applyProject( + parentdir, networkdir, gitdir, projectsubdir, **kwargs + ) + + hwypath = os.path.join( + "..", + "BlueprintNetworks", + "net_{}_{}".format(YEAR, NET_VARIANT), + HWY_SUBDIR, + ) + if not os.path.exists(hwypath): + os.makedirs(hwypath) + trnpath = os.path.join( + "..", + "BlueprintNetworks", + "net_{}_{}".format(YEAR, NET_VARIANT), + TRN_SUBDIR, + ) + if not os.path.exists(trnpath): + os.makedirs(trnpath) + + applied_SHA1 = networks_bp_baseline["hwy"].applyProject( + parentdir=TEMP_SUBDIR, + networkdir=SET_CAPCLASS, + gitdir=os.path.join(TEMP_SUBDIR, SET_CAPCLASS), + ) + + networks_bp_baseline["hwy"].write( + path=hwypath, + name=HWY_NET_NAME, + suppressQuery=True, + suppressValidation=True, + ) # MTC doesn't have turn penalties + + networks_bp_baseline["trn"].write( + path=trnpath, + name="transitLines", + writeEmptyFiles=False, + suppressQuery=True if BUILD_MODE == "test" else False, + suppressValidation=False, + cubeNetFileForValidation=os.path.join( + os.path.abspath(hwypath), HWY_NET_NAME + ), + ) # Write the transit capacity configuration - Wrangler.TransitNetwork.capacity.writeTransitVehicleToCapacity(directory = trnpath) - Wrangler.TransitNetwork.capacity.writeTransitLineToVehicle(directory = trnpath) - Wrangler.TransitNetwork.capacity.writeTransitPrefixToVehicle(directory = trnpath) + Wrangler.TransitNetwork.capacity.writeTransitVehicleToCapacity( + directory=trnpath + ) + Wrangler.TransitNetwork.capacity.writeTransitLineToVehicle( + directory=trnpath + ) + Wrangler.TransitNetwork.capacity.writeTransitPrefixToVehicle( + directory=trnpath + ) # revert back to the plus rowadway network without BP_Sea_Level_Rise_Inundation - networks['hwy'].saveNetworkFiles(suffix="_pre_SLR", to_suffix=False) - + networks["hwy"].saveNetworkFiles(suffix="_pre_SLR", to_suffix=False) - Wrangler.WranglerLogger.debug("Successfully completed running %s" % os.path.abspath(__file__)) + Wrangler.WranglerLogger.debug( + "Successfully completed running %s" % os.path.abspath(__file__) + ) diff --git a/scripts/build_network_mtc_futures.py b/scripts/build_network_mtc_futures.py index 52758a8..8c60c2c 100644 --- a/scripts/build_network_mtc_futures.py +++ b/scripts/build_network_mtc_futures.py @@ -1,4 +1,4 @@ -import argparse,collections,copy,datetime,os,pandas,shutil,sys,time +import argparse, collections, copy, datetime, os, pandas, shutil, sys, time import Wrangler # Based on NetworkWrangler\scripts\build_network.py @@ -38,7 +38,7 @@ # OPTIONAL. If PIVOT_DIR is specified, MANDATORY. Specifies year for PIVOT_DIR. PIVOT_YEAR = 2015 -# MANDATORY. Set this to the directory in which to write your outputs. +# MANDATORY. Set this to the directory in which to write your outputs. # "hwy" and "trn" subdirectories will be created here. OUT_DIR = None @@ -53,10 +53,10 @@ # OPTIONAL. The default route network project directory is Y:\networks. If # projects are stored in another directory, then use this variable to specify it. # For example: Y:\networks\projects -NETWORK_BASE_DIR = None +NETWORK_BASE_DIR = None NETWORK_PROJECT_SUBDIR = None -NETWORK_SEED_SUBDIR = None -NETWORK_PLAN_SUBDIR = None +NETWORK_SEED_SUBDIR = None +NETWORK_PLAN_SUBDIR = None # OPTIONAL. A list of project names which have been previously applied in the # PIVOT_DIR network that projects in this project might rely on. For example @@ -68,8 +68,8 @@ # the TAG. This is meant for developing a network project. TEST_PROJECTS = None -TRN_MODES = ['trn'] -NET_MODES = ['hwy'] + TRN_MODES +TRN_MODES = ["trn"] +NET_MODES = ["hwy"] + TRN_MODES THIS_FILE = os.path.realpath(__file__) ############################################################################### @@ -80,31 +80,34 @@ # # ############################################################################### def getProjectNameAndDir(project): - if type(project) == type({'this is':'a dict'}): - name = project['name'] + if type(project) == type({"this is": "a dict"}): + name = project["name"] else: name = project - (path,name) = os.path.split(name) - return (path,name) + (path, name) = os.path.split(name) + return (path, name) + def getNetworkListIndex(project, networks): for proj in networks: - (path,name) = getProjectNameAndDir(proj) - if project == name or project == os.path.join(path,name): + (path, name) = getProjectNameAndDir(proj) + if project == name or project == os.path.join(path, name): return networks.index(proj) return None + def getProjectMatchLevel(left, right): - (left_path,left_name) = getProjectNameAndDir(left) - (right_path,right_name) = getProjectNameAndDir(right) + (left_path, left_name) = getProjectNameAndDir(left) + (right_path, right_name) = getProjectNameAndDir(right) match = 0 - if os.path.join(left_path,left_name) == os.path.join(right_path,right_name): + if os.path.join(left_path, left_name) == os.path.join(right_path, right_name): match = 2 elif left_name == right_name: match = 1 - #Wrangler.WranglerLogger.debug("Match level %d for %s and %s" % (match, os.path.join(left_path,left_name), os.path.join(right_path,right_name))) + # Wrangler.WranglerLogger.debug("Match level %d for %s and %s" % (match, os.path.join(left_path,left_name), os.path.join(right_path,right_name))) return match + def getProjectYear(PROJECTS, my_proj, netmode): """ PROJECTS is an OrderedDict, year -> netmode -> [ project list ] @@ -113,11 +116,14 @@ def getProjectYear(PROJECTS, my_proj, netmode): """ for year in PROJECTS.keys(): if my_proj in PROJECTS[year][netmode]: - return "{}.{}.{:0>2d}".format(year,netmode,(PROJECTS[year][netmode].index(my_proj)+1)) + return "{}.{}.{:0>2d}".format( + year, netmode, (PROJECTS[year][netmode].index(my_proj) + 1) + ) return -1 -def checkRequirements(REQUIREMENTS, PROJECTS, req_type='prereq'): - if req_type not in ('prereq','coreq','conflict'): + +def checkRequirements(REQUIREMENTS, PROJECTS, req_type="prereq"): + if req_type not in ("prereq", "coreq", "conflict"): return (None, None) # Wrangler.WranglerLogger.debug("checkRequirements called with requirements=[{}] projects=[{}] req_typ={}".format(REQUIREMENTS, PROJECTS, req_type)) @@ -129,26 +135,38 @@ def checkRequirements(REQUIREMENTS, PROJECTS, req_type='prereq'): for project in REQUIREMENTS[netmode].keys(): project_year = getProjectYear(PROJECTS, project, netmode) if project_year == -1: - Wrangler.WranglerLogger.warn('Cannot find the %s project %s to check its requirements'.format(netmode, project)) + Wrangler.WranglerLogger.warn( + "Cannot find the %s project %s to check its requirements".format( + netmode, project + ) + ) continue # raise? - Wrangler.WranglerLogger.info('Checking {} project {} ({}) for {}'.format(netmode, project, project_year, req_type)) + Wrangler.WranglerLogger.info( + "Checking {} project {} ({}) for {}".format( + netmode, project, project_year, req_type + ) + ) for req_netmode in REQUIREMENTS[netmode][project].keys(): - req_proj_list = REQUIREMENTS[netmode][project][req_netmode] + req_proj_list = REQUIREMENTS[netmode][project][req_netmode] req_proj_years = {} for req_proj in req_proj_list: req_project_year = getProjectYear(PROJECTS, req_proj, req_netmode) # prereq - if req_type=="prereq": + if req_type == "prereq": if req_project_year < 0: is_ok = False # required project must be found Wrangler.WranglerLogger.warn("required project not found") if req_project_year > project_year: is_ok = False # and implemented before or at the same time as the project - Wrangler.WranglerLogger.warn("required project year {} > project year {}".format(req_project_year, project_year)) + Wrangler.WranglerLogger.warn( + "required project year {} > project year {}".format( + req_project_year, project_year + ) + ) # save into proj_years req_proj_years[req_proj] = req_project_year @@ -158,54 +176,72 @@ def checkRequirements(REQUIREMENTS, PROJECTS, req_type='prereq'): return (REQUIREMENTS, is_ok) -def writeRequirements(REQUIREMENTS, PROJECTS, req_type='prereq'): - if req_type=='prereq': - print_req = 'Pre-requisite' - elif req_type=='coreq': - print_req = 'Co-requisite' - elif req_type=='conflict': - print_req = 'Conflict' + +def writeRequirements(REQUIREMENTS, PROJECTS, req_type="prereq"): + if req_type == "prereq": + print_req = "Pre-requisite" + elif req_type == "coreq": + print_req = "Co-requisite" + elif req_type == "conflict": + print_req = "Conflict" else: return None Wrangler.WranglerLogger.info("Requirement verification - {}".format(print_req)) - Wrangler.WranglerLogger.info(" Year {:50} {:50} Year".format("Project",print_req+" " + "Project")) + Wrangler.WranglerLogger.info( + " Year {:50} {:50} Year".format( + "Project", print_req + " " + "Project" + ) + ) # REQUIREMENTS: netmode -> project -> netmode -> req_proj -> req_proj_year for netmode in REQUIREMENTS.keys(): for project in REQUIREMENTS[netmode].keys(): project_year = getProjectYear(PROJECTS, project, netmode) for req_netmode in REQUIREMENTS[netmode][project].keys(): for req_project in REQUIREMENTS[netmode][project][req_netmode].keys(): - Wrangler.WranglerLogger.info("{} {} {:50} {} {:50} {}".format(netmode, project_year, project, - req_netmode, req_project, REQUIREMENTS[netmode][project][req_netmode][req_project])) + Wrangler.WranglerLogger.info( + "{} {} {:50} {} {:50} {}".format( + netmode, + project_year, + project, + req_netmode, + req_project, + REQUIREMENTS[netmode][project][req_netmode][req_project], + ) + ) + def getProjectAttributes(project): # Start with TAG if not build mode, no kwargs - project_type = 'project' - tag = None - kwargs = {} + project_type = "project" + tag = None + kwargs = {} # Use project name, tags, kwargs from dictionary - if type(project)==type({'this is':'a dictionary'}): - project_name = project['name'] - if 'tag' in project: tag = project['tag'] - if 'type' in project: project_type = project['type'] - if 'kwargs' in project: kwargs = project['kwargs'] + if type(project) == type({"this is": "a dictionary"}): + project_name = project["name"] + if "tag" in project: + tag = project["tag"] + if "type" in project: + project_type = project["type"] + if "kwargs" in project: + kwargs = project["kwargs"] # Use Project name directly - elif type(project)==type("string"): + elif type(project) == type("string"): project_name = project # Other structures not recognized else: - Wrangler.WranglerLogger.fatal("Don't understand project %s" % str(project)) + Wrangler.WranglerLogger.fatal("Don't understand project %s" % str(project)) return (project_name, project_type, tag, kwargs) + def preCheckRequirementsForAllProjects(networks): - PRE_REQS = {'hwy':{},'trn':{}} - CO_REQS = {'hwy':{},'trn':{}} - CONFLICTS = {'hwy':{},'trn':{}} + PRE_REQS = {"hwy": {}, "trn": {}} + CO_REQS = {"hwy": {}, "trn": {}} + CONFLICTS = {"hwy": {}, "trn": {}} # Network Loop #1: check out all the projects, check if they're stale, check if they're the head repository. Build completed # project list so we can check pre-reqs, etc, in loop #2. @@ -216,134 +252,207 @@ def preCheckRequirementsForAllProjects(networks): for model_year in NETWORK_PROJECTS.keys(): for project in NETWORK_PROJECTS[model_year][netmode]: (project_name, projType, tag, kwargs) = getProjectAttributes(project) - if tag == None: tag = TAG + if tag == None: + tag = TAG # test mode - don't use TAG for TEST_PROJECTS - if BUILD_MODE=="test" and type(TEST_PROJECTS)==type(['List']): + if BUILD_MODE == "test" and type(TEST_PROJECTS) == type(["List"]): if project_name in TEST_PROJECTS: - Wrangler.WranglerLogger.debug("Skipping tag [%s] because test mode and [%s] is in TEST_PROJECTS" % - (TAG, project_name)) + Wrangler.WranglerLogger.debug( + "Skipping tag [%s] because test mode and [%s] is in TEST_PROJECTS" + % (TAG, project_name) + ) tag = None Wrangler.WranglerLogger.debug("Project name = %s" % project_name) cloned_SHA1 = None # if project = "dir1/dir2" assume dir1 is git, dir2 is the projectsubdir - (head,tail) = os.path.split(project_name) + (head, tail) = os.path.split(project_name) if head: - cloned_SHA1 = networks[netmode].cloneProject(networkdir=head, projectsubdir=tail, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR, **kwargs) - (prereqs, coreqs, conflicts) = networks[netmode].getReqs(networkdir=head, projectsubdir=tail, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR) + cloned_SHA1 = networks[netmode].cloneProject( + networkdir=head, + projectsubdir=tail, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + **kwargs + ) + (prereqs, coreqs, conflicts) = networks[netmode].getReqs( + networkdir=head, + projectsubdir=tail, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + ) else: - cloned_SHA1 = networks[netmode].cloneProject(networkdir=project_name, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR, **kwargs) - (prereqs, coreqs, conflicts) = networks[netmode].getReqs(networkdir=project_name, projectsubdir=tail, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR) + cloned_SHA1 = networks[netmode].cloneProject( + networkdir=project_name, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + **kwargs + ) + (prereqs, coreqs, conflicts) = networks[netmode].getReqs( + networkdir=project_name, + projectsubdir=tail, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + ) # find out if the applied project is behind HEAD # get the HEAD SHA1 cmd = r"git show-ref --head master" - if projType=='project': + if projType == "project": join_subdir = Wrangler.Network.NETWORK_PROJECT_SUBDIR - if projType=='seed': + if projType == "seed": join_subdir = Wrangler.Network.NETWORK_SEED_SUBDIR - cmd_dir = os.path.join(Wrangler.Network.NETWORK_BASE_DIR, join_subdir, project_name) - (retcode, retStdout, retStderr) = networks[netmode]._runAndLog(cmd, run_dir = cmd_dir) + cmd_dir = os.path.join( + Wrangler.Network.NETWORK_BASE_DIR, join_subdir, project_name + ) + (retcode, retStdout, retStderr) = networks[netmode]._runAndLog( + cmd, run_dir=cmd_dir + ) # Wrangler.WranglerLogger.debug("results of [%s]: %s %s %s" % (cmd, str(retcode), str(retStdout), str(retStderr))) - if retcode != 0: # this shouldn't happen -- wouldn't cloneAndApply have failed? - Wrangler.WranglerLogger.fatal("Couldn't run cmd [%s] in [%s]: stdout=[%s] stderr=[%s]" % \ - (cmd, cmd_dir, str(retStdout), str(retStderr))) + if ( + retcode != 0 + ): # this shouldn't happen -- wouldn't cloneAndApply have failed? + Wrangler.WranglerLogger.fatal( + "Couldn't run cmd [%s] in [%s]: stdout=[%s] stderr=[%s]" + % (cmd, cmd_dir, str(retStdout), str(retStderr)) + ) sys.exit(2) head_SHA1 = retStdout[0].split()[0] - + # if they're different, log more information and get approval (if not in test mode) if cloned_SHA1 != head_SHA1: - Wrangler.WranglerLogger.warn("Using non-head version of project of %s" % project_name) - Wrangler.WranglerLogger.warn(" Applying version [%s], Head is [%s]" % (cloned_SHA1, head_SHA1)) - + Wrangler.WranglerLogger.warn( + "Using non-head version of project of %s" % project_name + ) + Wrangler.WranglerLogger.warn( + " Applying version [%s], Head is [%s]" + % (cloned_SHA1, head_SHA1) + ) + cmd = "git log %s..%s" % (cloned_SHA1, head_SHA1) - (retcode, retStdout, retStderr) = networks[netmode]._runAndLog(cmd, run_dir = cmd_dir) - Wrangler.WranglerLogger.warn(" The following commits are not included:") + (retcode, retStdout, retStderr) = networks[netmode]._runAndLog( + cmd, run_dir=cmd_dir + ) + Wrangler.WranglerLogger.warn( + " The following commits are not included:" + ) for line in retStdout: Wrangler.WranglerLogger.warn(" %s" % line) - + # test mode => warn is sufficient # non-test mode => get explicit approval - if BUILD_MODE !="test": + if BUILD_MODE != "test": Wrangler.WranglerLogger.warn(" Is this ok? (y/n) ") response = raw_input("") Wrangler.WranglerLogger.debug(" response = [%s]" % response) if response.strip().lower()[0] != "y": sys.exit(2) - + # find out if the project is stale else: cmd = 'git show -s --format="%%ct" %s' % cloned_SHA1 - (retcode, retStdout, retStderr) = networks[netmode]._runAndLog(cmd, run_dir = cmd_dir) - applied_commit_date = datetime.datetime.fromtimestamp(int(retStdout[0])) + (retcode, retStdout, retStderr) = networks[netmode]._runAndLog( + cmd, run_dir=cmd_dir + ) + applied_commit_date = datetime.datetime.fromtimestamp( + int(retStdout[0]) + ) applied_commit_age = datetime.datetime.now() - applied_commit_date - + # if older than one year, holler STALE_YEARS = 2 - if applied_commit_age > datetime.timedelta(days=365*STALE_YEARS): - Wrangler.WranglerLogger.warn(" This project was last updated %.1f years ago (over %d), on %s" % \ - (applied_commit_age.days/365.0, - STALE_YEARS, applied_commit_date.strftime("%x"))) - if BUILD_MODE !="test": + if applied_commit_age > datetime.timedelta(days=365 * STALE_YEARS): + Wrangler.WranglerLogger.warn( + " This project was last updated %.1f years ago (over %d), on %s" + % ( + applied_commit_age.days / 365.0, + STALE_YEARS, + applied_commit_date.strftime("%x"), + ) + ) + if BUILD_MODE != "test": Wrangler.WranglerLogger.warn(" Is this ok? (y/n) ") response = raw_input("") - Wrangler.WranglerLogger.debug(" response = [%s]" % response) + Wrangler.WranglerLogger.debug( + " response = [%s]" % response + ) if response.strip().lower() not in ["y", "yes"]: sys.exit(2) - + clonedcount += 1 - + # format: netmode -> project -> { netmode: [requirements] } - if len(prereqs ) > 0: PRE_REQS[ netmode][project_name] = prereqs - if len(coreqs ) > 0: CO_REQS[ netmode][project_name] = coreqs - if len(conflicts) > 0: CONFLICTS[netmode][project_name] = conflicts + if len(prereqs) > 0: + PRE_REQS[netmode][project_name] = prereqs + if len(coreqs) > 0: + CO_REQS[netmode][project_name] = coreqs + if len(conflicts) > 0: + CONFLICTS[netmode][project_name] = conflicts # Check requirements - prFile = 'prereqs.csv' - crFile = 'coreqs.csv' - cfFile = 'conflicts.csv' + prFile = "prereqs.csv" + crFile = "coreqs.csv" + cfFile = "conflicts.csv" # Check prereqs - (PRE_REQS, allPrereqsFound) = checkRequirements(PRE_REQS, NETWORK_PROJECTS, req_type='prereq') - if len(PRE_REQS['trn'])>0 or len(PRE_REQS['hwy'])>0: - writeRequirements(PRE_REQS, NETWORK_PROJECTS, req_type='prereq') + (PRE_REQS, allPrereqsFound) = checkRequirements( + PRE_REQS, NETWORK_PROJECTS, req_type="prereq" + ) + if len(PRE_REQS["trn"]) > 0 or len(PRE_REQS["hwy"]) > 0: + writeRequirements(PRE_REQS, NETWORK_PROJECTS, req_type="prereq") if allPrereqsFound: - Wrangler.WranglerLogger.debug('All PRE-REQUISITES were found. Are the PRE-REQUISITES matches correct? (y/n)') + Wrangler.WranglerLogger.debug( + "All PRE-REQUISITES were found. Are the PRE-REQUISITES matches correct? (y/n)" + ) else: - Wrangler.WranglerLogger.debug('!!!WARNING!!! Some PRE-REQUISITES were not found or ordered correctly. Continue anyway? (y/n)') + Wrangler.WranglerLogger.debug( + "!!!WARNING!!! Some PRE-REQUISITES were not found or ordered correctly. Continue anyway? (y/n)" + ) response = raw_input("") Wrangler.WranglerLogger.debug(" response = [%s]" % response) if response.strip().lower() not in ["y", "yes"]: sys.exit(2) # Check coreqs - (CO_REQS, allCoreqsFound) = checkRequirements(CO_REQS, NETWORK_PROJECTS, req_type='coreq') - if len(CO_REQS['trn'])>0 or len(CO_REQS['hwy'])>0: - writeRequirements(CO_REQS, NETWORK_PROJECTS, req_type='coreq') + (CO_REQS, allCoreqsFound) = checkRequirements( + CO_REQS, NETWORK_PROJECTS, req_type="coreq" + ) + if len(CO_REQS["trn"]) > 0 or len(CO_REQS["hwy"]) > 0: + writeRequirements(CO_REQS, NETWORK_PROJECTS, req_type="coreq") if allCoreqsFound: - Wrangler.WranglerLogger.debug('All CO-REQUISITES were found. Are the CO-REQUISITE matches correct? (y/n)') + Wrangler.WranglerLogger.debug( + "All CO-REQUISITES were found. Are the CO-REQUISITE matches correct? (y/n)" + ) else: - Wrangler.WranglerLogger.debug('!!!WARNING!!! Some CO-REQUISITES were not found. Continue anyway? (y/n)') + Wrangler.WranglerLogger.debug( + "!!!WARNING!!! Some CO-REQUISITES were not found. Continue anyway? (y/n)" + ) response = raw_input("") Wrangler.WranglerLogger.debug(" response = [%s]" % response) if response.strip().lower() not in ["y", "yes"]: sys.exit(2) # Check conflicts - (CONFLICTS, anyConflictFound) = checkRequirements(CONFLICTS, NETWORK_PROJECTS, req_type='conflict') - if len(CONFLICTS['trn'])>0 or len(CONFLICTS['hwy'])>0: - writeRequirements(CONFLICTS, NETWORK_PROJECTS, 'conflict') + (CONFLICTS, anyConflictFound) = checkRequirements( + CONFLICTS, NETWORK_PROJECTS, req_type="conflict" + ) + if len(CONFLICTS["trn"]) > 0 or len(CONFLICTS["hwy"]) > 0: + writeRequirements(CONFLICTS, NETWORK_PROJECTS, "conflict") if anyConflictFound: - Wrangler.WranglerLogger.debug('!!!WARNING!!! Conflicting projects were found. Continue anyway? (y/n)') + Wrangler.WranglerLogger.debug( + "!!!WARNING!!! Conflicting projects were found. Continue anyway? (y/n)" + ) else: - Wrangler.WranglerLogger.debug('No conflicting projects were found. Enter \'y\' to continue.') + Wrangler.WranglerLogger.debug( + "No conflicting projects were found. Enter 'y' to continue." + ) response = raw_input("") Wrangler.WranglerLogger.debug(" response = [%s]" % response) if response.strip().lower() not in ["y", "yes"]: @@ -351,113 +460,155 @@ def preCheckRequirementsForAllProjects(networks): # Wrangler.WranglerLogger.debug("NETWORK_PROJECTS=%s NET_MODES=%s" % (str(NETWORK_PROJECTS), str(NET_MODES))) + ############################################################################### -if __name__ == '__main__': - parser = argparse.ArgumentParser(description=USAGE, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument("--configword", help="optional word for network specification script") - parser.add_argument("--model_type", choices=[Wrangler.Network.MODEL_TYPE_TM1, Wrangler.Network.MODEL_TYPE_TM2], - default=Wrangler.Network.MODEL_TYPE_TM1) - parser.add_argument("--analysis", choices=["Round1","Round2","PPA","PPA_NoSLR"], help="Specify which set of analysis are relevant for these networks.", default="Round1") - parser.add_argument("net_spec", metavar="network_specification.py", help="Script which defines required variables indicating how to build the network") - parser.add_argument("future", choices=["CleanAndGreen", "RisingTides", "BackToTheFuture"], help="Specify which Future Scenario for which to create networks") +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=USAGE, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + "--configword", help="optional word for network specification script" + ) + parser.add_argument( + "--model_type", + choices=[Wrangler.Network.MODEL_TYPE_TM1, Wrangler.Network.MODEL_TYPE_TM2], + default=Wrangler.Network.MODEL_TYPE_TM1, + ) + parser.add_argument( + "--analysis", + choices=["Round1", "Round2", "PPA", "PPA_NoSLR"], + help="Specify which set of analysis are relevant for these networks.", + default="Round1", + ) + parser.add_argument( + "net_spec", + metavar="network_specification.py", + help="Script which defines required variables indicating how to build the network", + ) + parser.add_argument( + "future", + choices=["CleanAndGreen", "RisingTides", "BackToTheFuture"], + help="Specify which Future Scenario for which to create networks", + ) args = parser.parse_args() - NOW = time.strftime("%Y%b%d.%H%M%S") - BUILD_MODE = None # regular + NOW = time.strftime("%Y%b%d.%H%M%S") + BUILD_MODE = None # regular if args.model_type == Wrangler.Network.MODEL_TYPE_TM1: - PIVOT_DIR = r"M:\\Application\\Model One\\Networks\\TM1_2015_Base_Network" + PIVOT_DIR = r"M:\\Application\\Model One\\Networks\\TM1_2015_Base_Network" TRANSIT_CAPACITY_DIR = os.path.join(PIVOT_DIR, "trn") NETWORK_BASE_DIR = r"M:\\Application\\Model One\\NetworkProjects" - TRN_SUBDIR = "trn" - TRN_NET_NAME = "Transit_Lines" - HWY_SUBDIR = "hwy" - HWY_NET_NAME = "freeflow.net" + TRN_SUBDIR = "trn" + TRN_NET_NAME = "Transit_Lines" + HWY_SUBDIR = "hwy" + HWY_NET_NAME = "freeflow.net" elif args.model_type == Wrangler.Network.MODEL_TYPE_TM2: - PIVOT_DIR = os.path.join(os.environ["USERPROFILE"], "Box","Modeling and Surveys","Development","Travel Model Two Development","Model Inputs","2015_revised_mazs") + PIVOT_DIR = os.path.join( + os.environ["USERPROFILE"], + "Box", + "Modeling and Surveys", + "Development", + "Travel Model Two Development", + "Model Inputs", + "2015_revised_mazs", + ) TRANSIT_CAPACITY_DIR = None NETWORK_BASE_DIR = r"M:\\Application\\Model Two\\NetworkProjects" - TRN_SUBDIR = "trn" - TRN_NET_NAME = "transitLines" - HWY_SUBDIR = "hwy" - HWY_NET_NAME = "mtc_final_network_base.net" + TRN_SUBDIR = "trn" + TRN_NET_NAME = "transitLines" + HWY_SUBDIR = "hwy" + HWY_NET_NAME = "mtc_final_network_base.net" if args.analysis == "Round1": PROJECT = "FU1" elif args.analysis == "Round2": - PROJECT = "FU2" - elif args.analysis in ["PPA","PPA_NoSLR"]: + PROJECT = "FU2" + elif args.analysis in ["PPA", "PPA_NoSLR"]: PROJECT = args.analysis # Read the configuration NETWORK_CONFIG = args.net_spec - SCENARIO = args.future + SCENARIO = args.future exec(open(NETWORK_CONFIG).read()) # Verify mandatory fields are set - if PROJECT==None: + if PROJECT == None: print("PROJECT not set in %s" % NETWORK_CONFIG) sys.exit(2) - if TAG==None: + if TAG == None: print("TAG not set in %s" % NETWORK_CONFIG) sys.exit(2) - if OUT_DIR==None: + if OUT_DIR == None: print("OUT_DIR not set in %s" % NETWORK_CONFIG) sys.exit(2) - if NETWORK_PROJECTS==None: + if NETWORK_PROJECTS == None: print("NETWORK_PROJECTS not set in %s" % NETWORK_CONFIG) sys.exit(2) - LOG_FILENAME = "build%snetwork_%s_%s_%s.info.LOG" % ("TEST" if BUILD_MODE=="test" else "", PROJECT, SCENARIO, NOW) + LOG_FILENAME = "build%snetwork_%s_%s_%s.info.LOG" % ( + "TEST" if BUILD_MODE == "test" else "", + PROJECT, + SCENARIO, + NOW, + ) Wrangler.setupLogging(LOG_FILENAME, LOG_FILENAME.replace("info", "debug")) if TRANSIT_CAPACITY_DIR: - Wrangler.TransitNetwork.capacity = Wrangler.TransitCapacity(directory=TRANSIT_CAPACITY_DIR) + Wrangler.TransitNetwork.capacity = Wrangler.TransitCapacity( + directory=TRANSIT_CAPACITY_DIR + ) # Create a scratch directory to check out project repos into SCRATCH_SUBDIR = "scratch" - TEMP_SUBDIR = "Wrangler_tmp_" + NOW - if not os.path.exists(SCRATCH_SUBDIR): os.mkdir(SCRATCH_SUBDIR) + TEMP_SUBDIR = "Wrangler_tmp_" + NOW + if not os.path.exists(SCRATCH_SUBDIR): + os.mkdir(SCRATCH_SUBDIR) os.chdir(SCRATCH_SUBDIR) - os.environ["CHAMP_node_names"] = os.path.join(PIVOT_DIR,"Node Description.xls") + os.environ["CHAMP_node_names"] = os.path.join(PIVOT_DIR, "Node Description.xls") networks = { - 'hwy' :Wrangler.HighwayNetwork(modelType=args.model_type, modelVersion=1.0, - basenetworkpath=os.path.join(PIVOT_DIR,"hwy"), - networkBaseDir=NETWORK_BASE_DIR, - networkProjectSubdir=NETWORK_PROJECT_SUBDIR, - networkSeedSubdir=NETWORK_SEED_SUBDIR, - networkPlanSubdir=NETWORK_PLAN_SUBDIR, - isTiered=True if PIVOT_DIR else False, - tag=TAG, - tempdir=TEMP_SUBDIR, - networkName="hwy", - tierNetworkName=HWY_NET_NAME), - 'trn':Wrangler.TransitNetwork( modelType=args.model_type, modelVersion=1.0, - basenetworkpath=os.path.join(PIVOT_DIR,"trn"), - networkBaseDir=NETWORK_BASE_DIR, - networkProjectSubdir=NETWORK_PROJECT_SUBDIR, - networkSeedSubdir=NETWORK_SEED_SUBDIR, - networkPlanSubdir=NETWORK_PLAN_SUBDIR, - isTiered=True if PIVOT_DIR else False, - networkName=TRN_NET_NAME) + "hwy": Wrangler.HighwayNetwork( + modelType=args.model_type, + modelVersion=1.0, + basenetworkpath=os.path.join(PIVOT_DIR, "hwy"), + networkBaseDir=NETWORK_BASE_DIR, + networkProjectSubdir=NETWORK_PROJECT_SUBDIR, + networkSeedSubdir=NETWORK_SEED_SUBDIR, + networkPlanSubdir=NETWORK_PLAN_SUBDIR, + isTiered=True if PIVOT_DIR else False, + tag=TAG, + tempdir=TEMP_SUBDIR, + networkName="hwy", + tierNetworkName=HWY_NET_NAME, + ), + "trn": Wrangler.TransitNetwork( + modelType=args.model_type, + modelVersion=1.0, + basenetworkpath=os.path.join(PIVOT_DIR, "trn"), + networkBaseDir=NETWORK_BASE_DIR, + networkProjectSubdir=NETWORK_PROJECT_SUBDIR, + networkSeedSubdir=NETWORK_SEED_SUBDIR, + networkPlanSubdir=NETWORK_PLAN_SUBDIR, + isTiered=True if PIVOT_DIR else False, + networkName=TRN_NET_NAME, + ), } # For projects applied in a pivot network (because they won't show up in the current project list) if APPLIED_PROJECTS != None: for proj in APPLIED_PROJECTS: - networks['hwy'].appliedProjects[proj]=TAG - + networks["hwy"].appliedProjects[proj] = TAG # Wrangler.WranglerLogger.debug("NETWORK_PROJECTS=%s NET_MODES=%s" % (str(NETWORK_PROJECTS), str(NET_MODES))) preCheckRequirementsForAllProjects(networks) # create the subdir for SET_CAPCLASS with set_capclass.job as apply.s - SET_CAPCLASS = "set_capclass" + SET_CAPCLASS = "set_capclass" SET_CAPCLASS_DIR = os.path.join(TEMP_SUBDIR, SET_CAPCLASS) os.makedirs(SET_CAPCLASS_DIR) - source_file = os.path.join(os.path.dirname(THIS_FILE), "set_capclass.job") - shutil.copyfile( source_file, os.path.join(SET_CAPCLASS_DIR, "apply.s")) + source_file = os.path.join(os.path.dirname(THIS_FILE), "set_capclass.job") + shutil.copyfile(source_file, os.path.join(SET_CAPCLASS_DIR, "apply.s")) networks_without_earthquake = {} @@ -467,80 +618,128 @@ def preCheckRequirementsForAllProjects(networks): appliedcount = 0 for netmode in NET_MODES: - Wrangler.WranglerLogger.info("Building {} {} networks".format(YEAR, netmode)) + Wrangler.WranglerLogger.info( + "Building {} {} networks".format(YEAR, netmode) + ) # restore version without earthquake if netmode in networks_without_earthquake: Wrangler.WranglerLogger.info("Restoring version without earthquake") networks[netmode] = networks_without_earthquake[netmode] - appliedcount += 1 # increment to trigger writing this out + appliedcount += 1 # increment to trigger writing this out del networks_without_earthquake[netmode] if netmode == "hwy": - shutil.move(os.path.join("FREEFLOW_WITHOUT_EARTHQUAKE.BLD"), - os.path.join("FREEFLOW.BLD")) + shutil.move( + os.path.join("FREEFLOW_WITHOUT_EARTHQUAKE.BLD"), + os.path.join("FREEFLOW.BLD"), + ) for project in projects_for_year[netmode]: (project_name, projType, tag, kwargs) = getProjectAttributes(project) - if tag == None: tag = TAG - - Wrangler.WranglerLogger.info("Applying project [{}] of type [{}] with tag [{}] and kwargs[{}]".format(project_name, projType, tag, kwargs)) - if projType=='plan': + if tag == None: + tag = TAG + + Wrangler.WranglerLogger.info( + "Applying project [{}] of type [{}] with tag [{}] and kwargs[{}]".format( + project_name, projType, tag, kwargs + ) + ) + if projType == "plan": continue applied_SHA1 = None - cloned_SHA1 = networks[netmode].cloneProject(networkdir=project_name, tag=tag, - projtype=projType, tempdir=TEMP_SUBDIR, **kwargs) - (parentdir, networkdir, gitdir, projectsubdir) = networks[netmode].getClonedProjectArgs(project_name, None, projType, TEMP_SUBDIR) - - if ((project_name == "Earthquake") and ((PROJECT == "FU1" and args.future in ["CleanAndGreen","BackToTheFuture"]) or (PROJECT == "FU2"))): + cloned_SHA1 = networks[netmode].cloneProject( + networkdir=project_name, + tag=tag, + projtype=projType, + tempdir=TEMP_SUBDIR, + **kwargs + ) + (parentdir, networkdir, gitdir, projectsubdir) = networks[ + netmode + ].getClonedProjectArgs(project_name, None, projType, TEMP_SUBDIR) + + if (project_name == "Earthquake") and ( + ( + PROJECT == "FU1" + and args.future in ["CleanAndGreen", "BackToTheFuture"] + ) + or (PROJECT == "FU2") + ): # Then this "project" is only temporary, so save aside a deepcopy of the network PRIOR # to the apply to restore after we write it - networks_without_earthquake[netmode] = copy.deepcopy(networks[netmode]) + networks_without_earthquake[netmode] = copy.deepcopy( + networks[netmode] + ) if netmode == "hwy": - shutil.copyfile(os.path.join("FREEFLOW.BLD"), - os.path.join("FREEFLOW_WITHOUT_EARTHQUAKE.BLD")) - - applied_SHA1 = networks[netmode].applyProject(parentdir, networkdir, gitdir, projectsubdir, **kwargs) + shutil.copyfile( + os.path.join("FREEFLOW.BLD"), + os.path.join("FREEFLOW_WITHOUT_EARTHQUAKE.BLD"), + ) + + applied_SHA1 = networks[netmode].applyProject( + parentdir, networkdir, gitdir, projectsubdir, **kwargs + ) appliedcount += 1 # if hwy project has set_capclass override, copy it to set_capclass/apply.s - set_capclass_override = os.path.join(TEMP_SUBDIR, project_name, "set_capclass.job") + set_capclass_override = os.path.join( + TEMP_SUBDIR, project_name, "set_capclass.job" + ) if os.path.exists(set_capclass_override): dest_file = os.path.join(SET_CAPCLASS_DIR, "apply.s") shutil.copyfile(set_capclass_override, dest_file) - Wrangler.WranglerLogger.info("Copied override {} to {}".format(set_capclass_override, dest_file)) - + Wrangler.WranglerLogger.info( + "Copied override {} to {}".format( + set_capclass_override, dest_file + ) + ) # apply set_capclass before writing any hwy network if netmode == "hwy" and appliedcount > 0: - applied_SHA1 = networks[netmode].applyProject(parentdir=TEMP_SUBDIR, networkdir=SET_CAPCLASS, - gitdir=os.path.join(TEMP_SUBDIR, SET_CAPCLASS)) + applied_SHA1 = networks[netmode].applyProject( + parentdir=TEMP_SUBDIR, + networkdir=SET_CAPCLASS, + gitdir=os.path.join(TEMP_SUBDIR, SET_CAPCLASS), + ) if appliedcount == 0: - Wrangler.WranglerLogger.info("No applied projects for this year -- skipping output") + Wrangler.WranglerLogger.info( + "No applied projects for this year -- skipping output" + ) continue # Initialize output subdirectories up a level (not in scratch) - hwypath=os.path.join("..", SCENARIO, OUT_DIR.format(YEAR),HWY_SUBDIR) - if not os.path.exists(hwypath): os.makedirs(hwypath) - trnpath = os.path.join("..", SCENARIO, OUT_DIR.format(YEAR),TRN_SUBDIR) - if not os.path.exists(trnpath): os.makedirs(trnpath) - - networks['hwy'].write(path=hwypath,name=HWY_NET_NAME,suppressQuery=True, - suppressValidation=True) # MTC doesn't have turn penalties - - networks['trn'].write(path=trnpath, - name="transitLines", - writeEmptyFiles = False, - suppressQuery = True if BUILD_MODE=="test" else False, - suppressValidation = False, - cubeNetFileForValidation = os.path.join(os.path.abspath(hwypath), HWY_NET_NAME)) - + hwypath = os.path.join("..", SCENARIO, OUT_DIR.format(YEAR), HWY_SUBDIR) + if not os.path.exists(hwypath): + os.makedirs(hwypath) + trnpath = os.path.join("..", SCENARIO, OUT_DIR.format(YEAR), TRN_SUBDIR) + if not os.path.exists(trnpath): + os.makedirs(trnpath) + + networks["hwy"].write( + path=hwypath, name=HWY_NET_NAME, suppressQuery=True, suppressValidation=True + ) # MTC doesn't have turn penalties + + networks["trn"].write( + path=trnpath, + name="transitLines", + writeEmptyFiles=False, + suppressQuery=True if BUILD_MODE == "test" else False, + suppressValidation=False, + cubeNetFileForValidation=os.path.join( + os.path.abspath(hwypath), HWY_NET_NAME + ), + ) # Write the transit capacity configuration - Wrangler.TransitNetwork.capacity.writeTransitVehicleToCapacity(directory = trnpath) - Wrangler.TransitNetwork.capacity.writeTransitLineToVehicle(directory = trnpath) - Wrangler.TransitNetwork.capacity.writeTransitPrefixToVehicle(directory = trnpath) - - Wrangler.WranglerLogger.debug("Successfully completed running %s" % os.path.abspath(__file__)) + Wrangler.TransitNetwork.capacity.writeTransitVehicleToCapacity( + directory=trnpath + ) + Wrangler.TransitNetwork.capacity.writeTransitLineToVehicle(directory=trnpath) + Wrangler.TransitNetwork.capacity.writeTransitPrefixToVehicle(directory=trnpath) + + Wrangler.WranglerLogger.debug( + "Successfully completed running %s" % os.path.abspath(__file__) + ) diff --git a/scripts/convert_network_mtc_TM1_to_TM15.py b/scripts/convert_network_mtc_TM1_to_TM15.py index 2433707..36a11b7 100644 --- a/scripts/convert_network_mtc_TM1_to_TM15.py +++ b/scripts/convert_network_mtc_TM1_to_TM15.py @@ -1,4 +1,4 @@ -import argparse,collections,datetime,os,pandas,shutil,sys,time +import argparse, collections, datetime, os, pandas, shutil, sys, time import Wrangler USAGE = """ @@ -9,98 +9,126 @@ """ -if __name__ == '__main__': - parser = argparse.ArgumentParser(description=USAGE, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument("INPUT_dir", help="Input directory containing hwy and trn files") - parser.add_argument("OUTPUT_dir", help="Output directory containing hwy and trn files") +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description=USAGE, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument( + "INPUT_dir", help="Input directory containing hwy and trn files" + ) + parser.add_argument( + "OUTPUT_dir", help="Output directory containing hwy and trn files" + ) args = parser.parse_args() - NOW = time.strftime("%Y%b%d.%H%M%S") - THIS_FILE = os.path.realpath(__file__) + NOW = time.strftime("%Y%b%d.%H%M%S") + THIS_FILE = os.path.realpath(__file__) NETWORK_BASE_DIR = r"M:\\Application\\Model One\\NetworkProjects" - TRN_SUBDIR = "trn" - TRN_NET_NAME = "Transit_Lines" - HWY_SUBDIR = "hwy" - HWY_NET_NAME = "freeflow.net" - TRANSIT_CAPACITY_DIR = None # doesn't exist in TM1 + TRN_SUBDIR = "trn" + TRN_NET_NAME = "Transit_Lines" + HWY_SUBDIR = "hwy" + HWY_NET_NAME = "freeflow.net" + TRANSIT_CAPACITY_DIR = None # doesn't exist in TM1 LOG_FILENAME = "convert_network_TM1to15_{}_{}.info.LOG".format(args.OUTPUT_dir, NOW) Wrangler.setupLogging(LOG_FILENAME, LOG_FILENAME.replace("info", "debug")) if TRANSIT_CAPACITY_DIR: - Wrangler.TransitNetwork.capacity = Wrangler.TransitCapacity(directory=TRANSIT_CAPACITY_DIR) + Wrangler.TransitNetwork.capacity = Wrangler.TransitCapacity( + directory=TRANSIT_CAPACITY_DIR + ) # Create a scratch directory to check out project repos into SCRATCH_SUBDIR = "scratch" - TEMP_SUBDIR = "Wrangler_tmp_" + NOW - if not os.path.exists(SCRATCH_SUBDIR): os.mkdir(SCRATCH_SUBDIR) + TEMP_SUBDIR = "Wrangler_tmp_" + NOW + if not os.path.exists(SCRATCH_SUBDIR): + os.mkdir(SCRATCH_SUBDIR) os.chdir(SCRATCH_SUBDIR) - if not os.path.exists(TEMP_SUBDIR): os.mkdir(TEMP_SUBDIR) + if not os.path.exists(TEMP_SUBDIR): + os.mkdir(TEMP_SUBDIR) networks = {} try: - networks['hwy'] = \ - Wrangler.HighwayNetwork(modelType=Wrangler.Network.MODEL_TYPE_TM1, modelVersion=1.0, - basenetworkpath=os.path.join(args.INPUT_dir,"hwy"), - networkBaseDir=NETWORK_BASE_DIR, - networkProjectSubdir=None, - networkSeedSubdir=None, - networkPlanSubdir=None, - isTiered=True, - tag=None, - tempdir=TEMP_SUBDIR, - networkName="hwy", - tierNetworkName=HWY_NET_NAME) - networks['trn'] = \ - Wrangler.TransitNetwork( modelType=Wrangler.Network.MODEL_TYPE_TM1, modelVersion=1.0, - basenetworkpath=os.path.join(args.INPUT_dir,"trn"), - networkBaseDir=NETWORK_BASE_DIR, - networkProjectSubdir=None, - networkSeedSubdir=None, - networkPlanSubdir=None, - isTiered=True, - networkName=TRN_NET_NAME) + networks["hwy"] = Wrangler.HighwayNetwork( + modelType=Wrangler.Network.MODEL_TYPE_TM1, + modelVersion=1.0, + basenetworkpath=os.path.join(args.INPUT_dir, "hwy"), + networkBaseDir=NETWORK_BASE_DIR, + networkProjectSubdir=None, + networkSeedSubdir=None, + networkPlanSubdir=None, + isTiered=True, + tag=None, + tempdir=TEMP_SUBDIR, + networkName="hwy", + tierNetworkName=HWY_NET_NAME, + ) + networks["trn"] = Wrangler.TransitNetwork( + modelType=Wrangler.Network.MODEL_TYPE_TM1, + modelVersion=1.0, + basenetworkpath=os.path.join(args.INPUT_dir, "trn"), + networkBaseDir=NETWORK_BASE_DIR, + networkProjectSubdir=None, + networkSeedSubdir=None, + networkPlanSubdir=None, + isTiered=True, + networkName=TRN_NET_NAME, + ) except Wrangler.NetworkException as e: Wrangler.WranglerLogger.fatal(e) sys.exit() # create the subdir for SET_CAPCLASS with set_capclass.job as apply.s - SET_CAPCLASS = "set_capclass" + SET_CAPCLASS = "set_capclass" SET_CAPCLASS_DIR = os.path.join(TEMP_SUBDIR, SET_CAPCLASS) os.mkdir(SET_CAPCLASS_DIR) - source_file = os.path.join(os.path.dirname(THIS_FILE), "set_capclass.job") - shutil.copyfile( source_file, os.path.join(SET_CAPCLASS_DIR, "apply.s")) + source_file = os.path.join(os.path.dirname(THIS_FILE), "set_capclass.job") + shutil.copyfile(source_file, os.path.join(SET_CAPCLASS_DIR, "apply.s")) # apply set_capclass before writing any hwy network - applied_SHA1 = networks['hwy'].applyProject(parentdir=TEMP_SUBDIR, networkdir=SET_CAPCLASS, - gitdir=os.path.join(TEMP_SUBDIR, SET_CAPCLASS)) + applied_SHA1 = networks["hwy"].applyProject( + parentdir=TEMP_SUBDIR, + networkdir=SET_CAPCLASS, + gitdir=os.path.join(TEMP_SUBDIR, SET_CAPCLASS), + ) # Initialize output subdirectories up a level (not in scratch) - hwypath=os.path.join("..", args.OUTPUT_dir, HWY_SUBDIR) - if not os.path.exists(hwypath): os.makedirs(hwypath) - trnpath = os.path.join("..", args.OUTPUT_dir,TRN_SUBDIR) - if not os.path.exists(trnpath): os.makedirs(trnpath) - - networks['hwy'].write(path=hwypath,name=HWY_NET_NAME,suppressQuery=True, - suppressValidation=True) # MTC TM1 doesn't have turn penalties - - os.environ["CHAMP_node_names"] = r"M:\\Application\\Model One\\Networks\\TM1_2015_Base_Network\\Node Description.xls" - hwy_abs_path = os.path.abspath( os.path.join(hwypath, HWY_NET_NAME) ) + hwypath = os.path.join("..", args.OUTPUT_dir, HWY_SUBDIR) + if not os.path.exists(hwypath): + os.makedirs(hwypath) + trnpath = os.path.join("..", args.OUTPUT_dir, TRN_SUBDIR) + if not os.path.exists(trnpath): + os.makedirs(trnpath) + + networks["hwy"].write( + path=hwypath, name=HWY_NET_NAME, suppressQuery=True, suppressValidation=True + ) # MTC TM1 doesn't have turn penalties + + os.environ[ + "CHAMP_node_names" + ] = r"M:\\Application\\Model One\\Networks\\TM1_2015_Base_Network\\Node Description.xls" + hwy_abs_path = os.path.abspath(os.path.join(hwypath, HWY_NET_NAME)) try: - networks['trn'].write(path=trnpath, - name="transitLines", - writeEmptyFiles = False, - suppressQuery = True, - suppressValidation = False, - cubeNetFileForValidation = hwy_abs_path) + networks["trn"].write( + path=trnpath, + name="transitLines", + writeEmptyFiles=False, + suppressQuery=True, + suppressValidation=False, + cubeNetFileForValidation=hwy_abs_path, + ) except Exception as e: - Wrangler.WranglerLogger.warn("Transit network validation failed, writing anyway") - networks['trn'].write(path=trnpath, - name="transitLines", - writeEmptyFiles = False, - suppressQuery = True, - suppressValidation = True, - cubeNetFileForValidation = hwy_abs_path) + Wrangler.WranglerLogger.warn( + "Transit network validation failed, writing anyway" + ) + networks["trn"].write( + path=trnpath, + name="transitLines", + writeEmptyFiles=False, + suppressQuery=True, + suppressValidation=True, + cubeNetFileForValidation=hwy_abs_path, + ) # Write the transit capacity configuration # create default capacity configuration? @@ -108,4 +136,6 @@ # Wrangler.TransitNetwork.capacity.writeTransitLineToVehicle(directory = trnpath) # Wrangler.TransitNetwork.capacity.writeTransitPrefixToVehicle(directory = trnpath) - Wrangler.WranglerLogger.debug("Successfully completed running %s" % os.path.abspath(__file__)) + Wrangler.WranglerLogger.debug( + "Successfully completed running %s" % os.path.abspath(__file__) + ) diff --git a/scripts/net_spec_NGF.py b/scripts/net_spec_NGF.py index 6b04b7d..54d28ef 100644 --- a/scripts/net_spec_NGF.py +++ b/scripts/net_spec_NGF.py @@ -1,12 +1,13 @@ import os import collections + # MANDATORY. Set this to be the Project Name. # e.g. "RTP2021", "TIP2021", etc -PROJECT = "NGF" +PROJECT = "NGF" # MANDATORY. Set this to be the git tag for checking out network projects. -#TAG = "HEAD" # Use this tag if you want NetworkWrangler to use the latest version in the local repo to build the network -#TAG = "PBA50_Blueprint" # Use this tag if you want to replicate the network built for PBA50 +# TAG = "HEAD" # Use this tag if you want NetworkWrangler to use the latest version in the local repo to build the network +# TAG = "PBA50_Blueprint" # Use this tag if you want to replicate the network built for PBA50 TAG = "HEAD" # A Alamedaproject can either be a simple string, or it can be @@ -15,100 +16,116 @@ # For example: # {'name':"Muni_TEP", 'kwargs':{'servicePlan':"'2012oct'"}} ########################################################### -COMMITTED_PROJECTS = collections.OrderedDict([ - (2015, { - 'hwy':[], - 'trn':[] - }), - (2020, { - 'hwy':[], - 'trn':[], - }), - (2025, { - 'hwy':[], - 'trn':[] - }), - (2030, { - 'hwy':[], - 'trn':[] - }), - (2035, { - 'hwy':[], - 'trn':[] - }) -]) +COMMITTED_PROJECTS = collections.OrderedDict( + [ + (2015, {"hwy": [], "trn": []}), + ( + 2020, + { + "hwy": [], + "trn": [], + }, + ), + (2025, {"hwy": [], "trn": []}), + (2030, {"hwy": [], "trn": []}), + (2035, {"hwy": [], "trn": []}), + ] +) ########################################################### # Blueprint projects -BLUEPRINT_PROJECTS = collections.OrderedDict([ - (2015, {'hwy':[], - 'trn':[] - }), - (2020, {'hwy':[], - 'trn':[] - }), - (2025, {'hwy':['BP_Vision_Zero' - ], - 'trn':[] - }), - (2030, {'hwy':[], - 'trn':[] - }), - (2035, {'hwy':[], - 'trn':[{'name':'EIR1_Freq_Boosts', 'kwargs':{'MODELYEAR':'2035'}, 'variants_include':['Mock']}] - }) - ]) +BLUEPRINT_PROJECTS = collections.OrderedDict( + [ + (2015, {"hwy": [], "trn": []}), + (2020, {"hwy": [], "trn": []}), + (2025, {"hwy": ["BP_Vision_Zero"], "trn": []}), + (2030, {"hwy": [], "trn": []}), + ( + 2035, + { + "hwy": [], + "trn": [ + { + "name": "EIR1_Freq_Boosts", + "kwargs": {"MODELYEAR": "2035"}, + "variants_include": ["Mock"], + } + ], + }, + ), + ] +) ########################################################### # NextGenFwy projects -NGF_PROJECTS = collections.OrderedDict([ - (2015, {'hwy':[], - 'trn':[] - }), - (2020, {'hwy':[], - 'trn':[] - }), - (2025, {'hwy':[], - 'trn':[] - }), - (2030, {'hwy':[], - 'trn':[] - }), - (2035, {'hwy':[{'name':'NGF_NoProject_farefiles', 'variants_include':['BlueprintSegmented']}, - {'name':'NGF_BlueprintSegmented', 'variants_include':['BlueprintSegmented']} - ], - 'trn':[] - }) - ]) +NGF_PROJECTS = collections.OrderedDict( + [ + (2015, {"hwy": [], "trn": []}), + (2020, {"hwy": [], "trn": []}), + (2025, {"hwy": [], "trn": []}), + (2030, {"hwy": [], "trn": []}), + ( + 2035, + { + "hwy": [ + { + "name": "NGF_NoProject_farefiles", + "variants_include": ["BlueprintSegmented"], + }, + { + "name": "NGF_BlueprintSegmented", + "variants_include": ["BlueprintSegmented"], + }, + ], + "trn": [], + }, + ), + ] +) # Put them together for NETWORK_PROJECTS -NETWORK_PROJECTS = collections.OrderedDict() +NETWORK_PROJECTS = collections.OrderedDict() for YEAR in COMMITTED_PROJECTS.keys(): NETWORK_PROJECTS[YEAR] = { - 'hwy':COMMITTED_PROJECTS[YEAR]['hwy'] + BLUEPRINT_PROJECTS[YEAR]['hwy'] + NGF_PROJECTS[YEAR]['hwy'], - 'trn':COMMITTED_PROJECTS[YEAR]['trn'] + BLUEPRINT_PROJECTS[YEAR]['trn'] + NGF_PROJECTS[YEAR]['trn'] + "hwy": COMMITTED_PROJECTS[YEAR]["hwy"] + + BLUEPRINT_PROJECTS[YEAR]["hwy"] + + NGF_PROJECTS[YEAR]["hwy"], + "trn": COMMITTED_PROJECTS[YEAR]["trn"] + + BLUEPRINT_PROJECTS[YEAR]["trn"] + + NGF_PROJECTS[YEAR]["trn"], } # handle net_remove, nets keywords - for netmode in ['hwy','trn']: + for netmode in ["hwy", "trn"]: # iterate backwards via index to delete cleanly - for project_idx in range(len(NETWORK_PROJECTS[YEAR][netmode])-1,-1,-1): + for project_idx in range(len(NETWORK_PROJECTS[YEAR][netmode]) - 1, -1, -1): project = NETWORK_PROJECTS[YEAR][netmode][project_idx] # special handling requires project to be specified as dictionary - if not isinstance(project, dict): continue + if not isinstance(project, dict): + continue # variants_exclude: specifies list of network variants for which this project should be *excluded* - if 'variants_exclude' in project.keys() and NET_VARIANT in project['variants_exclude']: - Wrangler.WranglerLogger.info("Removing {} {} {}".format(YEAR, netmode, project)) + if ( + "variants_exclude" in project.keys() + and NET_VARIANT in project["variants_exclude"] + ): + Wrangler.WranglerLogger.info( + "Removing {} {} {}".format(YEAR, netmode, project) + ) del NETWORK_PROJECTS[YEAR][netmode][project_idx] continue # variants_include: specifies list of network variants for which this project should be *included* # if this keyword is present, then this project is included *only* for variants in this list - if 'variants_include' in project.keys() and NET_VARIANT not in project['variants_include']: - Wrangler.WranglerLogger.info("Removing {} {} {}".format(YEAR, netmode, project)) + if ( + "variants_include" in project.keys() + and NET_VARIANT not in project["variants_include"] + ): + Wrangler.WranglerLogger.info( + "Removing {} {} {}".format(YEAR, netmode, project) + ) del NETWORK_PROJECTS[YEAR][netmode][project_idx] continue @@ -118,11 +135,15 @@ # for YEAR in NETWORK_PROJECTS.keys(): # if anything is applied - if ((len(NETWORK_PROJECTS[YEAR]['hwy']) > 0) or (len(NETWORK_PROJECTS[YEAR]['trn']) > 0)): - NETWORK_PROJECTS[YEAR]['hwy'].append('No_zero_length_links') + if (len(NETWORK_PROJECTS[YEAR]["hwy"]) > 0) or ( + len(NETWORK_PROJECTS[YEAR]["trn"]) > 0 + ): + NETWORK_PROJECTS[YEAR]["hwy"].append("No_zero_length_links") - if ((len(NETWORK_PROJECTS[YEAR]['hwy']) > 0) or (len(NETWORK_PROJECTS[YEAR]['trn']) > 0)): - NETWORK_PROJECTS[YEAR]['trn'].append('Move_buses_to_HOV_EXP_lanes') + if (len(NETWORK_PROJECTS[YEAR]["hwy"]) > 0) or ( + len(NETWORK_PROJECTS[YEAR]["trn"]) > 0 + ): + NETWORK_PROJECTS[YEAR]["trn"].append("Move_buses_to_HOV_EXP_lanes") # OPTIONAL. The default route network project directory is Y:\networks. If diff --git a/scripts/net_spec_STIP2019.py b/scripts/net_spec_STIP2019.py index 6a61b51..7cc2901 100644 --- a/scripts/net_spec_STIP2019.py +++ b/scripts/net_spec_STIP2019.py @@ -21,109 +21,167 @@ # to specify a special tag or special keyword args for the projects apply() call. # For example: # {'name':"Muni_TEP", 'kwargs':{'servicePlan':"'2012oct'"}} -NETWORK_PROJECTS = collections.OrderedDict([ - (2015, { - 'hwy':['PROJ_attributes', # adds PROJ attributes to NODE and LINK - {'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2015'}}], - 'trn':[] - }), - (2020, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2020'}}, - {'name':'EXP_237B', 'kwargs':{'FUTURE':SCENARIO}}, - 'EXP_580C', - 'EXP_680D', - 'EXP_680F', - 'SCL130001_237_101_MAT_Int_Mod', - 'REG090003_SCLARA_FIP', - 'ALA130005_Dougherty_road_widening', - 'ALA130006_Dublin_Blvd_widening', - 'ALA130014_7th_St_road_diet', - 'ALA150004_EastBay_BRT', - 'CC_130046_I680_SR4_Int_Rec', - 'CC_070035_I80_SPDamRd_Int_Phase1', - 'CC_070011_Brentwood_Blvd_Widening', - 'CC_070075_Kirker_Pass_Truck_Lane', - 'CC_090019_Bollinger_Canyon_Widening', - 'CC_130006_Concord_BART_road_diets', - 'CC_170001_SanRamonValleyBlvd_Lane_Addition', - 'CC_170061_Bus_On_Shoulder_680BRT', - 'MRN150009_San_Rafael_Bridge_Improvements', - 'SF_070027_Yerba_Buena_Ramp_Imp', - 'SF_070005_VanNess_BRT', - 'SF_130011_2ndSt_Road_Diet', - 'SM_110047_SR92_ElCam_Ramp_Mod', - 'SOL110005_Jepson_Van_to_Com', - 'SON070004_101_MarinSonNarrows_Phase1', - 'ALA050014_SR84_Widening', - 'ALA170011_BayBridge_HOV_Connectors', - 'ALA150047_TelegraphAve_Complete_Streets', - 'SCL190002_280_Foothill_improvement', - 'I80_AdaptiveRampMetering', - 'VAR170021_Freeway_Performance_I880', - 'SonomaCounty_Transit_NoBuild2050', - 'SF_MuniForward_Committed'], - 'trn':['ALA050015_BART_to_WarmSprings', - 'ACGo', - 'CC_050025_EBart_to_Antioch', - 'SCL110005_BART_to_Berryessa', - 'SF_010015_Transbay_Terminal', - 'SF_010037_Muni_Central_Subway', - 'SF_070027_Yerba_Buena_Ramp_Imp', - 'SOL030002_FairfieldVacaville_Stn', - 'SON090002_SMART', - 'SON090002_SMART_to_Larkspur', - 'CC_070062_Richmond_Ferry', - 'SF_MuniForward_Committed', - 'VTA_Next', - 'SCL130001_237_101_MAT_Int_Mod', - 'SonomaCounty_Transit_NoBuild2050', - 'SMART_Novato', - 'Xfare_update_2020', - 'ACTransit_Committed', - 'ferry_update_2019', - 'SamTrans_ECR_Rapid'], - }), - (2025, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2025'}}, - 'EXP_CC_050028_I680_SB_HOV_Completion', - 'EXP_101B1', - 'EXP_101B2', - 'EXP_101C'], - 'trn':['SF_010028_Caltrain_Modernization', - 'REG090037_New_BART_Trains'] - }), - (2030, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2030'}}], - 'trn':[] - }), - (2035, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2035'}}], - 'trn':[] - }), - # every project in the STIP 2040 baseline are loaded on or before 2035 - (2040, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2040'}}, - 'EXP_STIP_NoProject'], # this is not a real project - it sets the toll rates for express lanes - 'trn':['Move_buses_to_HOV_EXP_lanes'] - }), - # note that this is not really the 2045 network, it's really the STIP project set to build the STIP 2040 with Project - (2045, { - 'hwy':['SOL110006_Jepson_1B_1C', - 'STIP_US101_Managed_Lanes_I80_Solano', - 'STIP_US101_Managed_Lanes_NI380', - 'STIP_US101_ManagedLanes_Whipple_I380', - 'STIP_US101_ExpLanes_Phase5', - 'MAJ_SR4_Operational_Improvements', - 'STIP_17_06_0010_WoodsideRd', - 'STIP_ProduceAve', - 'STIP_ITS_SoSF', - 'STIP_ITS_SM', - 'STIP_FairgroundsDr', - 'EXP_STIP_Project'], # this project is not a real project - it sets the toll rates for express lanes - 'trn':['MAJ_BRT030001_BART_to_SanJose', - 'Move_buses_to_HOV_EXP_lanes'] - }) -]) +NETWORK_PROJECTS = collections.OrderedDict( + [ + ( + 2015, + { + "hwy": [ + "PROJ_attributes", # adds PROJ attributes to NODE and LINK + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2015"}, + }, + ], + "trn": [], + }, + ), + ( + 2020, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2020"}, + }, + {"name": "EXP_237B", "kwargs": {"FUTURE": SCENARIO}}, + "EXP_580C", + "EXP_680D", + "EXP_680F", + "SCL130001_237_101_MAT_Int_Mod", + "REG090003_SCLARA_FIP", + "ALA130005_Dougherty_road_widening", + "ALA130006_Dublin_Blvd_widening", + "ALA130014_7th_St_road_diet", + "ALA150004_EastBay_BRT", + "CC_130046_I680_SR4_Int_Rec", + "CC_070035_I80_SPDamRd_Int_Phase1", + "CC_070011_Brentwood_Blvd_Widening", + "CC_070075_Kirker_Pass_Truck_Lane", + "CC_090019_Bollinger_Canyon_Widening", + "CC_130006_Concord_BART_road_diets", + "CC_170001_SanRamonValleyBlvd_Lane_Addition", + "CC_170061_Bus_On_Shoulder_680BRT", + "MRN150009_San_Rafael_Bridge_Improvements", + "SF_070027_Yerba_Buena_Ramp_Imp", + "SF_070005_VanNess_BRT", + "SF_130011_2ndSt_Road_Diet", + "SM_110047_SR92_ElCam_Ramp_Mod", + "SOL110005_Jepson_Van_to_Com", + "SON070004_101_MarinSonNarrows_Phase1", + "ALA050014_SR84_Widening", + "ALA170011_BayBridge_HOV_Connectors", + "ALA150047_TelegraphAve_Complete_Streets", + "SCL190002_280_Foothill_improvement", + "I80_AdaptiveRampMetering", + "VAR170021_Freeway_Performance_I880", + "SonomaCounty_Transit_NoBuild2050", + "SF_MuniForward_Committed", + ], + "trn": [ + "ALA050015_BART_to_WarmSprings", + "ACGo", + "CC_050025_EBart_to_Antioch", + "SCL110005_BART_to_Berryessa", + "SF_010015_Transbay_Terminal", + "SF_010037_Muni_Central_Subway", + "SF_070027_Yerba_Buena_Ramp_Imp", + "SOL030002_FairfieldVacaville_Stn", + "SON090002_SMART", + "SON090002_SMART_to_Larkspur", + "CC_070062_Richmond_Ferry", + "SF_MuniForward_Committed", + "VTA_Next", + "SCL130001_237_101_MAT_Int_Mod", + "SonomaCounty_Transit_NoBuild2050", + "SMART_Novato", + "Xfare_update_2020", + "ACTransit_Committed", + "ferry_update_2019", + "SamTrans_ECR_Rapid", + ], + }, + ), + ( + 2025, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2025"}, + }, + "EXP_CC_050028_I680_SB_HOV_Completion", + "EXP_101B1", + "EXP_101B2", + "EXP_101C", + ], + "trn": [ + "SF_010028_Caltrain_Modernization", + "REG090037_New_BART_Trains", + ], + }, + ), + ( + 2030, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2030"}, + } + ], + "trn": [], + }, + ), + ( + 2035, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2035"}, + } + ], + "trn": [], + }, + ), + # every project in the STIP 2040 baseline are loaded on or before 2035 + ( + 2040, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2040"}, + }, + "EXP_STIP_NoProject", + ], # this is not a real project - it sets the toll rates for express lanes + "trn": ["Move_buses_to_HOV_EXP_lanes"], + }, + ), + # note that this is not really the 2045 network, it's really the STIP project set to build the STIP 2040 with Project + ( + 2045, + { + "hwy": [ + "SOL110006_Jepson_1B_1C", + "STIP_US101_Managed_Lanes_I80_Solano", + "STIP_US101_Managed_Lanes_NI380", + "STIP_US101_ManagedLanes_Whipple_I380", + "STIP_US101_ExpLanes_Phase5", + "MAJ_SR4_Operational_Improvements", + "STIP_17_06_0010_WoodsideRd", + "STIP_ProduceAve", + "STIP_ITS_SoSF", + "STIP_ITS_SM", + "STIP_FairgroundsDr", + "EXP_STIP_Project", + ], # this project is not a real project - it sets the toll rates for express lanes + "trn": ["MAJ_BRT030001_BART_to_SanJose", "Move_buses_to_HOV_EXP_lanes"], + }, + ), + ] +) # OPTIONAL. The default route network project directory is Y:\networks. If # projects are stored in another directory, then use this variable to specify it. diff --git a/scripts/net_spec_STIP2022.py b/scripts/net_spec_STIP2022.py index 2f489a1..34923a5 100644 --- a/scripts/net_spec_STIP2022.py +++ b/scripts/net_spec_STIP2022.py @@ -6,7 +6,7 @@ # MANDATORY. Set this to be the Scenario Name # Pass this as --scenario to build_network_mtc.py -assert(SCENARIO in ["NoProject","Project"]) +assert SCENARIO in ["NoProject", "Project"] # MANDATORY. Set this to be the git tag for checking out network projects. TAG = "HEAD" @@ -23,220 +23,314 @@ # {'name':"Muni_TEP", 'kwargs':{'servicePlan':"'2012oct'"}} ########################################################### -COMMITTED_PROJECTS = collections.OrderedDict([ - (2015, { - 'hwy':['PROJ_attributes', # adds PROJ attributes to NODE and LINK - {'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2015'}}], - 'trn':[] - }), - (2020, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2020'}}, - {'name':'EXP_237B', 'kwargs':{'FUTURE':"PBA50"}}, # todo: update this to support PBA50 - 'EXP_580C', - 'EXP_680D', - 'EXP_880A', - 'HOV_680F', - 'SCL130001_237_101_MAT_Int_Mod', - 'REG090003_SCLARA_FIP', - 'ALA130005_Dougherty_road_widening', - 'ALA130006_Dublin_Blvd_widening', - 'ALA130014_7th_St_road_diet', - 'ALA130026_Shattuck_Complete_Streets', - 'ALA170049_Central_AVE_Safety_Improvements', - 'ALA150004_EastBay_BRT', - 'CC_130001_BaileyRd_SR4', - 'CC_130046_I680_SR4_Int_Rec', - 'CC_070035_I80_SPDamRd_Int_Phase1', - 'CC_070011_Brentwood_Blvd_Widening', - 'CC_070075_Kirker_Pass_Truck_Lane', - 'CC_090019_Bollinger_Canyon_Widening', - 'CC_130006_Concord_BART_road_diets', - 'CC_170001_SanRamonValleyBlvd_Lane_Addition', - 'MRN150009_San_Rafael_Bridge_Improvements', - 'SF_070027_Yerba_Buena_Ramp_Imp', - 'SF_070005_VanNess_BRT', - 'SF_130011_2ndSt_Road_Diet', - 'SF_Market_Street_Closure', - 'SM_110047_SR92_ElCam_Ramp_Mod', - 'SOL110005_Jepson_Van_to_Com', - 'FBP_SL_042_Jepson_2A', - 'SON070004_101_MarinSonNarrows_Phase1', - 'ALA050014_SR84_Widening', - 'ALA170011_BayBridge_HOV_Connectors', - 'ALA150047_TelegraphAve_Complete_Streets', - 'SM_110047_SR92_ElCam_Ramp_Mod', - 'SCL190002_280_Foothill_improvement', - 'SCL190006_101SB_offramp_improvement', - 'I80_AdaptiveRampMetering', - 'VAR170021_Freeway_Performance_I880', - 'SonomaCounty_Transit_NoBuild2050', - 'SF_MuniForward_Committed', - 'FBP_MU_029_Broadway_Transit_Only_Lanes', - 'EXP_Blueprint_NoProject', - 'FBP_AL_067_Rte84Wide', - 'FBP_AL_065_Bancroft_Bus_Only', - 'FBP_SM_032_US101_Willow_Interchange'], - 'trn':['ALA050015_BART_to_WarmSprings', - 'ACGo', - 'CC_050025_EBart_to_Antioch', - 'GGTransit_Committed', - 'SCL110005_BART_to_Berryessa', - 'SF_010015_Transbay_Terminal', - 'SF_010037_Muni_Central_Subway', - 'SF_070027_Yerba_Buena_Ramp_Imp', - 'SOL030002_FairfieldVacaville_Stn', - 'SON090002_SMART', - 'SON090002_SMART_to_Larkspur', - 'CC_070062_Richmond_Ferry', - 'SF_MuniForward_Committed', - 'VTA_Next', - 'SCL130001_237_101_MAT_Int_Mod', - 'SonomaCounty_Transit_NoBuild2050', - 'SMART_Novato', - 'Xfare_update_2020', - 'ACTransit_Committed', - 'ferry_update_2019', - 'Napa_Solano_Updates_2020', - 'FBP_Beale_Transit_Only_Lane', - 'SamTrans_ECR_Rapid', - 'ALA150004_EastBay_BRT', - {'name':'FBP_SL_026_SolExpressBus', 'kwargs':{'MODELYEAR':'2020'}}], - }), - (2025, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2025'}}, - 'EXP_CC_050028_I680_SB_HOV_Completion', - 'EXP_101B1', - 'EXP_101B2', - 'EXP_680C1', - 'EXP_680F', - 'EXP_85D', - 'EXP_101C', - 'ALA150001_I680_SR84_Int_Wid', - 'ALA150043_Claremont_road_diet', - 'CC_070009_Slatten_Ranch_Rd_Extension', - 'SF_070004_Geary_BRT_Phase1', - 'SON070004_101_MarinSonNarrows_Phase2', - 'SOL110006_Jepson_1B_1C', - 'SCL190008_US101_DLC_Int_Imp', - 'I880_US101_AdaptiveRampMetering', - 'SOL070020_I80_I680_SR12_Int_1_2A', - 'FBP_NP_036_SR29_Imola_PNR', - 'ALA170052_Fruitvale_Ave_ped_improvements', - 'EXP_Blueprint_NoProject', - {'name': 'RRSP_Alameda_Point_Transit_Improvements', 'kwargs':{'BUILT':"'built'"}}], - 'trn':['SF_010028_Caltrain_Modernization', - 'SON090002_SMART_to_Windsor', - 'REG090037_New_BART_Trains', - 'FBP_NP_036_SR29_Imola_PNR', - 'SOL070020_I80_I680_SR12_Int_1_2A', - {'name': 'RRSP_Alameda_Point_Transit_Improvements', 'kwargs':{'BUILT':"'built'"}}] - }), - (2030, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2030'}}, - 'EXP_Blueprint_NoProject'], - 'trn':['BART_NoProject'] - }), - (2035, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2035'}}, - 'EXP_Blueprint_NoProject'], - 'trn':[] - }), - (2040, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2040'}}, - 'EXP_Blueprint_NoProject'], - 'trn':[] - }), - (2045, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2045'}}, - 'EXP_Blueprint_NoProject'], - 'trn':[] - }), - (2050, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2050'}}, - 'EXP_Blueprint_NoProject'], - 'trn':[] - }) -]) +COMMITTED_PROJECTS = collections.OrderedDict( + [ + ( + 2015, + { + "hwy": [ + "PROJ_attributes", # adds PROJ attributes to NODE and LINK + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2015"}, + }, + ], + "trn": [], + }, + ), + ( + 2020, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2020"}, + }, + { + "name": "EXP_237B", + "kwargs": {"FUTURE": "PBA50"}, + }, # todo: update this to support PBA50 + "EXP_580C", + "EXP_680D", + "EXP_880A", + "HOV_680F", + "SCL130001_237_101_MAT_Int_Mod", + "REG090003_SCLARA_FIP", + "ALA130005_Dougherty_road_widening", + "ALA130006_Dublin_Blvd_widening", + "ALA130014_7th_St_road_diet", + "ALA130026_Shattuck_Complete_Streets", + "ALA170049_Central_AVE_Safety_Improvements", + "ALA150004_EastBay_BRT", + "CC_130001_BaileyRd_SR4", + "CC_130046_I680_SR4_Int_Rec", + "CC_070035_I80_SPDamRd_Int_Phase1", + "CC_070011_Brentwood_Blvd_Widening", + "CC_070075_Kirker_Pass_Truck_Lane", + "CC_090019_Bollinger_Canyon_Widening", + "CC_130006_Concord_BART_road_diets", + "CC_170001_SanRamonValleyBlvd_Lane_Addition", + "MRN150009_San_Rafael_Bridge_Improvements", + "SF_070027_Yerba_Buena_Ramp_Imp", + "SF_070005_VanNess_BRT", + "SF_130011_2ndSt_Road_Diet", + "SF_Market_Street_Closure", + "SM_110047_SR92_ElCam_Ramp_Mod", + "SOL110005_Jepson_Van_to_Com", + "FBP_SL_042_Jepson_2A", + "SON070004_101_MarinSonNarrows_Phase1", + "ALA050014_SR84_Widening", + "ALA170011_BayBridge_HOV_Connectors", + "ALA150047_TelegraphAve_Complete_Streets", + "SM_110047_SR92_ElCam_Ramp_Mod", + "SCL190002_280_Foothill_improvement", + "SCL190006_101SB_offramp_improvement", + "I80_AdaptiveRampMetering", + "VAR170021_Freeway_Performance_I880", + "SonomaCounty_Transit_NoBuild2050", + "SF_MuniForward_Committed", + "FBP_MU_029_Broadway_Transit_Only_Lanes", + "EXP_Blueprint_NoProject", + "FBP_AL_067_Rte84Wide", + "FBP_AL_065_Bancroft_Bus_Only", + "FBP_SM_032_US101_Willow_Interchange", + ], + "trn": [ + "ALA050015_BART_to_WarmSprings", + "ACGo", + "CC_050025_EBart_to_Antioch", + "GGTransit_Committed", + "SCL110005_BART_to_Berryessa", + "SF_010015_Transbay_Terminal", + "SF_010037_Muni_Central_Subway", + "SF_070027_Yerba_Buena_Ramp_Imp", + "SOL030002_FairfieldVacaville_Stn", + "SON090002_SMART", + "SON090002_SMART_to_Larkspur", + "CC_070062_Richmond_Ferry", + "SF_MuniForward_Committed", + "VTA_Next", + "SCL130001_237_101_MAT_Int_Mod", + "SonomaCounty_Transit_NoBuild2050", + "SMART_Novato", + "Xfare_update_2020", + "ACTransit_Committed", + "ferry_update_2019", + "Napa_Solano_Updates_2020", + "FBP_Beale_Transit_Only_Lane", + "SamTrans_ECR_Rapid", + "ALA150004_EastBay_BRT", + { + "name": "FBP_SL_026_SolExpressBus", + "kwargs": {"MODELYEAR": "2020"}, + }, + ], + }, + ), + ( + 2025, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2025"}, + }, + "EXP_CC_050028_I680_SB_HOV_Completion", + "EXP_101B1", + "EXP_101B2", + "EXP_680C1", + "EXP_680F", + "EXP_85D", + "EXP_101C", + "ALA150001_I680_SR84_Int_Wid", + "ALA150043_Claremont_road_diet", + "CC_070009_Slatten_Ranch_Rd_Extension", + "SF_070004_Geary_BRT_Phase1", + "SON070004_101_MarinSonNarrows_Phase2", + "SOL110006_Jepson_1B_1C", + "SCL190008_US101_DLC_Int_Imp", + "I880_US101_AdaptiveRampMetering", + "SOL070020_I80_I680_SR12_Int_1_2A", + "FBP_NP_036_SR29_Imola_PNR", + "ALA170052_Fruitvale_Ave_ped_improvements", + "EXP_Blueprint_NoProject", + { + "name": "RRSP_Alameda_Point_Transit_Improvements", + "kwargs": {"BUILT": "'built'"}, + }, + ], + "trn": [ + "SF_010028_Caltrain_Modernization", + "SON090002_SMART_to_Windsor", + "REG090037_New_BART_Trains", + "FBP_NP_036_SR29_Imola_PNR", + "SOL070020_I80_I680_SR12_Int_1_2A", + { + "name": "RRSP_Alameda_Point_Transit_Improvements", + "kwargs": {"BUILT": "'built'"}, + }, + ], + }, + ), + ( + 2030, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2030"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": ["BART_NoProject"], + }, + ), + ( + 2035, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2035"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": [], + }, + ), + ( + 2040, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2040"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": [], + }, + ), + ( + 2045, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2045"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": [], + }, + ), + ( + 2050, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2050"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": [], + }, + ), + ] +) ########################################################### # STIP projects -STIP_PROJECTS = collections.OrderedDict([ - (2015, { - 'hwy':[], - 'trn':[] - }), - (2020, { - 'hwy':[], - 'trn':[], - }), - (2025, { - 'hwy':['STIP2022_SCL110001_US101SV_EXP_P5', - 'FBP_CC_050_SR4_Operation_Improvements_EB'], - 'trn':[] - }), - (2030, { - 'hwy':['STIP2022_SM190009_US101SM_EXP', - 'STIP2022_CC170017_I680NB_EXP_P1', - 'FBP_AL_045_Oak_Ala_Access_Pr'], - 'trn':['MAJ_BRT030001_BART_to_SanJose', - 'FBP_AL_045_Oak_Ala_Access_Pr'] - }), - (2035, { - 'hwy':[], - 'trn':[] - }), - (2040, { - 'hwy':['FBP_CC_051_SR4_Operation_Improvements_WB'], - 'trn':[] - }), - (2045, { - 'hwy':[], - 'trn':[] - }), - (2050, { - 'hwy':['STIP_ITS_SM', - 'STIP2022_CC170017_I680_ITS_TOS'], - 'trn':[] - }) -]) +STIP_PROJECTS = collections.OrderedDict( + [ + (2015, {"hwy": [], "trn": []}), + ( + 2020, + { + "hwy": [], + "trn": [], + }, + ), + ( + 2025, + { + "hwy": [ + "STIP2022_SCL110001_US101SV_EXP_P5", + "FBP_CC_050_SR4_Operation_Improvements_EB", + ], + "trn": [], + }, + ), + ( + 2030, + { + "hwy": [ + "STIP2022_SM190009_US101SM_EXP", + "STIP2022_CC170017_I680NB_EXP_P1", + "FBP_AL_045_Oak_Ala_Access_Pr", + ], + "trn": [ + "MAJ_BRT030001_BART_to_SanJose", + "FBP_AL_045_Oak_Ala_Access_Pr", + ], + }, + ), + (2035, {"hwy": [], "trn": []}), + (2040, {"hwy": ["FBP_CC_051_SR4_Operation_Improvements_WB"], "trn": []}), + (2045, {"hwy": [], "trn": []}), + (2050, {"hwy": ["STIP_ITS_SM", "STIP2022_CC170017_I680_ITS_TOS"], "trn": []}), + ] +) ########################################################### # Put them together for NETWORK_PROJECTS -NETWORK_PROJECTS = collections.OrderedDict() +NETWORK_PROJECTS = collections.OrderedDict() for YEAR in COMMITTED_PROJECTS.keys(): if SCENARIO == "NoProject": # baseline: just committed NETWORK_PROJECTS[YEAR] = { - 'hwy':COMMITTED_PROJECTS[YEAR]['hwy'], - 'trn':COMMITTED_PROJECTS[YEAR]['trn'] + "hwy": COMMITTED_PROJECTS[YEAR]["hwy"], + "trn": COMMITTED_PROJECTS[YEAR]["trn"], } else: # stip NETWORK_PROJECTS[YEAR] = { - 'hwy':COMMITTED_PROJECTS[YEAR]['hwy'] + STIP_PROJECTS[YEAR]['hwy'], - 'trn':COMMITTED_PROJECTS[YEAR]['trn'] + STIP_PROJECTS[YEAR]['trn'] + "hwy": COMMITTED_PROJECTS[YEAR]["hwy"] + STIP_PROJECTS[YEAR]["hwy"], + "trn": COMMITTED_PROJECTS[YEAR]["trn"] + STIP_PROJECTS[YEAR]["trn"], } # handle net_remove, nets keywords - for netmode in ['hwy','trn']: + for netmode in ["hwy", "trn"]: # iterate backwards via index to delete cleanly - for project_idx in range(len(NETWORK_PROJECTS[YEAR][netmode])-1,-1,-1): + for project_idx in range(len(NETWORK_PROJECTS[YEAR][netmode]) - 1, -1, -1): project = NETWORK_PROJECTS[YEAR][netmode][project_idx] # special handling requires project to be specified as dictionary - if not isinstance(project, dict): continue + if not isinstance(project, dict): + continue # variants_exclude: specifies list of network variants for which this project should be *excluded* - if 'variants_exclude' in project.keys() and NET_VARIANT in project['variants_exclude']: - Wrangler.WranglerLogger.info("Removing {} {} {}".format(YEAR, netmode, project)) + if ( + "variants_exclude" in project.keys() + and NET_VARIANT in project["variants_exclude"] + ): + Wrangler.WranglerLogger.info( + "Removing {} {} {}".format(YEAR, netmode, project) + ) del NETWORK_PROJECTS[YEAR][netmode][project_idx] continue # variants_include: specifies list of network variants for which this project should be *included* # if this keyword is present, then this project is included *only* for variants in this list - if 'variants_include' in project.keys() and NET_VARIANT not in project['variants_include']: - Wrangler.WranglerLogger.info("Removing {} {} {}".format(YEAR, netmode, project)) + if ( + "variants_include" in project.keys() + and NET_VARIANT not in project["variants_include"] + ): + Wrangler.WranglerLogger.info( + "Removing {} {} {}".format(YEAR, netmode, project) + ) del NETWORK_PROJECTS[YEAR][netmode][project_idx] continue @@ -247,11 +341,15 @@ # for YEAR in NETWORK_PROJECTS.keys(): # if anything is applied - if ((len(NETWORK_PROJECTS[YEAR]['hwy']) > 0) or (len(NETWORK_PROJECTS[YEAR]['trn']) > 0)): - NETWORK_PROJECTS[YEAR]['hwy'].append('No_zero_length_links') + if (len(NETWORK_PROJECTS[YEAR]["hwy"]) > 0) or ( + len(NETWORK_PROJECTS[YEAR]["trn"]) > 0 + ): + NETWORK_PROJECTS[YEAR]["hwy"].append("No_zero_length_links") - if ((len(NETWORK_PROJECTS[YEAR]['hwy']) > 0) or (len(NETWORK_PROJECTS[YEAR]['trn']) > 0)): - NETWORK_PROJECTS[YEAR]['trn'].append('Move_buses_to_HOV_EXP_lanes') + if (len(NETWORK_PROJECTS[YEAR]["hwy"]) > 0) or ( + len(NETWORK_PROJECTS[YEAR]["trn"]) > 0 + ): + NETWORK_PROJECTS[YEAR]["trn"].append("Move_buses_to_HOV_EXP_lanes") # OPTIONAL. The default route network project directory is Y:\networks. If # projects are stored in another directory, then use this variable to specify it. diff --git a/scripts/net_spec_TIP2023.py b/scripts/net_spec_TIP2023.py index b8e41d4..872eccc 100644 --- a/scripts/net_spec_TIP2023.py +++ b/scripts/net_spec_TIP2023.py @@ -6,14 +6,14 @@ # MANDATORY. Set this to be the Scenario Name # Pass this as --scenario to build_network_mtc.py -#assert(SCENARIO in ["NoProject","Project"]) +# assert(SCENARIO in ["NoProject","Project"]) # MANDATORY. Set this to be the git tag for checking out network projects. TAG = "TIP_2023" # MANDATORY. Set this to the directory in which to write your outputs. # "hwy" and "trn" subdirectories will be created here. -#OUT_DIR = PROJECT + "_" + SCENARIO + "_network_{}" # YEAR +# OUT_DIR = PROJECT + "_" + SCENARIO + "_network_{}" # YEAR OUT_DIR = PROJECT + "_" + "_network_{}" # YEAR # MANDATORY. Should be a dictionary with keys "hwy", "muni", "rail", "bus" @@ -24,492 +24,907 @@ # {'name':"Muni_TEP", 'kwargs':{'servicePlan':"'2012oct'"}} ########################################################### -COMMITTED_PROJECTS = collections.OrderedDict([ - (2015, { - 'hwy':['PROJ_attributes', # adds PROJ attributes to NODE and LINK - {'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2015'}}], - 'trn':[] - }), - (2020, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2020'}}, - {'name':'EXP_237B', 'kwargs':{'FUTURE':"PBA50"}}, # todo: update this to support PBA50 - 'EXP_580C', - 'EXP_680D', - 'EXP_880A', - 'HOV_680F', - 'SCL130001_237_101_MAT_Int_Mod', - 'REG090003_SCLARA_FIP', - 'ALA130005_Dougherty_road_widening', - 'ALA130006_Dublin_Blvd_widening', - 'ALA130014_7th_St_road_diet', - 'ALA130026_Shattuck_Complete_Streets', - 'ALA170049_Central_AVE_Safety_Improvements', - 'ALA150004_EastBay_BRT', - 'CC_130001_BaileyRd_SR4', - 'CC_130046_I680_SR4_Int_Rec', - 'CC_070035_I80_SPDamRd_Int_Phase1', - 'CC_070011_Brentwood_Blvd_Widening', - 'CC_070075_Kirker_Pass_Truck_Lane', - 'CC_090019_Bollinger_Canyon_Widening', - 'CC_130006_Concord_BART_road_diets', - 'CC_170001_SanRamonValleyBlvd_Lane_Addition', - 'MRN150009_San_Rafael_Bridge_Improvements', - 'SF_070005_VanNess_BRT', - 'SF_130011_2ndSt_Road_Diet', - 'SF_Market_Street_Closure', - 'SM_110047_SR92_ElCam_Ramp_Mod', - 'SOL110005_Jepson_Van_to_Com', - 'FBP_SL_042_Jepson_2A', - 'SON070004_101_MarinSonNarrows_Phase1', - 'ALA050014_SR84_Widening', - 'ALA170011_BayBridge_HOV_Connectors', - 'ALA150047_TelegraphAve_Complete_Streets', - 'SM_110047_SR92_ElCam_Ramp_Mod', - 'SCL190002_280_Foothill_improvement', - 'SCL190006_101SB_offramp_improvement', - 'I80_AdaptiveRampMetering', - 'VAR170021_Freeway_Performance_I880', - 'SonomaCounty_Transit_NoBuild2050', - 'SF_MuniForward_Committed', - 'FBP_MU_029_Broadway_Transit_Only_Lanes', - 'EXP_Blueprint_NoProject', - 'FBP_AL_067_Rte84Wide', - 'FBP_AL_065_Bancroft_Bus_Only', - 'FBP_SM_032_US101_Willow_Interchange'], - 'trn':['ALA050015_BART_to_WarmSprings', - 'ACGo', - 'CC_050025_EBart_to_Antioch', - 'GGTransit_Committed', - 'SCL110005_BART_to_Berryessa', - 'SF_010015_Transbay_Terminal', - 'SF_010037_Muni_Central_Subway', - 'SOL030002_FairfieldVacaville_Stn', - 'SON090002_SMART', - 'SON090002_SMART_to_Larkspur', - 'CC_070062_Richmond_Ferry', - 'SF_MuniForward_Committed', - 'VTA_Next', - 'SCL130001_237_101_MAT_Int_Mod', - 'SonomaCounty_Transit_NoBuild2050', - 'SMART_Novato', - 'Xfare_update_2020', - 'ACTransit_Committed', - 'ferry_update_2019', - 'Napa_Solano_Updates_2020', - 'FBP_Beale_Transit_Only_Lane', - 'SamTrans_ECR_Rapid', - 'ALA150004_EastBay_BRT', - {'name':'FBP_SL_026_SolExpressBus', 'kwargs':{'MODELYEAR':'2020'}}], - }), - (2025, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2025'}}, - 'EXP_CC_050028_I680_SB_HOV_Completion', - 'EXP_101B1', - 'EXP_101B2', - 'EXP_680C1', - 'EXP_680F', - 'EXP_85D', - 'EXP_101C', - 'ALA150001_I680_SR84_Int_Wid', - 'ALA150043_Claremont_road_diet', - 'CC_070009_Slatten_Ranch_Rd_Extension', - 'SF_070004_Geary_BRT_Phase1', - 'SON070004_101_MarinSonNarrows_Phase2', - 'SOL110006_Jepson_1B_1C', - 'SCL190008_US101_DLC_Int_Imp', - 'I880_US101_AdaptiveRampMetering', - 'SOL070020_I80_I680_SR12_Int_1_2A', - 'FBP_NP_036_SR29_Imola_PNR', - 'ALA170052_Fruitvale_Ave_ped_improvements', - 'EXP_Blueprint_NoProject'], - 'trn':['SF_010028_Caltrain_Modernization', - 'REG090037_New_BART_Trains', - 'FBP_NP_036_SR29_Imola_PNR', - 'SOL070020_I80_I680_SR12_Int_1_2A'] - }), - (2030, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2030'}}, - 'EXP_Blueprint_NoProject'], - 'trn':['BART_NoProject', - 'SON090002_SMART_to_Windsor'], - }), - (2035, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2035'}}, - 'EXP_Blueprint_NoProject'], - 'trn':[] - }), - (2040, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2040'}}, - 'EXP_Blueprint_NoProject'], - 'trn':[] - }), - (2045, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2045'}}, - 'EXP_Blueprint_NoProject'], - 'trn':[] - }), - (2050, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2050'}}, - 'EXP_Blueprint_NoProject'], - 'trn':[] - }) -]) +COMMITTED_PROJECTS = collections.OrderedDict( + [ + ( + 2015, + { + "hwy": [ + "PROJ_attributes", # adds PROJ attributes to NODE and LINK + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2015"}, + }, + ], + "trn": [], + }, + ), + ( + 2020, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2020"}, + }, + { + "name": "EXP_237B", + "kwargs": {"FUTURE": "PBA50"}, + }, # todo: update this to support PBA50 + "EXP_580C", + "EXP_680D", + "EXP_880A", + "HOV_680F", + "SCL130001_237_101_MAT_Int_Mod", + "REG090003_SCLARA_FIP", + "ALA130005_Dougherty_road_widening", + "ALA130006_Dublin_Blvd_widening", + "ALA130014_7th_St_road_diet", + "ALA130026_Shattuck_Complete_Streets", + "ALA170049_Central_AVE_Safety_Improvements", + "ALA150004_EastBay_BRT", + "CC_130001_BaileyRd_SR4", + "CC_130046_I680_SR4_Int_Rec", + "CC_070035_I80_SPDamRd_Int_Phase1", + "CC_070011_Brentwood_Blvd_Widening", + "CC_070075_Kirker_Pass_Truck_Lane", + "CC_090019_Bollinger_Canyon_Widening", + "CC_130006_Concord_BART_road_diets", + "CC_170001_SanRamonValleyBlvd_Lane_Addition", + "MRN150009_San_Rafael_Bridge_Improvements", + "SF_070005_VanNess_BRT", + "SF_130011_2ndSt_Road_Diet", + "SF_Market_Street_Closure", + "SM_110047_SR92_ElCam_Ramp_Mod", + "SOL110005_Jepson_Van_to_Com", + "FBP_SL_042_Jepson_2A", + "SON070004_101_MarinSonNarrows_Phase1", + "ALA050014_SR84_Widening", + "ALA170011_BayBridge_HOV_Connectors", + "ALA150047_TelegraphAve_Complete_Streets", + "SM_110047_SR92_ElCam_Ramp_Mod", + "SCL190002_280_Foothill_improvement", + "SCL190006_101SB_offramp_improvement", + "I80_AdaptiveRampMetering", + "VAR170021_Freeway_Performance_I880", + "SonomaCounty_Transit_NoBuild2050", + "SF_MuniForward_Committed", + "FBP_MU_029_Broadway_Transit_Only_Lanes", + "EXP_Blueprint_NoProject", + "FBP_AL_067_Rte84Wide", + "FBP_AL_065_Bancroft_Bus_Only", + "FBP_SM_032_US101_Willow_Interchange", + ], + "trn": [ + "ALA050015_BART_to_WarmSprings", + "ACGo", + "CC_050025_EBart_to_Antioch", + "GGTransit_Committed", + "SCL110005_BART_to_Berryessa", + "SF_010015_Transbay_Terminal", + "SF_010037_Muni_Central_Subway", + "SOL030002_FairfieldVacaville_Stn", + "SON090002_SMART", + "SON090002_SMART_to_Larkspur", + "CC_070062_Richmond_Ferry", + "SF_MuniForward_Committed", + "VTA_Next", + "SCL130001_237_101_MAT_Int_Mod", + "SonomaCounty_Transit_NoBuild2050", + "SMART_Novato", + "Xfare_update_2020", + "ACTransit_Committed", + "ferry_update_2019", + "Napa_Solano_Updates_2020", + "FBP_Beale_Transit_Only_Lane", + "SamTrans_ECR_Rapid", + "ALA150004_EastBay_BRT", + { + "name": "FBP_SL_026_SolExpressBus", + "kwargs": {"MODELYEAR": "2020"}, + }, + ], + }, + ), + ( + 2025, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2025"}, + }, + "EXP_CC_050028_I680_SB_HOV_Completion", + "EXP_101B1", + "EXP_101B2", + "EXP_680C1", + "EXP_680F", + "EXP_85D", + "EXP_101C", + "ALA150001_I680_SR84_Int_Wid", + "ALA150043_Claremont_road_diet", + "CC_070009_Slatten_Ranch_Rd_Extension", + "SF_070004_Geary_BRT_Phase1", + "SON070004_101_MarinSonNarrows_Phase2", + "SOL110006_Jepson_1B_1C", + "SCL190008_US101_DLC_Int_Imp", + "I880_US101_AdaptiveRampMetering", + "SOL070020_I80_I680_SR12_Int_1_2A", + "FBP_NP_036_SR29_Imola_PNR", + "ALA170052_Fruitvale_Ave_ped_improvements", + "EXP_Blueprint_NoProject", + ], + "trn": [ + "SF_010028_Caltrain_Modernization", + "REG090037_New_BART_Trains", + "FBP_NP_036_SR29_Imola_PNR", + "SOL070020_I80_I680_SR12_Int_1_2A", + ], + }, + ), + ( + 2030, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2030"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": ["BART_NoProject", "SON090002_SMART_to_Windsor"], + }, + ), + ( + 2035, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2035"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": [], + }, + ), + ( + 2040, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2040"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": [], + }, + ), + ( + 2045, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2045"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": [], + }, + ), + ( + 2050, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2050"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": [], + }, + ), + ] +) ########################################################### # TIP projects -TIP_PROJECTS = collections.OrderedDict([ - (2015, {'hwy':[], - 'trn':[] - }), - (2020, {'hwy':[], - 'trn':[] - }), - (2025, {'hwy':[{'name':'RRSP_Alameda_Point_Transit_Improvements', 'tag':'PBA50_Blueprint'}, - 'MAJ_MTC050027_Berkeley_Ferry', - 'MAJ_WETA_Service_Frequency_Increase', - 'FBP_MU_041_Hovercraft_Pilot', - 'BP_Vision_Zero', - 'EXP_Blueprint', - 'MAJ_AC_Frequency_Improvement', - 'MRN050034_101_MarinSonNarrows_Phase2', - 'FBP_MU_044_SouthSF_Ferry_Serv_Incr', - 'FBP_MU_029_ACRapid_2025', - 'RRSP_E14_Mission_Corridor', - 'FBP_MR_026_NovatoWide', - 'FBP_NP_038_TSP_On_SR29', - 'FBP_NP_044_Soscol_Junction', - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7','kwargs':{'PHASE':"'2B'"}}, - {'name':'EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2025'}, 'variants_exclude':['Alt1', 'NextGenFwy']}, - {'name':'EIR1_EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2025'}, 'variants_include':['Alt1']}, - {'name':'FBP_CC_15_23rd_St_BRT', 'kwargs':{'MODELYEAR':'2025'}}, - 'FBP_CC_057_LoneTreeWide', - 'FBP_CC_067_WillowPassWide', - 'FBP_CC_065_LaurelWide', - 'FBP_SN_017_Arata_Int', - 'FBP_CC_017_Brentwood_Intermodal', - 'FBP_SF_030_Balboa_Park_Area_2', - 'EXP_Blueprint', - 'FBP_AL_039_I580_Interchange_Imps', - 'FBP_CC_056_LaurelExtension', - 'FBP_SC_084_10th_BridgeWide', - 'FBP_SL_053_PeabodyWide', - 'FBP_SC_073_BlossomHill_101Wide', - 'FBP_SC_082_US101_25_Interchange', - 'FBP_CC_045_SanPabloDam_Interchange_Phase2', - 'FBP_CC_030_OakleyAmtrak', - 'FBP_SM_034_Route92_ElCamino_Interchange', - 'FBP_SL_019_BeniciaRoad_Diet', - 'FBP_SL_023_WestTexasRoad_Diet', - 'FBP_SN_012_PetalumaBlvd_Diet', - 'SF_Park_Presidio_HOV_Pilot', - 'SF_Lombard_HOV_Pilot', - {'name':'EIR2_Val_Link_ExpressBus', 'variants_include':['Alt2']}, - {'name':'EIR2_ReXBlue', 'variants_include':['Alt2']}, - 'FBP_SC_072_US101_Trimble_Interchange', - {'name':'EXP_uncommitted_noAllLaneTolling', 'kwargs':{'MODELYEAR':'2025'}, 'variants_include':['NextGenFwy']}], - 'trn':['FBP_AL_001_NewarkFremPDA', - {'name':'FBP_MU_059_ACTransbay_Freq_Incr', 'variants_exclude':['Alt2']}, - 'MAJ_AC_Frequency_Improvement', - {'name':'RRSP_Alameda_Point_Transit_Improvements', 'tag':'PBA50_Blueprint'}, - 'MAJ_MTC050027_Berkeley_Ferry', - 'MAJ_WETA_Service_Frequency_Increase', - {'name':'FBP_MU_046_ACE_Freq_Inc', 'kwargs':{'MODELYEAR':'2025'}}, - 'FBP_MU_041_Hovercraft_Pilot', - 'FBP_MU_049_Caltrain_6TPHPD', - {'name':'FBP_MU_060_ReX_Blue', 'variants_exclude':['Alt2']}, - {'name':'EIR2_ReXBlue', 'variants_include':['Alt2']}, - 'FBP_MU_044_SouthSF_Ferry_Serv_Incr', - 'GGT_Service_Imp', - 'FBP_MU_029_ACRapid_2025', - 'RRSP_E14_Mission_Corridor', - 'FBP_NP_044_Soscol_Junction', - 'MAJ_Alameda_Point_SF_Ferry', - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7','kwargs':{'PHASE':"'2B'"}}, - {'name':'FBP_CC_15_23rd_St_BRT', 'kwargs':{'MODELYEAR':'2025'}}, - 'FBP_CC_030_OakleyAmtrak', - 'MAJ_Sonoma_Frequency_Increase', - {'name':'EIR1_Freq_Boosts', 'kwargs':{'MODELYEAR':'2025'}, 'variants_include':['Alt1']}, - {'name':'EIR2_HRA_Freq_Incr', 'kwargs':{'MODELYEAR':'2025'}, 'variants_include':['Alt2']}, - {'name':'EIR2_PDA_Freq_Incr', 'kwargs':{'MODELYEAR':'2025'}, 'variants_include':['Alt2']}, - {'name':'EIR2_Val_Link_ExpressBus', 'variants_include':['Alt2']}] - }), - (2030, {'hwy':['MAJ_SanPablo_BRT', - 'SF_070027_Yerba_Buena_Ramp_Imp', - 'CC_170061_Bus_On_Shoulder_680BRT', - 'BP_Vision_Zero', - {'name':'FBP_AL_021_South_Bay_Connect', 'variants_exclude':['Alt2']}, - 'FBP_MU_044_Richmond_Ferry_Serv_Incr', - 'MAJ_REG090037_BART_Core_Cap', - {'name':'Transform_Valley_Link', 'variants_exclude':['Alt2']}, - 'FBP_NP_040_VINE_Exp_Bus_Enhancements', - 'FBP_AL_045_Oak_Ala_Access_Pr', - # 'FBP_MR_018_US101_BOS', - 'FBP_CC_036_I80_ExpBus_Impr', - {'name':'FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5','kwargs':{'PHASE':"'4'"}, 'variants_exclude':['Alt1']}, - 'FBP_CC_021_Ant_Mart_Herc_Ferry', - {'name':'EIR1_EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['Alt1']}, - 'FBP_SC_104_OaklandWide', - {'name':'FBP_CC_15_23rd_St_BRT', 'kwargs':{'MODELYEAR':'2030'}}, - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2030'}}, - {'name':'MAJ_Bay_Area_Forward_all', 'kwargs':{'MODELYEAR':'2030'}}, - {'name':'BP_Tolls_On_Congested_Freeways_2030', 'variants_exclude':['NextGenFwy']}, - {'name':'EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2030'}, 'variants_exclude':['Alt1', 'NextGenFwy']}, - 'FBP_CC_064_CaminoTassajaraWide', - 'FBP_CC_066_CypressWide', - 'FBP_SC_059_SR237EBWide', - 'FBP_AL_064_UnionCityWide', - 'FBP_SC_074_US101_BuenaVista_Int', - 'EXP_Blueprint', - 'FBP_SC_054_SR17_Corridor_Relief', - 'FBP_AL_043_A_StreetWide', - 'FBP_CC_061_062_West_Leland_Ext_Phases1_2', - 'FBP_SM_042_Hwy1_ManorDrive', - 'FBP_SL_042_Jepson_2B_2C', - 'FBP_CC_024_Oakley_PNR_Tri_Delta', - 'FBP_SC_083_US101_Zanker_Skyport_Interchange', - 'FBP_SL_022_SonomaBlvd_Diet', - 'FBP_SM_027_US101_92', - 'FBP_SM_007_ElCamino_CompleteStreets', - 'FBP_AL_044_I880_Whipple_Imps', - 'ALA110002_I880_Industrial_Interchange', - 'FBP_AL_051_7St_Grade_Sep_West', - 'FBP_AL_055_DubBlvd_NCanyons_Ext', - 'FBP_AL_062_TassajaraWide', - 'ALA090020_I880_Industrial_Parkway_AuxLanes', - 'FBP_CC_063_BrentwoodWide', - 'FBP_CC_054_CrowCanyonWide', - 'MAJ_Geary_BRT_Phase2', - 'MAJ_SF_Congestion_Pricing', - 'MAJ_MissionBay_SF_Ferry', - 'FBP_SM_033_US101_Holly_Interchange', - 'FBP_SM_035_Peninsula_101_OnOffRamps', - 'STIP_ProduceAve', - 'MAJ_SCL050009_VTA_Eastridge_Extension', - 'FBP_SC_103_MontagueWide', - 'FBP_SL_033_FairgroundsWide', - 'ALA_Fallon_Realign', - 'ALA210027_180_Powell_Transit_Access', - 'ALA210028_I80_HOV_Bus_Lane_Ext', - 'SCL210026_Julian_James_Conversion', - {'name':'FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5', 'kwargs':{'PHASE':"'1'"}, 'variants_exclude':['Alt1']}, - {'name':'FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5', 'kwargs':{'PHASE':"'2'"}, 'variants_exclude':['Alt1']}, - {'name':'FBP_CC_050_SR4_Operation_Improvements_EB', 'variants_exclude':['Alt1']}, - {'name':'FBP_CC_051_SR4_Operation_Improvements_WB', 'variants_exclude':['Alt1']}, - {'name':'EXP_uncommitted_noAllLaneTolling', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['NextGenFwy']}, - {'name':'Transform_SR37_Widening_Interim', 'variants_exclude':['Alt1']}], - 'trn':['BP_PDA_Transit_Enhancements', - 'SF_070027_Yerba_Buena_Ramp_Imp', - {'name':'FBP_MU_046_ACE_Freq_Inc', 'kwargs':{'MODELYEAR':'2030'}}, - 'MAJ_BRT030001_BART_to_SanJose', - 'BART_Irvington_Infill', - 'MAJ_REG090037_BART_Core_Cap', - {'name':'FBP_AL_021_South_Bay_Connect', 'variants_exclude':['Alt2']}, - 'FBP_MU_049_Caltrain_8TPHPD', - 'FBP_MU_061_ReX_Green', - 'MAJ_SanPablo_BRT', - 'FBP_MU_044_Richmond_Ferry_Serv_Incr', - {'name':'Transform_Valley_Link', 'variants_exclude':['Alt2']}, - 'FBP_SF_028_SF_Express_Bus_On_Exp_Lanes', - {'name':'MAJ_SF_050002_Caltrain_Ext_TransbayTerminal', 'variants_exclude':['Alt2']}, - 'FBP_SF_024_Historic_Streetcar_Ext', - 'FBP_MuniForward_Uncommitted_Rail', - 'FBP_CC_036_I80_ExpBus_Impr', - 'FBP_CC_021_Ant_Mart_Herc_Ferry', - 'FBP_AL_045_Oak_Ala_Access_Pr', - 'FBP_CC_028_Hercules_Station', - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2030'}}, - {'name':'FBP_CC_15_23rd_St_BRT', 'kwargs':{'MODELYEAR':'2030'}}, - 'FBP_CC_024_Oakley_PNR_Tri_Delta', - 'MAJ_Geary_BRT_Phase2', - 'MAJ_SF_Congestion_Pricing', - 'MAJ_MissionBay_SF_Ferry', - 'MAJ_RedwoodCity_SF_Ferry', - 'MAJ_SCL050009_VTA_Eastridge_Extension', - {'name':'FBP_SM_020_Regional_Express_Buses', 'kwargs':{'PHASE':"'Phase1_4Routes'"}}, - {'name':'EIR1_Freq_Boosts', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['Alt1']}, - {'name':'EIR2_HRA_Freq_Incr', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['Alt2']}, - {'name':'EIR2_PDA_Freq_Incr', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['Alt2']}, - {'name':'EIR2_Fix_Alt2', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['Alt2']}, - {'name':'Transform_SR37_Widening_Interim', 'variants_exclude':['Alt1']}, - {'name':'SON090002_SMART_NorthPetaluma', 'variants_exclude':['Baseline']}] - }), - (2035, {'hwy':['MAJ_MuniForward_Uncommitted', - 'MAJ_Treasure_Island_Congestion_Pricing', - 'BP_Vision_Zero', - 'RRSP_East_West_Connector', - 'Transform_I680_Multimodal_Imp', - 'FBP_SM_022_I380_Widening', - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'3'"}, 'variants_exclude':['Alt1']}, - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'4'"}, 'variants_exclude':['Alt1']}, - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'5'"}, 'variants_exclude':['Alt1']}, - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2035'}}, - {'name':'EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2035'}, 'variants_exclude':['Alt1', 'NextGenFwy']}, - {'name':'EIR1_EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2035'}, 'variants_include':['Alt1']}, - {'name':'MAJ_Bay_Area_Forward_all', 'kwargs':{'MODELYEAR':'2035'}}, - {'name':'BP_Tolls_On_Congested_Freeways_2035', 'variants_exclude':['NextGenFwy']}, - 'FBP_AL_076_TelegraphDiet', - 'FBP_SN_018_Cotati_101_RailroadAve_Impr', - 'FBP_NP_079_Trower_Ext', - 'EXP_Blueprint', - {'name':'EIR1_No_SR37', 'variants_include':['Alt1']}, - {'name':'EXP_uncommitted_noAllLaneTolling', 'kwargs':{'MODELYEAR':'2035'}, 'variants_include':['NextGenFwy']}], - 'trn':['MAJ_MuniForward_Uncommitted', - 'RRSP_South_East_Waterfront_Transit_Imp', - 'FBP_MU_062_ReX_Red', - 'Transform_I680_Multimodal_Imp', - 'Transform_SeamlessTransit', - 'MAJ_Treasure_Island_Congestion_Pricing', - 'RRSP_East_West_Connector', - 'MAJ_Treasure_Island_Ferry', - 'FBP_NP_079_Trower_Ext', - {'name':'FBP_SM_020_Regional_Express_Buses', 'kwargs':{'PHASE':"'Phase2_2Routes'"}}, - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'3'"}, 'variants_exclude':['Alt1']}, - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'4'"}, 'variants_exclude':['Alt1']}, - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2035'}}, - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'5'"}, 'variants_exclude':['Alt1']}, - {'name':'FBP_SL_026_SolExpressBus', 'kwargs':{'MODELYEAR':'2035'}}, - 'FBP_SL_020_MilitaryWest_Diet', - {'name':'EIR1_Freq_Boosts', 'kwargs':{'MODELYEAR':'2035'}, 'variants_include':['Alt1']}, - {'name':'EIR2_VTA_LRT_Orange', 'variants_include':['Alt2']}, - {'name':'EIR2_Fix_Alt2', 'kwargs':{'MODELYEAR':'2035'}, 'variants_include':['Alt2']}, - {'name':'EIR1_No_SR37', 'variants_include':['Alt1']}] - }), - (2040, {'hwy':['BP_Vision_Zero', - 'FBP_SC_050_I680_Montague_Int_Imp', - 'FBP_MU_029_ACRapid_2040', - 'FBP_NP_074_SoscolWide', - 'FBP_CC_059_PittAntiochWide', - {'name':'FBP_CC_051_SR4_Operation_Improvements_WB', 'variants_exclude':['Alt1']}, - 'FBP_CC_037_680_AuxLanes', - 'RRSP_EC_Cap_Imp_ECR_Bus', - {'name':'MAJ_SR_239', 'variants_exclude':['Alt1']}, - 'FBP_NP_033_Napa_PNR_Lots', - 'FBP_CC_018_BRT_Brentwood', - 'FBP_SC_043_I280_Mainline_Impr', - 'MAJ_ElCaminoReal_BRT', - 'FBP_AL_042_I680_Stoneridge_Widening', - {'name':'FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5', 'kwargs':{'PHASE':"'5'"},'variants_exclude':['Alt1']}, - {'name':'EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2040'}, 'variants_exclude':['Alt1', 'NextGenFwy']}, - {'name':'EIR1_EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2040'}, 'variants_include':['Alt1']}, - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2040'}}, - {'name':'MAJ_Bay_Area_Forward_all', 'kwargs':{'MODELYEAR':'2040'}}, - 'FBP_SC_105_SanTomasWide', - 'FBP_SC_102_CalaverasWide', - 'FBP_CC_039_Eastbound24Wide', - {'name':'FBP_MU_064_SR37_LongTerm', 'variants_exclude':['Alt1']}, - 'FBP_SC_094_LawrenceWide', - 'FBP_NP_066_Newell_Dr', - 'EXP_Blueprint', - 'FBP_AL_052_AutoMallWide', - 'FBP_CC_038_SR242_Clayton_OnOffRamps', - 'FBP_SC_047_I280_Winchester_OffRamp', - 'FBP_SC_076_US101_Taylor_Interchange', - 'FBP_NP_051_Airport_Junction', - 'FBP_SC_101_BrokawBridgeWide', - 'FBP_SC_081_US101_SR237', - 'FBP_SC_088_Envision_Expwy', - 'FBP_SC_039_SR237WBWide', - {'name':'FBP_MR_021_101_580_Direct_Connector', 'variants_exclude':['Alt1']}, - {'name':'FBP_MU_056_Dumbarton_GRT', 'variants_exclude':['Alt2']}, - {'name':'EIR2_Val_Link_ExpressBus', 'kwargs':{'action':"'revert'"}, 'variants_include':['Alt2']}, - {'name':'Transform_Valley_Link', 'variants_include':['Alt2']}, - {'name':'FBP_AL_021_South_Bay_Connect', 'variants_include':['Alt2']}, - {'name':'Transform_AC_Transbay_Improvements', 'variants_include':['Alt2']}, - 'FBP_SC_042_I280_Downtown_Access_Improvements', - {'name':'EXP_uncommitted_noAllLaneTolling', 'kwargs':{'MODELYEAR':'2040'}, 'variants_include':['NextGenFwy']}], - 'trn':[{'name':'FBP_MU_046_ACE_Freq_Inc', 'kwargs':{'MODELYEAR':'2040'}}, - 'MAJ_Vasona_LRT_Extension', - 'FBP_MU_029_ACRapid_2040', - 'RRSP_EC_Cap_Imp_ECR_Bus', - 'MAJ_SJC_People_Mover', - 'FBP_NP_028_NapaVineRegRoutesFrequency', - 'FBP_NP_034_NapaVineRegExpServiceHrs', - 'FBP_NP_029_NapaVineLocExpServiceHrs', - 'FBP_NP_033_Napa_PNR_Lots', - 'FBP_SC_043_I280_Mainline_Impr', - 'FBP_CC_018_BRT_Brentwood', - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2040'}}, - 'MAJ_ElCaminoReal_BRT', - {'name':'FBP_MU_064_SR37_LongTerm', 'variants_exclude':['Alt1']}, - 'FBP_NP_051_Airport_Junction', - {'name':'FBP_MU_056_Dumbarton_GRT', 'variants_exclude':['Alt2']}, - 'FBP_SC_088_Envision_Expwy', - {'name':'HSR', 'variants_exclude':['Alt2']}, - {'name':'MAJ_SF_050002_Caltrain_Ext_TransbayTerminal', 'variants_include':['Alt2']}, - {'name':'EIR2_Val_Link_ExpressBus', 'kwargs':{'action':"'revert'"}, 'variants_include':['Alt2']}, - {'name':'Transform_Valley_Link', 'variants_include':['Alt2']}, - {'name':'FBP_AL_021_South_Bay_Connect', 'variants_include':['Alt2']}, - {'name':'Transform_AC_Transbay_Improvements', 'variants_include':['Alt2']}, - {'name':'EIR2_ReXGreen', 'variants_include':['Alt2']}, - 'FBP_CC_019_CCCTA_Freq_Increase', - {'name':'EIR2_Fix_Alt2', 'kwargs':{'MODELYEAR':'2040'}, 'variants_include':['Alt2']}] - }), - (2045, {'hwy':['BP_Vision_Zero', - {'name':'EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2045'}, 'variants_exclude':['Alt1', 'NextGenFwy']}, - {'name':'EIR1_EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2045'}, 'variants_include':['Alt1']}, - {'name':'MAJ_Bay_Area_Forward_all', 'kwargs':{'MODELYEAR':'2045'}}, - {'name':'FBP_AL_048_SR262_Phase1', 'variants_exclude':['Alt1']}, - 'FBP_NP_045_SR29_Gateway_Impr', - 'EXP_Blueprint', - {'name':'EXP_uncommitted_noAllLaneTolling', 'kwargs':{'MODELYEAR':'2045'}, 'variants_include':['NextGenFwy']}], - 'trn':[{'name':'FBP_MU_046_ACE_Freq_Inc', 'kwargs':{'MODELYEAR':'2045'}}, - 'FBP_SC_106_VTA_LRT_Modernization'] - }), - (2050, {'hwy':['BP_Vision_Zero', - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'6'"}, 'variants_exclude':['Alt1']}, - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'7'"}, 'variants_exclude':['Alt1']}, - 'FBP_SC_028_Stevens_Creek_LRT', - {'name':'MAJ_Bay_Area_Forward_all', 'kwargs':{'MODELYEAR':'2050'}}, - 'EXP_Blueprint', - 'FBP_SC_041_Envision_Highway_Minor', - 'STIP_ITS_SM', - {'name':'BP_Transbay_Crossing', 'variants_exclude':['Alt2']}], - 'trn':[{'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'6'"}, 'variants_exclude':['Alt1']}, - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'7'"}, 'variants_exclude':['Alt1']}, - 'FBP_SC_028_Stevens_Creek_LRT', - {'name':'BP_Transbay_Crossing', 'variants_exclude':['Alt2']}] - }) - ]) - +TIP_PROJECTS = collections.OrderedDict( + [ + (2015, {"hwy": [], "trn": []}), + (2020, {"hwy": [], "trn": []}), + ( + 2025, + { + "hwy": [ + { + "name": "RRSP_Alameda_Point_Transit_Improvements", + "tag": "PBA50_Blueprint", + }, + "MAJ_MTC050027_Berkeley_Ferry", + "MAJ_WETA_Service_Frequency_Increase", + "FBP_MU_041_Hovercraft_Pilot", + "BP_Vision_Zero", + "EXP_Blueprint", + "MAJ_AC_Frequency_Improvement", + "MRN050034_101_MarinSonNarrows_Phase2", + "FBP_MU_044_SouthSF_Ferry_Serv_Incr", + "FBP_MU_029_ACRapid_2025", + "RRSP_E14_Mission_Corridor", + "FBP_MR_026_NovatoWide", + "FBP_NP_038_TSP_On_SR29", + "FBP_NP_044_Soscol_Junction", + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'2B'"}, + }, + { + "name": "EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2025"}, + "variants_exclude": ["Alt1", "NextGenFwy"], + }, + { + "name": "EIR1_EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2025"}, + "variants_include": ["Alt1"], + }, + {"name": "FBP_CC_15_23rd_St_BRT", "kwargs": {"MODELYEAR": "2025"}}, + "FBP_CC_057_LoneTreeWide", + "FBP_CC_067_WillowPassWide", + "FBP_CC_065_LaurelWide", + "FBP_SN_017_Arata_Int", + "FBP_CC_017_Brentwood_Intermodal", + "FBP_SF_030_Balboa_Park_Area_2", + "EXP_Blueprint", + "FBP_AL_039_I580_Interchange_Imps", + "FBP_CC_056_LaurelExtension", + "FBP_SC_084_10th_BridgeWide", + "FBP_SL_053_PeabodyWide", + "FBP_SC_073_BlossomHill_101Wide", + "FBP_SC_082_US101_25_Interchange", + "FBP_CC_045_SanPabloDam_Interchange_Phase2", + "FBP_CC_030_OakleyAmtrak", + "FBP_SM_034_Route92_ElCamino_Interchange", + "FBP_SL_019_BeniciaRoad_Diet", + "FBP_SL_023_WestTexasRoad_Diet", + "FBP_SN_012_PetalumaBlvd_Diet", + "SF_Park_Presidio_HOV_Pilot", + "SF_Lombard_HOV_Pilot", + {"name": "EIR2_Val_Link_ExpressBus", "variants_include": ["Alt2"]}, + {"name": "EIR2_ReXBlue", "variants_include": ["Alt2"]}, + "FBP_SC_072_US101_Trimble_Interchange", + { + "name": "EXP_uncommitted_noAllLaneTolling", + "kwargs": {"MODELYEAR": "2025"}, + "variants_include": ["NextGenFwy"], + }, + ], + "trn": [ + "FBP_AL_001_NewarkFremPDA", + { + "name": "FBP_MU_059_ACTransbay_Freq_Incr", + "variants_exclude": ["Alt2"], + }, + "MAJ_AC_Frequency_Improvement", + { + "name": "RRSP_Alameda_Point_Transit_Improvements", + "tag": "PBA50_Blueprint", + }, + "MAJ_MTC050027_Berkeley_Ferry", + "MAJ_WETA_Service_Frequency_Increase", + { + "name": "FBP_MU_046_ACE_Freq_Inc", + "kwargs": {"MODELYEAR": "2025"}, + }, + "FBP_MU_041_Hovercraft_Pilot", + "FBP_MU_049_Caltrain_6TPHPD", + {"name": "FBP_MU_060_ReX_Blue", "variants_exclude": ["Alt2"]}, + {"name": "EIR2_ReXBlue", "variants_include": ["Alt2"]}, + "FBP_MU_044_SouthSF_Ferry_Serv_Incr", + "GGT_Service_Imp", + "FBP_MU_029_ACRapid_2025", + "RRSP_E14_Mission_Corridor", + "FBP_NP_044_Soscol_Junction", + "MAJ_Alameda_Point_SF_Ferry", + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'2B'"}, + }, + {"name": "FBP_CC_15_23rd_St_BRT", "kwargs": {"MODELYEAR": "2025"}}, + "FBP_CC_030_OakleyAmtrak", + "MAJ_Sonoma_Frequency_Increase", + { + "name": "EIR1_Freq_Boosts", + "kwargs": {"MODELYEAR": "2025"}, + "variants_include": ["Alt1"], + }, + { + "name": "EIR2_HRA_Freq_Incr", + "kwargs": {"MODELYEAR": "2025"}, + "variants_include": ["Alt2"], + }, + { + "name": "EIR2_PDA_Freq_Incr", + "kwargs": {"MODELYEAR": "2025"}, + "variants_include": ["Alt2"], + }, + {"name": "EIR2_Val_Link_ExpressBus", "variants_include": ["Alt2"]}, + ], + }, + ), + ( + 2030, + { + "hwy": [ + "MAJ_SanPablo_BRT", + "SF_070027_Yerba_Buena_Ramp_Imp", + "CC_170061_Bus_On_Shoulder_680BRT", + "BP_Vision_Zero", + { + "name": "FBP_AL_021_South_Bay_Connect", + "variants_exclude": ["Alt2"], + }, + "FBP_MU_044_Richmond_Ferry_Serv_Incr", + "MAJ_REG090037_BART_Core_Cap", + {"name": "Transform_Valley_Link", "variants_exclude": ["Alt2"]}, + "FBP_NP_040_VINE_Exp_Bus_Enhancements", + "FBP_AL_045_Oak_Ala_Access_Pr", + # 'FBP_MR_018_US101_BOS', + "FBP_CC_036_I80_ExpBus_Impr", + { + "name": "FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5", + "kwargs": {"PHASE": "'4'"}, + "variants_exclude": ["Alt1"], + }, + "FBP_CC_021_Ant_Mart_Herc_Ferry", + { + "name": "EIR1_EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["Alt1"], + }, + "FBP_SC_104_OaklandWide", + {"name": "FBP_CC_15_23rd_St_BRT", "kwargs": {"MODELYEAR": "2030"}}, + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2030"}, + }, + { + "name": "MAJ_Bay_Area_Forward_all", + "kwargs": {"MODELYEAR": "2030"}, + }, + { + "name": "BP_Tolls_On_Congested_Freeways_2030", + "variants_exclude": ["NextGenFwy"], + }, + { + "name": "EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2030"}, + "variants_exclude": ["Alt1", "NextGenFwy"], + }, + "FBP_CC_064_CaminoTassajaraWide", + "FBP_CC_066_CypressWide", + "FBP_SC_059_SR237EBWide", + "FBP_AL_064_UnionCityWide", + "FBP_SC_074_US101_BuenaVista_Int", + "EXP_Blueprint", + "FBP_SC_054_SR17_Corridor_Relief", + "FBP_AL_043_A_StreetWide", + "FBP_CC_061_062_West_Leland_Ext_Phases1_2", + "FBP_SM_042_Hwy1_ManorDrive", + "FBP_SL_042_Jepson_2B_2C", + "FBP_CC_024_Oakley_PNR_Tri_Delta", + "FBP_SC_083_US101_Zanker_Skyport_Interchange", + "FBP_SL_022_SonomaBlvd_Diet", + "FBP_SM_027_US101_92", + "FBP_SM_007_ElCamino_CompleteStreets", + "FBP_AL_044_I880_Whipple_Imps", + "ALA110002_I880_Industrial_Interchange", + "FBP_AL_051_7St_Grade_Sep_West", + "FBP_AL_055_DubBlvd_NCanyons_Ext", + "FBP_AL_062_TassajaraWide", + "ALA090020_I880_Industrial_Parkway_AuxLanes", + "FBP_CC_063_BrentwoodWide", + "FBP_CC_054_CrowCanyonWide", + "MAJ_Geary_BRT_Phase2", + "MAJ_SF_Congestion_Pricing", + "MAJ_MissionBay_SF_Ferry", + "FBP_SM_033_US101_Holly_Interchange", + "FBP_SM_035_Peninsula_101_OnOffRamps", + "STIP_ProduceAve", + "MAJ_SCL050009_VTA_Eastridge_Extension", + "FBP_SC_103_MontagueWide", + "FBP_SL_033_FairgroundsWide", + "ALA_Fallon_Realign", + "ALA210027_180_Powell_Transit_Access", + "ALA210028_I80_HOV_Bus_Lane_Ext", + "SCL210026_Julian_James_Conversion", + { + "name": "FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5", + "kwargs": {"PHASE": "'1'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5", + "kwargs": {"PHASE": "'2'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "FBP_CC_050_SR4_Operation_Improvements_EB", + "variants_exclude": ["Alt1"], + }, + { + "name": "FBP_CC_051_SR4_Operation_Improvements_WB", + "variants_exclude": ["Alt1"], + }, + { + "name": "EXP_uncommitted_noAllLaneTolling", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["NextGenFwy"], + }, + { + "name": "Transform_SR37_Widening_Interim", + "variants_exclude": ["Alt1"], + }, + ], + "trn": [ + "BP_PDA_Transit_Enhancements", + "SF_070027_Yerba_Buena_Ramp_Imp", + { + "name": "FBP_MU_046_ACE_Freq_Inc", + "kwargs": {"MODELYEAR": "2030"}, + }, + "MAJ_BRT030001_BART_to_SanJose", + "BART_Irvington_Infill", + "MAJ_REG090037_BART_Core_Cap", + { + "name": "FBP_AL_021_South_Bay_Connect", + "variants_exclude": ["Alt2"], + }, + "FBP_MU_049_Caltrain_8TPHPD", + "FBP_MU_061_ReX_Green", + "MAJ_SanPablo_BRT", + "FBP_MU_044_Richmond_Ferry_Serv_Incr", + {"name": "Transform_Valley_Link", "variants_exclude": ["Alt2"]}, + "FBP_SF_028_SF_Express_Bus_On_Exp_Lanes", + { + "name": "MAJ_SF_050002_Caltrain_Ext_TransbayTerminal", + "variants_exclude": ["Alt2"], + }, + "FBP_SF_024_Historic_Streetcar_Ext", + "FBP_MuniForward_Uncommitted_Rail", + "FBP_CC_036_I80_ExpBus_Impr", + "FBP_CC_021_Ant_Mart_Herc_Ferry", + "FBP_AL_045_Oak_Ala_Access_Pr", + "FBP_CC_028_Hercules_Station", + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2030"}, + }, + {"name": "FBP_CC_15_23rd_St_BRT", "kwargs": {"MODELYEAR": "2030"}}, + "FBP_CC_024_Oakley_PNR_Tri_Delta", + "MAJ_Geary_BRT_Phase2", + "MAJ_SF_Congestion_Pricing", + "MAJ_MissionBay_SF_Ferry", + "MAJ_RedwoodCity_SF_Ferry", + "MAJ_SCL050009_VTA_Eastridge_Extension", + { + "name": "FBP_SM_020_Regional_Express_Buses", + "kwargs": {"PHASE": "'Phase1_4Routes'"}, + }, + { + "name": "EIR1_Freq_Boosts", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["Alt1"], + }, + { + "name": "EIR2_HRA_Freq_Incr", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["Alt2"], + }, + { + "name": "EIR2_PDA_Freq_Incr", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["Alt2"], + }, + { + "name": "EIR2_Fix_Alt2", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["Alt2"], + }, + { + "name": "Transform_SR37_Widening_Interim", + "variants_exclude": ["Alt1"], + }, + { + "name": "SON090002_SMART_NorthPetaluma", + "variants_exclude": ["Baseline"], + }, + ], + }, + ), + ( + 2035, + { + "hwy": [ + "MAJ_MuniForward_Uncommitted", + "MAJ_Treasure_Island_Congestion_Pricing", + "BP_Vision_Zero", + "RRSP_East_West_Connector", + "Transform_I680_Multimodal_Imp", + "FBP_SM_022_I380_Widening", + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'3'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'4'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'5'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2035"}, + }, + { + "name": "EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2035"}, + "variants_exclude": ["Alt1", "NextGenFwy"], + }, + { + "name": "EIR1_EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2035"}, + "variants_include": ["Alt1"], + }, + { + "name": "MAJ_Bay_Area_Forward_all", + "kwargs": {"MODELYEAR": "2035"}, + }, + { + "name": "BP_Tolls_On_Congested_Freeways_2035", + "variants_exclude": ["NextGenFwy"], + }, + "FBP_AL_076_TelegraphDiet", + "FBP_SN_018_Cotati_101_RailroadAve_Impr", + "FBP_NP_079_Trower_Ext", + "EXP_Blueprint", + {"name": "EIR1_No_SR37", "variants_include": ["Alt1"]}, + { + "name": "EXP_uncommitted_noAllLaneTolling", + "kwargs": {"MODELYEAR": "2035"}, + "variants_include": ["NextGenFwy"], + }, + ], + "trn": [ + "MAJ_MuniForward_Uncommitted", + "RRSP_South_East_Waterfront_Transit_Imp", + "FBP_MU_062_ReX_Red", + "Transform_I680_Multimodal_Imp", + "Transform_SeamlessTransit", + "MAJ_Treasure_Island_Congestion_Pricing", + "RRSP_East_West_Connector", + "MAJ_Treasure_Island_Ferry", + "FBP_NP_079_Trower_Ext", + { + "name": "FBP_SM_020_Regional_Express_Buses", + "kwargs": {"PHASE": "'Phase2_2Routes'"}, + }, + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'3'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'4'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2035"}, + }, + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'5'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "FBP_SL_026_SolExpressBus", + "kwargs": {"MODELYEAR": "2035"}, + }, + "FBP_SL_020_MilitaryWest_Diet", + { + "name": "EIR1_Freq_Boosts", + "kwargs": {"MODELYEAR": "2035"}, + "variants_include": ["Alt1"], + }, + {"name": "EIR2_VTA_LRT_Orange", "variants_include": ["Alt2"]}, + { + "name": "EIR2_Fix_Alt2", + "kwargs": {"MODELYEAR": "2035"}, + "variants_include": ["Alt2"], + }, + {"name": "EIR1_No_SR37", "variants_include": ["Alt1"]}, + ], + }, + ), + ( + 2040, + { + "hwy": [ + "BP_Vision_Zero", + "FBP_SC_050_I680_Montague_Int_Imp", + "FBP_MU_029_ACRapid_2040", + "FBP_NP_074_SoscolWide", + "FBP_CC_059_PittAntiochWide", + { + "name": "FBP_CC_051_SR4_Operation_Improvements_WB", + "variants_exclude": ["Alt1"], + }, + "FBP_CC_037_680_AuxLanes", + "RRSP_EC_Cap_Imp_ECR_Bus", + {"name": "MAJ_SR_239", "variants_exclude": ["Alt1"]}, + "FBP_NP_033_Napa_PNR_Lots", + "FBP_CC_018_BRT_Brentwood", + "FBP_SC_043_I280_Mainline_Impr", + "MAJ_ElCaminoReal_BRT", + "FBP_AL_042_I680_Stoneridge_Widening", + { + "name": "FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5", + "kwargs": {"PHASE": "'5'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2040"}, + "variants_exclude": ["Alt1", "NextGenFwy"], + }, + { + "name": "EIR1_EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2040"}, + "variants_include": ["Alt1"], + }, + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2040"}, + }, + { + "name": "MAJ_Bay_Area_Forward_all", + "kwargs": {"MODELYEAR": "2040"}, + }, + "FBP_SC_105_SanTomasWide", + "FBP_SC_102_CalaverasWide", + "FBP_CC_039_Eastbound24Wide", + {"name": "FBP_MU_064_SR37_LongTerm", "variants_exclude": ["Alt1"]}, + "FBP_SC_094_LawrenceWide", + "FBP_NP_066_Newell_Dr", + "EXP_Blueprint", + "FBP_AL_052_AutoMallWide", + "FBP_CC_038_SR242_Clayton_OnOffRamps", + "FBP_SC_047_I280_Winchester_OffRamp", + "FBP_SC_076_US101_Taylor_Interchange", + "FBP_NP_051_Airport_Junction", + "FBP_SC_101_BrokawBridgeWide", + "FBP_SC_081_US101_SR237", + "FBP_SC_088_Envision_Expwy", + "FBP_SC_039_SR237WBWide", + { + "name": "FBP_MR_021_101_580_Direct_Connector", + "variants_exclude": ["Alt1"], + }, + {"name": "FBP_MU_056_Dumbarton_GRT", "variants_exclude": ["Alt2"]}, + { + "name": "EIR2_Val_Link_ExpressBus", + "kwargs": {"action": "'revert'"}, + "variants_include": ["Alt2"], + }, + {"name": "Transform_Valley_Link", "variants_include": ["Alt2"]}, + { + "name": "FBP_AL_021_South_Bay_Connect", + "variants_include": ["Alt2"], + }, + { + "name": "Transform_AC_Transbay_Improvements", + "variants_include": ["Alt2"], + }, + "FBP_SC_042_I280_Downtown_Access_Improvements", + { + "name": "EXP_uncommitted_noAllLaneTolling", + "kwargs": {"MODELYEAR": "2040"}, + "variants_include": ["NextGenFwy"], + }, + ], + "trn": [ + { + "name": "FBP_MU_046_ACE_Freq_Inc", + "kwargs": {"MODELYEAR": "2040"}, + }, + "MAJ_Vasona_LRT_Extension", + "FBP_MU_029_ACRapid_2040", + "RRSP_EC_Cap_Imp_ECR_Bus", + "MAJ_SJC_People_Mover", + "FBP_NP_028_NapaVineRegRoutesFrequency", + "FBP_NP_034_NapaVineRegExpServiceHrs", + "FBP_NP_029_NapaVineLocExpServiceHrs", + "FBP_NP_033_Napa_PNR_Lots", + "FBP_SC_043_I280_Mainline_Impr", + "FBP_CC_018_BRT_Brentwood", + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2040"}, + }, + "MAJ_ElCaminoReal_BRT", + {"name": "FBP_MU_064_SR37_LongTerm", "variants_exclude": ["Alt1"]}, + "FBP_NP_051_Airport_Junction", + {"name": "FBP_MU_056_Dumbarton_GRT", "variants_exclude": ["Alt2"]}, + "FBP_SC_088_Envision_Expwy", + {"name": "HSR", "variants_exclude": ["Alt2"]}, + { + "name": "MAJ_SF_050002_Caltrain_Ext_TransbayTerminal", + "variants_include": ["Alt2"], + }, + { + "name": "EIR2_Val_Link_ExpressBus", + "kwargs": {"action": "'revert'"}, + "variants_include": ["Alt2"], + }, + {"name": "Transform_Valley_Link", "variants_include": ["Alt2"]}, + { + "name": "FBP_AL_021_South_Bay_Connect", + "variants_include": ["Alt2"], + }, + { + "name": "Transform_AC_Transbay_Improvements", + "variants_include": ["Alt2"], + }, + {"name": "EIR2_ReXGreen", "variants_include": ["Alt2"]}, + "FBP_CC_019_CCCTA_Freq_Increase", + { + "name": "EIR2_Fix_Alt2", + "kwargs": {"MODELYEAR": "2040"}, + "variants_include": ["Alt2"], + }, + ], + }, + ), + ( + 2045, + { + "hwy": [ + "BP_Vision_Zero", + { + "name": "EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2045"}, + "variants_exclude": ["Alt1", "NextGenFwy"], + }, + { + "name": "EIR1_EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2045"}, + "variants_include": ["Alt1"], + }, + { + "name": "MAJ_Bay_Area_Forward_all", + "kwargs": {"MODELYEAR": "2045"}, + }, + {"name": "FBP_AL_048_SR262_Phase1", "variants_exclude": ["Alt1"]}, + "FBP_NP_045_SR29_Gateway_Impr", + "EXP_Blueprint", + { + "name": "EXP_uncommitted_noAllLaneTolling", + "kwargs": {"MODELYEAR": "2045"}, + "variants_include": ["NextGenFwy"], + }, + ], + "trn": [ + { + "name": "FBP_MU_046_ACE_Freq_Inc", + "kwargs": {"MODELYEAR": "2045"}, + }, + "FBP_SC_106_VTA_LRT_Modernization", + ], + }, + ), + ( + 2050, + { + "hwy": [ + "BP_Vision_Zero", + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'6'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'7'"}, + "variants_exclude": ["Alt1"], + }, + "FBP_SC_028_Stevens_Creek_LRT", + { + "name": "MAJ_Bay_Area_Forward_all", + "kwargs": {"MODELYEAR": "2050"}, + }, + "EXP_Blueprint", + "FBP_SC_041_Envision_Highway_Minor", + "STIP_ITS_SM", + {"name": "BP_Transbay_Crossing", "variants_exclude": ["Alt2"]}, + ], + "trn": [ + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'6'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'7'"}, + "variants_exclude": ["Alt1"], + }, + "FBP_SC_028_Stevens_Creek_LRT", + {"name": "BP_Transbay_Crossing", "variants_exclude": ["Alt2"]}, + ], + }, + ), + ] +) ########################################################### # Put them together for NETWORK_PROJECTS -NETWORK_PROJECTS = collections.OrderedDict() +NETWORK_PROJECTS = collections.OrderedDict() for YEAR in COMMITTED_PROJECTS.keys(): if SCENARIO == "NoProject": # baseline: just committed NETWORK_PROJECTS[YEAR] = { - 'hwy':COMMITTED_PROJECTS[YEAR]['hwy'], - 'trn':COMMITTED_PROJECTS[YEAR]['trn'] + "hwy": COMMITTED_PROJECTS[YEAR]["hwy"], + "trn": COMMITTED_PROJECTS[YEAR]["trn"], } else: # tip NETWORK_PROJECTS[YEAR] = { - 'hwy':COMMITTED_PROJECTS[YEAR]['hwy'] + TIP_PROJECTS[YEAR]['hwy'], - 'trn':COMMITTED_PROJECTS[YEAR]['trn'] + TIP_PROJECTS[YEAR]['trn'] + "hwy": COMMITTED_PROJECTS[YEAR]["hwy"] + TIP_PROJECTS[YEAR]["hwy"], + "trn": COMMITTED_PROJECTS[YEAR]["trn"] + TIP_PROJECTS[YEAR]["trn"], } # handle net_remove, nets keywords - for netmode in ['hwy','trn']: + for netmode in ["hwy", "trn"]: # iterate backwards via index to delete cleanly - for project_idx in range(len(NETWORK_PROJECTS[YEAR][netmode])-1,-1,-1): + for project_idx in range(len(NETWORK_PROJECTS[YEAR][netmode]) - 1, -1, -1): project = NETWORK_PROJECTS[YEAR][netmode][project_idx] # special handling requires project to be specified as dictionary - if not isinstance(project, dict): continue + if not isinstance(project, dict): + continue # variants_exclude: specifies list of network variants for which this project should be *excluded* - if 'variants_exclude' in project.keys() and NET_VARIANT in project['variants_exclude']: - Wrangler.WranglerLogger.info("Removing {} {} {}".format(YEAR, netmode, project)) + if ( + "variants_exclude" in project.keys() + and NET_VARIANT in project["variants_exclude"] + ): + Wrangler.WranglerLogger.info( + "Removing {} {} {}".format(YEAR, netmode, project) + ) del NETWORK_PROJECTS[YEAR][netmode][project_idx] continue # variants_include: specifies list of network variants for which this project should be *included* # if this keyword is present, then this project is included *only* for variants in this list - if 'variants_include' in project.keys() and NET_VARIANT not in project['variants_include']: - Wrangler.WranglerLogger.info("Removing {} {} {}".format(YEAR, netmode, project)) + if ( + "variants_include" in project.keys() + and NET_VARIANT not in project["variants_include"] + ): + Wrangler.WranglerLogger.info( + "Removing {} {} {}".format(YEAR, netmode, project) + ) del NETWORK_PROJECTS[YEAR][netmode][project_idx] continue @@ -520,11 +935,15 @@ # for YEAR in NETWORK_PROJECTS.keys(): # if anything is applied - if ((len(NETWORK_PROJECTS[YEAR]['hwy']) > 0) or (len(NETWORK_PROJECTS[YEAR]['trn']) > 0)): - NETWORK_PROJECTS[YEAR]['hwy'].append('No_zero_length_links') + if (len(NETWORK_PROJECTS[YEAR]["hwy"]) > 0) or ( + len(NETWORK_PROJECTS[YEAR]["trn"]) > 0 + ): + NETWORK_PROJECTS[YEAR]["hwy"].append("No_zero_length_links") - if ((len(NETWORK_PROJECTS[YEAR]['hwy']) > 0) or (len(NETWORK_PROJECTS[YEAR]['trn']) > 0)): - NETWORK_PROJECTS[YEAR]['trn'].append('Move_buses_to_HOV_EXP_lanes') + if (len(NETWORK_PROJECTS[YEAR]["hwy"]) > 0) or ( + len(NETWORK_PROJECTS[YEAR]["trn"]) > 0 + ): + NETWORK_PROJECTS[YEAR]["trn"].append("Move_buses_to_HOV_EXP_lanes") # OPTIONAL. The default route network project directory is Y:\networks. If # projects are stored in another directory, then use this variable to specify it. diff --git a/scripts/net_spec_blueprint.py b/scripts/net_spec_blueprint.py index caf0546..53bbe07 100644 --- a/scripts/net_spec_blueprint.py +++ b/scripts/net_spec_blueprint.py @@ -1,12 +1,13 @@ import os + # MANDATORY. Set this to be the Project Name. # e.g. "RTP2021", "TIP2021", etc -PROJECT = "Blueprint" +PROJECT = "Blueprint" # MANDATORY. Set this to be the git tag for checking out network projects. -#TAG = "HEAD" # Use this tag if you want NetworkWrangler to use the latest version in the local repo to build the network -#TAG = "PBA50_Blueprint" # Use this tag if you want to replicate the network built for PBA50 -TAG = "NGF_NoProject" # Use this tag if you want to build the Next Gen Freeways No Project variant +# TAG = "HEAD" # Use this tag if you want NetworkWrangler to use the latest version in the local repo to build the network +# TAG = "PBA50_Blueprint" # Use this tag if you want to replicate the network built for PBA50 +TAG = "NGF_NoProject" # Use this tag if you want to build the Next Gen Freeways No Project variant # A Alamedaproject can either be a simple string, or it can be # a dictionary with with keys 'name', 'tag' (optional), and 'kwargs' (optional) @@ -14,457 +15,863 @@ # For example: # {'name':"Muni_TEP", 'kwargs':{'servicePlan':"'2012oct'"}} ########################################################### -COMMITTED_PROJECTS = collections.OrderedDict([ - (2015, { - 'hwy':['PROJ_attributes', # adds PROJ attributes to NODE and LINK - {'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2015'}}], - 'trn':[] - }), - (2020, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2020'}}, - {'name':'EXP_237B', 'kwargs':{'FUTURE':"PBA50"}}, # todo: update this to support PBA50 - 'EXP_580C', - 'EXP_680D', - 'EXP_880A', - 'HOV_680F', - 'SCL130001_237_101_MAT_Int_Mod', - 'REG090003_SCLARA_FIP', - 'ALA130005_Dougherty_road_widening', - 'ALA130006_Dublin_Blvd_widening', - 'ALA130014_7th_St_road_diet', - 'ALA130026_Shattuck_Complete_Streets', - 'ALA170049_Central_AVE_Safety_Improvements', - 'ALA150004_EastBay_BRT', - 'CC_130001_BaileyRd_SR4', - 'CC_130046_I680_SR4_Int_Rec', - 'CC_070035_I80_SPDamRd_Int_Phase1', - 'CC_070011_Brentwood_Blvd_Widening', - 'CC_070075_Kirker_Pass_Truck_Lane', - 'CC_090019_Bollinger_Canyon_Widening', - 'CC_130006_Concord_BART_road_diets', - 'CC_170001_SanRamonValleyBlvd_Lane_Addition', - 'MRN150009_San_Rafael_Bridge_Improvements', - 'SF_070027_Yerba_Buena_Ramp_Imp', - 'SF_070005_VanNess_BRT', - 'SF_130011_2ndSt_Road_Diet', - 'SF_Market_Street_Closure', - 'SM_110047_SR92_ElCam_Ramp_Mod', - 'SOL110005_Jepson_Van_to_Com', - 'FBP_SL_042_Jepson_2A', - 'SON070004_101_MarinSonNarrows_Phase1', - 'ALA050014_SR84_Widening', - 'ALA170011_BayBridge_HOV_Connectors', - 'ALA150047_TelegraphAve_Complete_Streets', - 'SM_110047_SR92_ElCam_Ramp_Mod', - 'SCL190002_280_Foothill_improvement', - 'SCL190006_101SB_offramp_improvement', - 'I80_AdaptiveRampMetering', - 'VAR170021_Freeway_Performance_I880', - 'SonomaCounty_Transit_NoBuild2050', - 'SF_MuniForward_Committed', - 'FBP_MU_029_Broadway_Transit_Only_Lanes', - 'EXP_Blueprint_NoProject', - 'FBP_AL_067_Rte84Wide', - 'FBP_AL_065_Bancroft_Bus_Only', - 'FBP_SM_032_US101_Willow_Interchange'], - 'trn':['ALA050015_BART_to_WarmSprings', - 'ACGo', - 'CC_050025_EBart_to_Antioch', - 'GGTransit_Committed', - 'SCL110005_BART_to_Berryessa', - 'SF_010015_Transbay_Terminal', - 'SF_010037_Muni_Central_Subway', - 'SF_070027_Yerba_Buena_Ramp_Imp', - 'SOL030002_FairfieldVacaville_Stn', - 'SON090002_SMART', - 'SON090002_SMART_to_Larkspur', - 'CC_070062_Richmond_Ferry', - 'SF_MuniForward_Committed', - 'VTA_Next', - 'SCL130001_237_101_MAT_Int_Mod', - 'SonomaCounty_Transit_NoBuild2050', - 'SMART_Novato', - 'Xfare_update_2020', - 'ACTransit_Committed', - 'ferry_update_2019', - 'Napa_Solano_Updates_2020', - 'FBP_Beale_Transit_Only_Lane', - 'SamTrans_ECR_Rapid', - 'ALA150004_EastBay_BRT', - {'name':'FBP_SL_026_SolExpressBus', 'kwargs':{'MODELYEAR':'2020'}}], - }), - (2025, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2025'}}, - 'EXP_CC_050028_I680_SB_HOV_Completion', - 'EXP_101B1', - 'EXP_101B2', - 'EXP_680C1', - 'EXP_680F', - 'EXP_85D', - 'EXP_101C', - 'ALA150001_I680_SR84_Int_Wid', - 'ALA150043_Claremont_road_diet', - 'CC_070009_Slatten_Ranch_Rd_Extension', - 'SF_070004_Geary_BRT_Phase1', - 'SON070004_101_MarinSonNarrows_Phase2', - 'SOL110006_Jepson_1B_1C', - 'SCL190008_US101_DLC_Int_Imp', - 'CC_170061_Bus_On_Shoulder_680BRT', - 'I880_US101_AdaptiveRampMetering', - 'MAJ_SCL050009_VTA_Eastridge_Extension', - 'SOL070020_I80_I680_SR12_Int_1_2A', - 'FBP_NP_036_SR29_Imola_PNR', - 'ALA170052_Fruitvale_Ave_ped_improvements', - 'EXP_Blueprint_NoProject'], - 'trn':['SF_010028_Caltrain_Modernization', - 'SON090002_SMART_to_Windsor', - 'MAJ_SCL050009_VTA_Eastridge_Extension', - 'REG090037_New_BART_Trains', - 'FBP_NP_036_SR29_Imola_PNR', - 'SOL070020_I80_I680_SR12_Int_1_2A'] - }), - (2030, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2030'}}, - 'EXP_Blueprint_NoProject'], - 'trn':['BART_NoProject'] - }), - (2035, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2035'}}, - 'EXP_Blueprint_NoProject'], - 'trn':[] - }), - (2040, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2040'}}, - 'EXP_Blueprint_NoProject'], - 'trn':[] - }), - (2045, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2045'}}, - 'EXP_Blueprint_NoProject'], - 'trn':[] - }), - (2050, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2050'}}, - 'EXP_Blueprint_NoProject'], - 'trn':[] - }) -]) +COMMITTED_PROJECTS = collections.OrderedDict( + [ + ( + 2015, + { + "hwy": [ + "PROJ_attributes", # adds PROJ attributes to NODE and LINK + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2015"}, + }, + ], + "trn": [], + }, + ), + ( + 2020, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2020"}, + }, + { + "name": "EXP_237B", + "kwargs": {"FUTURE": "PBA50"}, + }, # todo: update this to support PBA50 + "EXP_580C", + "EXP_680D", + "EXP_880A", + "HOV_680F", + "SCL130001_237_101_MAT_Int_Mod", + "REG090003_SCLARA_FIP", + "ALA130005_Dougherty_road_widening", + "ALA130006_Dublin_Blvd_widening", + "ALA130014_7th_St_road_diet", + "ALA130026_Shattuck_Complete_Streets", + "ALA170049_Central_AVE_Safety_Improvements", + "ALA150004_EastBay_BRT", + "CC_130001_BaileyRd_SR4", + "CC_130046_I680_SR4_Int_Rec", + "CC_070035_I80_SPDamRd_Int_Phase1", + "CC_070011_Brentwood_Blvd_Widening", + "CC_070075_Kirker_Pass_Truck_Lane", + "CC_090019_Bollinger_Canyon_Widening", + "CC_130006_Concord_BART_road_diets", + "CC_170001_SanRamonValleyBlvd_Lane_Addition", + "MRN150009_San_Rafael_Bridge_Improvements", + "SF_070027_Yerba_Buena_Ramp_Imp", + "SF_070005_VanNess_BRT", + "SF_130011_2ndSt_Road_Diet", + "SF_Market_Street_Closure", + "SM_110047_SR92_ElCam_Ramp_Mod", + "SOL110005_Jepson_Van_to_Com", + "FBP_SL_042_Jepson_2A", + "SON070004_101_MarinSonNarrows_Phase1", + "ALA050014_SR84_Widening", + "ALA170011_BayBridge_HOV_Connectors", + "ALA150047_TelegraphAve_Complete_Streets", + "SM_110047_SR92_ElCam_Ramp_Mod", + "SCL190002_280_Foothill_improvement", + "SCL190006_101SB_offramp_improvement", + "I80_AdaptiveRampMetering", + "VAR170021_Freeway_Performance_I880", + "SonomaCounty_Transit_NoBuild2050", + "SF_MuniForward_Committed", + "FBP_MU_029_Broadway_Transit_Only_Lanes", + "EXP_Blueprint_NoProject", + "FBP_AL_067_Rte84Wide", + "FBP_AL_065_Bancroft_Bus_Only", + "FBP_SM_032_US101_Willow_Interchange", + ], + "trn": [ + "ALA050015_BART_to_WarmSprings", + "ACGo", + "CC_050025_EBart_to_Antioch", + "GGTransit_Committed", + "SCL110005_BART_to_Berryessa", + "SF_010015_Transbay_Terminal", + "SF_010037_Muni_Central_Subway", + "SF_070027_Yerba_Buena_Ramp_Imp", + "SOL030002_FairfieldVacaville_Stn", + "SON090002_SMART", + "SON090002_SMART_to_Larkspur", + "CC_070062_Richmond_Ferry", + "SF_MuniForward_Committed", + "VTA_Next", + "SCL130001_237_101_MAT_Int_Mod", + "SonomaCounty_Transit_NoBuild2050", + "SMART_Novato", + "Xfare_update_2020", + "ACTransit_Committed", + "ferry_update_2019", + "Napa_Solano_Updates_2020", + "FBP_Beale_Transit_Only_Lane", + "SamTrans_ECR_Rapid", + "ALA150004_EastBay_BRT", + { + "name": "FBP_SL_026_SolExpressBus", + "kwargs": {"MODELYEAR": "2020"}, + }, + ], + }, + ), + ( + 2025, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2025"}, + }, + "EXP_CC_050028_I680_SB_HOV_Completion", + "EXP_101B1", + "EXP_101B2", + "EXP_680C1", + "EXP_680F", + "EXP_85D", + "EXP_101C", + "ALA150001_I680_SR84_Int_Wid", + "ALA150043_Claremont_road_diet", + "CC_070009_Slatten_Ranch_Rd_Extension", + "SF_070004_Geary_BRT_Phase1", + "SON070004_101_MarinSonNarrows_Phase2", + "SOL110006_Jepson_1B_1C", + "SCL190008_US101_DLC_Int_Imp", + "CC_170061_Bus_On_Shoulder_680BRT", + "I880_US101_AdaptiveRampMetering", + "MAJ_SCL050009_VTA_Eastridge_Extension", + "SOL070020_I80_I680_SR12_Int_1_2A", + "FBP_NP_036_SR29_Imola_PNR", + "ALA170052_Fruitvale_Ave_ped_improvements", + "EXP_Blueprint_NoProject", + ], + "trn": [ + "SF_010028_Caltrain_Modernization", + "SON090002_SMART_to_Windsor", + "MAJ_SCL050009_VTA_Eastridge_Extension", + "REG090037_New_BART_Trains", + "FBP_NP_036_SR29_Imola_PNR", + "SOL070020_I80_I680_SR12_Int_1_2A", + ], + }, + ), + ( + 2030, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2030"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": ["BART_NoProject"], + }, + ), + ( + 2035, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2035"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": [], + }, + ), + ( + 2040, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2040"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": [], + }, + ), + ( + 2045, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2045"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": [], + }, + ), + ( + 2050, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2050"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": [], + }, + ), + ] +) ########################################################### # Blueprint projects -BLUEPRINT_PROJECTS = collections.OrderedDict([ - (2015, {'hwy':[], - 'trn':[] - }), - (2020, {'hwy':[], - 'trn':[] - }), - (2025, {'hwy':['RRSP_Alameda_Point_Transit_Improvements', - 'MAJ_MTC050027_Berkeley_Ferry', - 'MAJ_WETA_Service_Frequency_Increase', - {'name':'Transform_SR37_Widening_Interim', 'variants_exclude':['Alt1']}, - 'MAJ_SF_Congestion_Pricing', - 'MAJ_Geary_BRT_Phase2', - 'FBP_MU_041_Hovercraft_Pilot', - 'BP_Vision_Zero', - 'EXP_Blueprint', - 'MAJ_AC_Frequency_Improvement', - 'MRN050034_101_MarinSonNarrows_Phase2', - 'FBP_MU_044_SouthSF_Ferry_Serv_Incr', - 'FBP_MU_029_ACRapid_2025', - 'RRSP_E14_Mission_Corridor', - 'FBP_MR_026_NovatoWide', - 'FBP_CC_054_CrowCanyonWide', - 'FBP_NP_038_TSP_On_SR29', - {'name':'FBP_CC_050_SR4_Operation_Improvements_EB', 'variants_exclude':['Alt1']}, - 'FBP_NP_044_Soscol_Junction', - 'FBP_SL_033_FairgroundsWide', - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7','kwargs':{'PHASE':"'2B'"}}, - {'name':'FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5', 'kwargs':{'PHASE':"'1'"}, 'variants_exclude':['Alt1']}, - {'name':'FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5', 'kwargs':{'PHASE':"'2'"}, 'variants_exclude':['Alt1']}, - {'name':'EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2025'}, 'variants_exclude':['Alt1', 'NextGenFwy']}, - {'name':'EXP_uncommitted_noAllLaneTolling', 'kwargs':{'MODELYEAR':'2025'}, 'variants_include':['NextGenFwy']}, - {'name':'EIR1_EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2025'}, 'variants_include':['Alt1']}, - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2025'}}, - {'name':'FBP_CC_15_23rd_St_BRT', 'kwargs':{'MODELYEAR':'2025'}}, - 'FBP_SC_103_MontagueWide', - {'name':'MAJ_Bay_Area_Forward_all', 'kwargs':{'MODELYEAR':'2025'}}, - 'FBP_CC_057_LoneTreeWide', - 'FBP_CC_063_BrentwoodWide', - 'FBP_CC_067_WillowPassWide', - 'FBP_CC_065_LaurelWide', - 'FBP_AL_062_TassajaraWide', - 'FBP_SC_039_SR237WBWide', - 'FBP_AL_051_7St_Grade_Sep_West', - 'FBP_AL_044_I880_Whipple_Imps', - 'FBP_AL_055_DubBlvd_NCanyons_Ext', - 'FBP_SN_017_Arata_Int', - 'FBP_CC_017_Brentwood_Intermodal', - 'FBP_SF_030_Balboa_Park_Area_2', - 'EXP_Blueprint', - 'FBP_AL_039_I580_Interchange_Imps', - 'FBP_CC_056_LaurelExtension', - 'FBP_SC_084_10th_BridgeWide', - 'FBP_SL_053_PeabodyWide', - 'FBP_SC_073_BlossomHill_101Wide', - 'FBP_SC_082_US101_25_Interchange', - 'FBP_SM_035_Peninsula_101_OnOffRamps', - 'FBP_CC_045_SanPabloDam_Interchange_Phase2', - 'FBP_CC_030_OakleyAmtrak', - 'STIP_ProduceAve', - 'FBP_SM_033_US101_Holly_Interchange', - 'FBP_SM_034_Route92_ElCamino_Interchange', - 'FBP_SL_019_BeniciaRoad_Diet', - 'FBP_SL_023_WestTexasRoad_Diet', - 'FBP_SN_012_PetalumaBlvd_Diet', - 'MAJ_MissionBay_SF_Ferry', - {'name':'EIR2_Val_Link_ExpressBus', 'variants_include':['Alt2']}, - {'name':'EIR2_ReXBlue', 'variants_include':['Alt2']}, - 'FBP_SC_072_US101_Trimble_Interchange'], - 'trn':['MAJ_Geary_BRT_Phase2', - 'FBP_AL_001_NewarkFremPDA', - {'name':'FBP_MU_059_ACTransbay_Freq_Incr', 'variants_exclude':['Alt2']}, - 'MAJ_AC_Frequency_Improvement', - 'RRSP_Alameda_Point_Transit_Improvements', - 'MAJ_MTC050027_Berkeley_Ferry', - 'MAJ_WETA_Service_Frequency_Increase', - {'name':'FBP_MU_046_ACE_Freq_Inc', 'kwargs':{'MODELYEAR':'2025'}}, - {'name':'Transform_SR37_Widening_Interim', 'variants_exclude':['Alt1']}, - {'name':'MAJ_SF_Congestion_Pricing', 'variants_exclude':['NextGenFwy']}, - 'FBP_MU_041_Hovercraft_Pilot', - 'FBP_MU_049_Caltrain_6TPHPD', - {'name':'FBP_MU_060_ReX_Blue', 'variants_exclude':['Alt2']}, - {'name':'EIR2_ReXBlue', 'variants_include':['Alt2']}, - 'FBP_MU_044_SouthSF_Ferry_Serv_Incr', - 'GGT_Service_Imp', - 'FBP_MU_029_ACRapid_2025', - 'RRSP_E14_Mission_Corridor', - 'FBP_NP_044_Soscol_Junction', - 'MAJ_RedwoodCity_SF_Ferry', - 'MAJ_Alameda_Point_SF_Ferry', - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7','kwargs':{'PHASE':"'2B'"}}, - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2025'}}, - {'name':'FBP_CC_15_23rd_St_BRT', 'kwargs':{'MODELYEAR':'2025'}}, - 'FBP_CC_030_OakleyAmtrak', - 'FBP_SM_020_Regional_Express_Buses', - 'MAJ_MissionBay_SF_Ferry', - 'MAJ_Sonoma_Frequency_Increase', - {'name':'EIR1_Freq_Boosts', 'kwargs':{'MODELYEAR':'2025'}, 'variants_include':['Alt1']}, - {'name':'EIR2_HRA_Freq_Incr', 'kwargs':{'MODELYEAR':'2025'}, 'variants_include':['Alt2']}, - {'name':'EIR2_PDA_Freq_Incr', 'kwargs':{'MODELYEAR':'2025'}, 'variants_include':['Alt2']}, - {'name':'EIR2_Val_Link_ExpressBus', 'variants_include':['Alt2']}, - {'name':'SON090002_SMART_NorthPetaluma', 'variants_exclude':['Baseline']}] - }), - (2030, {'hwy':['MAJ_SanPablo_BRT', - {'name':'BP_Tolls_On_Congested_Freeways_2030', 'variants_exclude':['NextGenFwy']}, - 'BP_Vision_Zero', - {'name':'FBP_AL_021_South_Bay_Connect', 'variants_exclude':['Alt2']}, - 'FBP_MU_044_Richmond_Ferry_Serv_Incr', - 'MAJ_REG090037_BART_Core_Cap', - {'name':'Transform_Valley_Link', 'variants_exclude':['Alt2']}, - 'FBP_NP_040_VINE_Exp_Bus_Enhancements', - 'FBP_AL_045_Oak_Ala_Access_Pr', - {'name':'FBP_MR_021_101_580_Direct_Connector', 'variants_exclude':['Alt1']}, - # 'FBP_MR_018_US101_BOS', - 'FBP_CC_036_I80_ExpBus_Impr', - {'name':'FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5','kwargs':{'PHASE':"'4'"}, 'variants_exclude':['Alt1']}, - 'FBP_CC_021_Ant_Mart_Herc_Ferry', - {'name':'EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2030'}, 'variants_exclude':['Alt1', 'NextGenFwy']}, - {'name':'EXP_uncommitted_noAllLaneTolling', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['NextGenFwy']}, - {'name':'EIR1_EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['Alt1']}, - 'FBP_SC_104_OaklandWide', - {'name':'FBP_CC_15_23rd_St_BRT', 'kwargs':{'MODELYEAR':'2030'}}, - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2030'}}, - {'name':'MAJ_Bay_Area_Forward_all', 'kwargs':{'MODELYEAR':'2030'}}, - 'FBP_CC_064_CaminoTassajaraWide', - 'FBP_CC_066_CypressWide', - 'FBP_SC_059_SR237EBWide', - 'FBP_AL_064_UnionCityWide', - 'FBP_SC_074_US101_BuenaVista_Int', - 'EXP_Blueprint', - 'FBP_SC_054_SR17_Corridor_Relief', - 'FBP_AL_043_A_StreetWide', - 'FBP_CC_061_062_West_Leland_Ext_Phases1_2', - 'FBP_SM_042_Hwy1_ManorDrive', - 'FBP_SL_042_Jepson_2B_2C', - 'FBP_CC_024_Oakley_PNR_Tri_Delta', - 'FBP_SC_083_US101_Zanker_Skyport_Interchange', - 'FBP_SL_022_SonomaBlvd_Diet', - 'FBP_SM_027_US101_92', - 'FBP_SM_007_ElCamino_CompleteStreets'], - 'trn':['BP_PDA_Transit_Enhancements', - {'name':'FBP_MU_046_ACE_Freq_Inc', 'kwargs':{'MODELYEAR':'2030'}}, - 'MAJ_BRT030001_BART_to_SanJose', - 'BART_Irvington_Infill', - 'MAJ_REG090037_BART_Core_Cap', - {'name':'FBP_AL_021_South_Bay_Connect', 'variants_exclude':['Alt2']}, - 'FBP_MU_049_Caltrain_8TPHPD', - 'FBP_MU_061_ReX_Green', - 'MAJ_SanPablo_BRT', - 'FBP_MU_044_Richmond_Ferry_Serv_Incr', - {'name':'Transform_Valley_Link', 'variants_exclude':['Alt2']}, - 'FBP_SF_028_SF_Express_Bus_On_Exp_Lanes', - {'name':'MAJ_SF_050002_Caltrain_Ext_TransbayTerminal', 'variants_exclude':['Alt2']}, - 'FBP_SF_024_Historic_Streetcar_Ext', - 'FBP_MuniForward_Uncommitted_Rail', - 'FBP_CC_036_I80_ExpBus_Impr', - 'FBP_CC_021_Ant_Mart_Herc_Ferry', - 'FBP_AL_045_Oak_Ala_Access_Pr', - 'FBP_CC_028_Hercules_Station', - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2030'}}, - {'name':'FBP_CC_15_23rd_St_BRT', 'kwargs':{'MODELYEAR':'2030'}}, - 'FBP_CC_024_Oakley_PNR_Tri_Delta', - {'name':'EIR1_Freq_Boosts', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['Alt1']}, - {'name':'EIR2_HRA_Freq_Incr', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['Alt2']}, - {'name':'EIR2_PDA_Freq_Incr', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['Alt2']}, - {'name':'EIR2_Fix_Alt2', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['Alt2']}] - }), - (2035, {'hwy':['MAJ_MuniForward_Uncommitted', - 'MAJ_Treasure_Island_Congestion_Pricing', - {'name':'BP_Tolls_On_Congested_Freeways_2035', 'variants_exclude':['NextGenFwy']}, - 'BP_Vision_Zero', - 'RRSP_East_West_Connector', - 'Transform_I680_Multimodal_Imp', - 'FBP_SM_022_I380_Widening', - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'3'"}, 'variants_exclude':['Alt1']}, - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'4'"}, 'variants_exclude':['Alt1']}, - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'5'"}, 'variants_exclude':['Alt1']}, - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2035'}}, - {'name':'EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2035'}, 'variants_exclude':['Alt1', 'NextGenFwy']}, - {'name':'EXP_uncommitted_noAllLaneTolling', 'kwargs':{'MODELYEAR':'2035'}, 'variants_include':['NextGenFwy']}, - {'name':'EIR1_EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2035'}, 'variants_include':['Alt1']}, - {'name':'MAJ_Bay_Area_Forward_all', 'kwargs':{'MODELYEAR':'2035'}}, - 'FBP_AL_076_TelegraphDiet', - 'FBP_SN_018_Cotati_101_RailroadAve_Impr', - 'FBP_NP_079_Trower_Ext', - 'EXP_Blueprint', - {'name':'EIR1_No_SR37', 'variants_include':['Alt1']}, - {'name':'NGF_NoProject_tollscsv', 'variants_include':['NextGenFwy']}], - 'trn':['MAJ_MuniForward_Uncommitted', - 'RRSP_South_East_Waterfront_Transit_Imp', - 'FBP_MU_062_ReX_Red', - 'Transform_I680_Multimodal_Imp', - 'Transform_SeamlessTransit', - 'MAJ_Treasure_Island_Congestion_Pricing', - 'RRSP_East_West_Connector', - 'MAJ_Treasure_Island_Ferry', - 'FBP_NP_079_Trower_Ext', - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'3'"}, 'variants_exclude':['Alt1']}, - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'4'"}, 'variants_exclude':['Alt1']}, - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2035'}}, - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'5'"}, 'variants_exclude':['Alt1']}, - {'name':'FBP_SL_026_SolExpressBus', 'kwargs':{'MODELYEAR':'2035'}}, - 'FBP_SL_020_MilitaryWest_Diet', - {'name':'EIR1_Freq_Boosts', 'kwargs':{'MODELYEAR':'2035'}, 'variants_include':['Alt1']}, - {'name':'EIR2_VTA_LRT_Orange', 'variants_include':['Alt2']}, - {'name':'EIR2_Fix_Alt2', 'kwargs':{'MODELYEAR':'2035'}, 'variants_include':['Alt2']}, - {'name':'EIR1_No_SR37', 'variants_include':['Alt1']}] - }), - (2040, {'hwy':['BP_Vision_Zero', - 'FBP_SC_050_I680_Montague_Int_Imp', - 'FBP_MU_029_ACRapid_2040', - 'FBP_NP_074_SoscolWide', - 'FBP_CC_059_PittAntiochWide', - {'name':'FBP_CC_051_SR4_Operation_Improvements_WB', 'variants_exclude':['Alt1']}, - 'FBP_CC_037_680_AuxLanes', - 'RRSP_EC_Cap_Imp_ECR_Bus', - {'name':'MAJ_SR_239', 'variants_exclude':['Alt1']}, - 'FBP_NP_033_Napa_PNR_Lots', - 'FBP_CC_018_BRT_Brentwood', - 'FBP_SC_043_I280_Mainline_Impr', - 'MAJ_ElCaminoReal_BRT', - 'FBP_AL_042_I680_Stoneridge_Widening', - {'name':'FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5', 'kwargs':{'PHASE':"'5'"},'variants_exclude':['Alt1']}, - {'name':'EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2040'}, 'variants_exclude':['Alt1', 'NextGenFwy']}, - {'name':'EXP_uncommitted_noAllLaneTolling', 'kwargs':{'MODELYEAR':'2040'}, 'variants_include':['NextGenFwy']}, - {'name':'EIR1_EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2040'}, 'variants_include':['Alt1']}, - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2040'}}, - {'name':'MAJ_Bay_Area_Forward_all', 'kwargs':{'MODELYEAR':'2040'}}, - 'FBP_SC_105_SanTomasWide', - 'FBP_SC_102_CalaverasWide', - 'FBP_CC_039_Eastbound24Wide', - {'name':'FBP_MU_064_SR37_LongTerm', 'variants_exclude':['Alt1']}, - 'FBP_SC_094_LawrenceWide', - 'FBP_NP_066_Newell_Dr', - 'EXP_Blueprint', - 'FBP_AL_052_AutoMallWide', - 'FBP_CC_038_SR242_Clayton_OnOffRamps', - 'FBP_SC_047_I280_Winchester_OffRamp', - 'FBP_SC_076_US101_Taylor_Interchange', - 'FBP_NP_051_Airport_Junction', - 'FBP_SC_101_BrokawBridgeWide', - 'FBP_SC_081_US101_SR237', - 'FBP_SC_088_Envision_Expwy', - {'name':'FBP_MU_056_Dumbarton_GRT', 'variants_exclude':['Alt2']}, - {'name':'EIR2_Val_Link_ExpressBus', 'kwargs':{'action':"'revert'"}, 'variants_include':['Alt2']}, - {'name':'Transform_Valley_Link', 'variants_include':['Alt2']}, - {'name':'FBP_AL_021_South_Bay_Connect', 'variants_include':['Alt2']}, - {'name':'Transform_AC_Transbay_Improvements', 'variants_include':['Alt2']}, - 'FBP_SC_042_I280_Downtown_Access_Improvements'], - 'trn':[{'name':'FBP_MU_046_ACE_Freq_Inc', 'kwargs':{'MODELYEAR':'2040'}}, - 'MAJ_Vasona_LRT_Extension', - 'FBP_MU_029_ACRapid_2040', - 'RRSP_EC_Cap_Imp_ECR_Bus', - 'MAJ_SJC_People_Mover', - 'FBP_NP_028_NapaVineRegRoutesFrequency', - 'FBP_NP_034_NapaVineRegExpServiceHrs', - 'FBP_NP_029_NapaVineLocExpServiceHrs', - 'FBP_NP_033_Napa_PNR_Lots', - 'FBP_SC_043_I280_Mainline_Impr', - 'FBP_CC_018_BRT_Brentwood', - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2040'}}, - 'MAJ_ElCaminoReal_BRT', - {'name':'FBP_MU_064_SR37_LongTerm', 'variants_exclude':['Alt1']}, - 'FBP_NP_051_Airport_Junction', - {'name':'FBP_MU_056_Dumbarton_GRT', 'variants_exclude':['Alt2']}, - 'FBP_SC_088_Envision_Expwy', - {'name':'HSR', 'variants_exclude':['Alt2']}, - {'name':'MAJ_SF_050002_Caltrain_Ext_TransbayTerminal', 'variants_include':['Alt2']}, - {'name':'EIR2_Val_Link_ExpressBus', 'kwargs':{'action':"'revert'"}, 'variants_include':['Alt2']}, - {'name':'Transform_Valley_Link', 'variants_include':['Alt2']}, - {'name':'FBP_AL_021_South_Bay_Connect', 'variants_include':['Alt2']}, - {'name':'Transform_AC_Transbay_Improvements', 'variants_include':['Alt2']}, - {'name':'EIR2_ReXGreen', 'variants_include':['Alt2']}, - 'FBP_CC_019_CCCTA_Freq_Increase', - {'name':'EIR2_Fix_Alt2', 'kwargs':{'MODELYEAR':'2040'}, 'variants_include':['Alt2']}] - }), - (2045, {'hwy':['BP_Vision_Zero', - {'name':'EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2045'}, 'variants_exclude':['Alt1', 'NextGenFwy']}, - {'name':'EXP_uncommitted_noAllLaneTolling', 'kwargs':{'MODELYEAR':'2045'}, 'variants_include':['NextGenFwy']}, - {'name':'EIR1_EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2045'}, 'variants_include':['Alt1']}, - {'name':'MAJ_Bay_Area_Forward_all', 'kwargs':{'MODELYEAR':'2045'}}, - {'name':'FBP_AL_048_SR262_Phase1', 'variants_exclude':['Alt1']}, - 'FBP_NP_045_SR29_Gateway_Impr', - 'EXP_Blueprint'], - 'trn':[{'name':'FBP_MU_046_ACE_Freq_Inc', 'kwargs':{'MODELYEAR':'2045'}}, - 'FBP_SC_106_VTA_LRT_Modernization'] - }), - (2050, {'hwy':['BP_Vision_Zero', - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'6'"}, 'variants_exclude':['Alt1']}, - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'7'"}, 'variants_exclude':['Alt1']}, - 'FBP_SC_028_Stevens_Creek_LRT', - {'name':'MAJ_Bay_Area_Forward_all', 'kwargs':{'MODELYEAR':'2050'}}, - 'EXP_Blueprint', - 'FBP_SC_041_Envision_Highway_Minor', - 'STIP_ITS_SM', - {'name':'BP_Transbay_Crossing', 'variants_exclude':['Alt2']}], - 'trn':[{'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'6'"}, 'variants_exclude':['Alt1']}, - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', 'kwargs':{'PHASE':"'7'"}, 'variants_exclude':['Alt1']}, - 'FBP_SC_028_Stevens_Creek_LRT', - {'name':'BP_Transbay_Crossing', 'variants_exclude':['Alt2']}] - }) - ]) +BLUEPRINT_PROJECTS = collections.OrderedDict( + [ + (2015, {"hwy": [], "trn": []}), + (2020, {"hwy": [], "trn": []}), + ( + 2025, + { + "hwy": [ + "RRSP_Alameda_Point_Transit_Improvements", + "MAJ_MTC050027_Berkeley_Ferry", + "MAJ_WETA_Service_Frequency_Increase", + { + "name": "Transform_SR37_Widening_Interim", + "variants_exclude": ["Alt1"], + }, + "MAJ_SF_Congestion_Pricing", + "MAJ_Geary_BRT_Phase2", + "FBP_MU_041_Hovercraft_Pilot", + "BP_Vision_Zero", + "EXP_Blueprint", + "MAJ_AC_Frequency_Improvement", + "MRN050034_101_MarinSonNarrows_Phase2", + "FBP_MU_044_SouthSF_Ferry_Serv_Incr", + "FBP_MU_029_ACRapid_2025", + "RRSP_E14_Mission_Corridor", + "FBP_MR_026_NovatoWide", + "FBP_CC_054_CrowCanyonWide", + "FBP_NP_038_TSP_On_SR29", + { + "name": "FBP_CC_050_SR4_Operation_Improvements_EB", + "variants_exclude": ["Alt1"], + }, + "FBP_NP_044_Soscol_Junction", + "FBP_SL_033_FairgroundsWide", + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'2B'"}, + }, + { + "name": "FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5", + "kwargs": {"PHASE": "'1'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5", + "kwargs": {"PHASE": "'2'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2025"}, + "variants_exclude": ["Alt1", "NextGenFwy"], + }, + { + "name": "EXP_uncommitted_noAllLaneTolling", + "kwargs": {"MODELYEAR": "2025"}, + "variants_include": ["NextGenFwy"], + }, + { + "name": "EIR1_EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2025"}, + "variants_include": ["Alt1"], + }, + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2025"}, + }, + {"name": "FBP_CC_15_23rd_St_BRT", "kwargs": {"MODELYEAR": "2025"}}, + "FBP_SC_103_MontagueWide", + { + "name": "MAJ_Bay_Area_Forward_all", + "kwargs": {"MODELYEAR": "2025"}, + }, + "FBP_CC_057_LoneTreeWide", + "FBP_CC_063_BrentwoodWide", + "FBP_CC_067_WillowPassWide", + "FBP_CC_065_LaurelWide", + "FBP_AL_062_TassajaraWide", + "FBP_SC_039_SR237WBWide", + "FBP_AL_051_7St_Grade_Sep_West", + "FBP_AL_044_I880_Whipple_Imps", + "FBP_AL_055_DubBlvd_NCanyons_Ext", + "FBP_SN_017_Arata_Int", + "FBP_CC_017_Brentwood_Intermodal", + "FBP_SF_030_Balboa_Park_Area_2", + "EXP_Blueprint", + "FBP_AL_039_I580_Interchange_Imps", + "FBP_CC_056_LaurelExtension", + "FBP_SC_084_10th_BridgeWide", + "FBP_SL_053_PeabodyWide", + "FBP_SC_073_BlossomHill_101Wide", + "FBP_SC_082_US101_25_Interchange", + "FBP_SM_035_Peninsula_101_OnOffRamps", + "FBP_CC_045_SanPabloDam_Interchange_Phase2", + "FBP_CC_030_OakleyAmtrak", + "STIP_ProduceAve", + "FBP_SM_033_US101_Holly_Interchange", + "FBP_SM_034_Route92_ElCamino_Interchange", + "FBP_SL_019_BeniciaRoad_Diet", + "FBP_SL_023_WestTexasRoad_Diet", + "FBP_SN_012_PetalumaBlvd_Diet", + "MAJ_MissionBay_SF_Ferry", + {"name": "EIR2_Val_Link_ExpressBus", "variants_include": ["Alt2"]}, + {"name": "EIR2_ReXBlue", "variants_include": ["Alt2"]}, + "FBP_SC_072_US101_Trimble_Interchange", + ], + "trn": [ + "MAJ_Geary_BRT_Phase2", + "FBP_AL_001_NewarkFremPDA", + { + "name": "FBP_MU_059_ACTransbay_Freq_Incr", + "variants_exclude": ["Alt2"], + }, + "MAJ_AC_Frequency_Improvement", + "RRSP_Alameda_Point_Transit_Improvements", + "MAJ_MTC050027_Berkeley_Ferry", + "MAJ_WETA_Service_Frequency_Increase", + { + "name": "FBP_MU_046_ACE_Freq_Inc", + "kwargs": {"MODELYEAR": "2025"}, + }, + { + "name": "Transform_SR37_Widening_Interim", + "variants_exclude": ["Alt1"], + }, + { + "name": "MAJ_SF_Congestion_Pricing", + "variants_exclude": ["NextGenFwy"], + }, + "FBP_MU_041_Hovercraft_Pilot", + "FBP_MU_049_Caltrain_6TPHPD", + {"name": "FBP_MU_060_ReX_Blue", "variants_exclude": ["Alt2"]}, + {"name": "EIR2_ReXBlue", "variants_include": ["Alt2"]}, + "FBP_MU_044_SouthSF_Ferry_Serv_Incr", + "GGT_Service_Imp", + "FBP_MU_029_ACRapid_2025", + "RRSP_E14_Mission_Corridor", + "FBP_NP_044_Soscol_Junction", + "MAJ_RedwoodCity_SF_Ferry", + "MAJ_Alameda_Point_SF_Ferry", + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'2B'"}, + }, + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2025"}, + }, + {"name": "FBP_CC_15_23rd_St_BRT", "kwargs": {"MODELYEAR": "2025"}}, + "FBP_CC_030_OakleyAmtrak", + "FBP_SM_020_Regional_Express_Buses", + "MAJ_MissionBay_SF_Ferry", + "MAJ_Sonoma_Frequency_Increase", + { + "name": "EIR1_Freq_Boosts", + "kwargs": {"MODELYEAR": "2025"}, + "variants_include": ["Alt1"], + }, + { + "name": "EIR2_HRA_Freq_Incr", + "kwargs": {"MODELYEAR": "2025"}, + "variants_include": ["Alt2"], + }, + { + "name": "EIR2_PDA_Freq_Incr", + "kwargs": {"MODELYEAR": "2025"}, + "variants_include": ["Alt2"], + }, + {"name": "EIR2_Val_Link_ExpressBus", "variants_include": ["Alt2"]}, + { + "name": "SON090002_SMART_NorthPetaluma", + "variants_exclude": ["Baseline"], + }, + ], + }, + ), + ( + 2030, + { + "hwy": [ + "MAJ_SanPablo_BRT", + { + "name": "BP_Tolls_On_Congested_Freeways_2030", + "variants_exclude": ["NextGenFwy"], + }, + "BP_Vision_Zero", + { + "name": "FBP_AL_021_South_Bay_Connect", + "variants_exclude": ["Alt2"], + }, + "FBP_MU_044_Richmond_Ferry_Serv_Incr", + "MAJ_REG090037_BART_Core_Cap", + {"name": "Transform_Valley_Link", "variants_exclude": ["Alt2"]}, + "FBP_NP_040_VINE_Exp_Bus_Enhancements", + "FBP_AL_045_Oak_Ala_Access_Pr", + { + "name": "FBP_MR_021_101_580_Direct_Connector", + "variants_exclude": ["Alt1"], + }, + # 'FBP_MR_018_US101_BOS', + "FBP_CC_036_I80_ExpBus_Impr", + { + "name": "FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5", + "kwargs": {"PHASE": "'4'"}, + "variants_exclude": ["Alt1"], + }, + "FBP_CC_021_Ant_Mart_Herc_Ferry", + { + "name": "EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2030"}, + "variants_exclude": ["Alt1", "NextGenFwy"], + }, + { + "name": "EXP_uncommitted_noAllLaneTolling", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["NextGenFwy"], + }, + { + "name": "EIR1_EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["Alt1"], + }, + "FBP_SC_104_OaklandWide", + {"name": "FBP_CC_15_23rd_St_BRT", "kwargs": {"MODELYEAR": "2030"}}, + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2030"}, + }, + { + "name": "MAJ_Bay_Area_Forward_all", + "kwargs": {"MODELYEAR": "2030"}, + }, + "FBP_CC_064_CaminoTassajaraWide", + "FBP_CC_066_CypressWide", + "FBP_SC_059_SR237EBWide", + "FBP_AL_064_UnionCityWide", + "FBP_SC_074_US101_BuenaVista_Int", + "EXP_Blueprint", + "FBP_SC_054_SR17_Corridor_Relief", + "FBP_AL_043_A_StreetWide", + "FBP_CC_061_062_West_Leland_Ext_Phases1_2", + "FBP_SM_042_Hwy1_ManorDrive", + "FBP_SL_042_Jepson_2B_2C", + "FBP_CC_024_Oakley_PNR_Tri_Delta", + "FBP_SC_083_US101_Zanker_Skyport_Interchange", + "FBP_SL_022_SonomaBlvd_Diet", + "FBP_SM_027_US101_92", + "FBP_SM_007_ElCamino_CompleteStreets", + ], + "trn": [ + "BP_PDA_Transit_Enhancements", + { + "name": "FBP_MU_046_ACE_Freq_Inc", + "kwargs": {"MODELYEAR": "2030"}, + }, + "MAJ_BRT030001_BART_to_SanJose", + "BART_Irvington_Infill", + "MAJ_REG090037_BART_Core_Cap", + { + "name": "FBP_AL_021_South_Bay_Connect", + "variants_exclude": ["Alt2"], + }, + "FBP_MU_049_Caltrain_8TPHPD", + "FBP_MU_061_ReX_Green", + "MAJ_SanPablo_BRT", + "FBP_MU_044_Richmond_Ferry_Serv_Incr", + {"name": "Transform_Valley_Link", "variants_exclude": ["Alt2"]}, + "FBP_SF_028_SF_Express_Bus_On_Exp_Lanes", + { + "name": "MAJ_SF_050002_Caltrain_Ext_TransbayTerminal", + "variants_exclude": ["Alt2"], + }, + "FBP_SF_024_Historic_Streetcar_Ext", + "FBP_MuniForward_Uncommitted_Rail", + "FBP_CC_036_I80_ExpBus_Impr", + "FBP_CC_021_Ant_Mart_Herc_Ferry", + "FBP_AL_045_Oak_Ala_Access_Pr", + "FBP_CC_028_Hercules_Station", + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2030"}, + }, + {"name": "FBP_CC_15_23rd_St_BRT", "kwargs": {"MODELYEAR": "2030"}}, + "FBP_CC_024_Oakley_PNR_Tri_Delta", + { + "name": "EIR1_Freq_Boosts", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["Alt1"], + }, + { + "name": "EIR2_HRA_Freq_Incr", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["Alt2"], + }, + { + "name": "EIR2_PDA_Freq_Incr", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["Alt2"], + }, + { + "name": "EIR2_Fix_Alt2", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["Alt2"], + }, + ], + }, + ), + ( + 2035, + { + "hwy": [ + "MAJ_MuniForward_Uncommitted", + "MAJ_Treasure_Island_Congestion_Pricing", + { + "name": "BP_Tolls_On_Congested_Freeways_2035", + "variants_exclude": ["NextGenFwy"], + }, + "BP_Vision_Zero", + "RRSP_East_West_Connector", + "Transform_I680_Multimodal_Imp", + "FBP_SM_022_I380_Widening", + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'3'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'4'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'5'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2035"}, + }, + { + "name": "EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2035"}, + "variants_exclude": ["Alt1", "NextGenFwy"], + }, + { + "name": "EXP_uncommitted_noAllLaneTolling", + "kwargs": {"MODELYEAR": "2035"}, + "variants_include": ["NextGenFwy"], + }, + { + "name": "EIR1_EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2035"}, + "variants_include": ["Alt1"], + }, + { + "name": "MAJ_Bay_Area_Forward_all", + "kwargs": {"MODELYEAR": "2035"}, + }, + "FBP_AL_076_TelegraphDiet", + "FBP_SN_018_Cotati_101_RailroadAve_Impr", + "FBP_NP_079_Trower_Ext", + "EXP_Blueprint", + {"name": "EIR1_No_SR37", "variants_include": ["Alt1"]}, + { + "name": "NGF_NoProject_tollscsv", + "variants_include": ["NextGenFwy"], + }, + ], + "trn": [ + "MAJ_MuniForward_Uncommitted", + "RRSP_South_East_Waterfront_Transit_Imp", + "FBP_MU_062_ReX_Red", + "Transform_I680_Multimodal_Imp", + "Transform_SeamlessTransit", + "MAJ_Treasure_Island_Congestion_Pricing", + "RRSP_East_West_Connector", + "MAJ_Treasure_Island_Ferry", + "FBP_NP_079_Trower_Ext", + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'3'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'4'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2035"}, + }, + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'5'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "FBP_SL_026_SolExpressBus", + "kwargs": {"MODELYEAR": "2035"}, + }, + "FBP_SL_020_MilitaryWest_Diet", + { + "name": "EIR1_Freq_Boosts", + "kwargs": {"MODELYEAR": "2035"}, + "variants_include": ["Alt1"], + }, + {"name": "EIR2_VTA_LRT_Orange", "variants_include": ["Alt2"]}, + { + "name": "EIR2_Fix_Alt2", + "kwargs": {"MODELYEAR": "2035"}, + "variants_include": ["Alt2"], + }, + {"name": "EIR1_No_SR37", "variants_include": ["Alt1"]}, + ], + }, + ), + ( + 2040, + { + "hwy": [ + "BP_Vision_Zero", + "FBP_SC_050_I680_Montague_Int_Imp", + "FBP_MU_029_ACRapid_2040", + "FBP_NP_074_SoscolWide", + "FBP_CC_059_PittAntiochWide", + { + "name": "FBP_CC_051_SR4_Operation_Improvements_WB", + "variants_exclude": ["Alt1"], + }, + "FBP_CC_037_680_AuxLanes", + "RRSP_EC_Cap_Imp_ECR_Bus", + {"name": "MAJ_SR_239", "variants_exclude": ["Alt1"]}, + "FBP_NP_033_Napa_PNR_Lots", + "FBP_CC_018_BRT_Brentwood", + "FBP_SC_043_I280_Mainline_Impr", + "MAJ_ElCaminoReal_BRT", + "FBP_AL_042_I680_Stoneridge_Widening", + { + "name": "FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5", + "kwargs": {"PHASE": "'5'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2040"}, + "variants_exclude": ["Alt1", "NextGenFwy"], + }, + { + "name": "EXP_uncommitted_noAllLaneTolling", + "kwargs": {"MODELYEAR": "2040"}, + "variants_include": ["NextGenFwy"], + }, + { + "name": "EIR1_EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2040"}, + "variants_include": ["Alt1"], + }, + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2040"}, + }, + { + "name": "MAJ_Bay_Area_Forward_all", + "kwargs": {"MODELYEAR": "2040"}, + }, + "FBP_SC_105_SanTomasWide", + "FBP_SC_102_CalaverasWide", + "FBP_CC_039_Eastbound24Wide", + {"name": "FBP_MU_064_SR37_LongTerm", "variants_exclude": ["Alt1"]}, + "FBP_SC_094_LawrenceWide", + "FBP_NP_066_Newell_Dr", + "EXP_Blueprint", + "FBP_AL_052_AutoMallWide", + "FBP_CC_038_SR242_Clayton_OnOffRamps", + "FBP_SC_047_I280_Winchester_OffRamp", + "FBP_SC_076_US101_Taylor_Interchange", + "FBP_NP_051_Airport_Junction", + "FBP_SC_101_BrokawBridgeWide", + "FBP_SC_081_US101_SR237", + "FBP_SC_088_Envision_Expwy", + {"name": "FBP_MU_056_Dumbarton_GRT", "variants_exclude": ["Alt2"]}, + { + "name": "EIR2_Val_Link_ExpressBus", + "kwargs": {"action": "'revert'"}, + "variants_include": ["Alt2"], + }, + {"name": "Transform_Valley_Link", "variants_include": ["Alt2"]}, + { + "name": "FBP_AL_021_South_Bay_Connect", + "variants_include": ["Alt2"], + }, + { + "name": "Transform_AC_Transbay_Improvements", + "variants_include": ["Alt2"], + }, + "FBP_SC_042_I280_Downtown_Access_Improvements", + ], + "trn": [ + { + "name": "FBP_MU_046_ACE_Freq_Inc", + "kwargs": {"MODELYEAR": "2040"}, + }, + "MAJ_Vasona_LRT_Extension", + "FBP_MU_029_ACRapid_2040", + "RRSP_EC_Cap_Imp_ECR_Bus", + "MAJ_SJC_People_Mover", + "FBP_NP_028_NapaVineRegRoutesFrequency", + "FBP_NP_034_NapaVineRegExpServiceHrs", + "FBP_NP_029_NapaVineLocExpServiceHrs", + "FBP_NP_033_Napa_PNR_Lots", + "FBP_SC_043_I280_Mainline_Impr", + "FBP_CC_018_BRT_Brentwood", + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2040"}, + }, + "MAJ_ElCaminoReal_BRT", + {"name": "FBP_MU_064_SR37_LongTerm", "variants_exclude": ["Alt1"]}, + "FBP_NP_051_Airport_Junction", + {"name": "FBP_MU_056_Dumbarton_GRT", "variants_exclude": ["Alt2"]}, + "FBP_SC_088_Envision_Expwy", + {"name": "HSR", "variants_exclude": ["Alt2"]}, + { + "name": "MAJ_SF_050002_Caltrain_Ext_TransbayTerminal", + "variants_include": ["Alt2"], + }, + { + "name": "EIR2_Val_Link_ExpressBus", + "kwargs": {"action": "'revert'"}, + "variants_include": ["Alt2"], + }, + {"name": "Transform_Valley_Link", "variants_include": ["Alt2"]}, + { + "name": "FBP_AL_021_South_Bay_Connect", + "variants_include": ["Alt2"], + }, + { + "name": "Transform_AC_Transbay_Improvements", + "variants_include": ["Alt2"], + }, + {"name": "EIR2_ReXGreen", "variants_include": ["Alt2"]}, + "FBP_CC_019_CCCTA_Freq_Increase", + { + "name": "EIR2_Fix_Alt2", + "kwargs": {"MODELYEAR": "2040"}, + "variants_include": ["Alt2"], + }, + ], + }, + ), + ( + 2045, + { + "hwy": [ + "BP_Vision_Zero", + { + "name": "EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2045"}, + "variants_exclude": ["Alt1", "NextGenFwy"], + }, + { + "name": "EXP_uncommitted_noAllLaneTolling", + "kwargs": {"MODELYEAR": "2045"}, + "variants_include": ["NextGenFwy"], + }, + { + "name": "EIR1_EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2045"}, + "variants_include": ["Alt1"], + }, + { + "name": "MAJ_Bay_Area_Forward_all", + "kwargs": {"MODELYEAR": "2045"}, + }, + {"name": "FBP_AL_048_SR262_Phase1", "variants_exclude": ["Alt1"]}, + "FBP_NP_045_SR29_Gateway_Impr", + "EXP_Blueprint", + ], + "trn": [ + { + "name": "FBP_MU_046_ACE_Freq_Inc", + "kwargs": {"MODELYEAR": "2045"}, + }, + "FBP_SC_106_VTA_LRT_Modernization", + ], + }, + ), + ( + 2050, + { + "hwy": [ + "BP_Vision_Zero", + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'6'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'7'"}, + "variants_exclude": ["Alt1"], + }, + "FBP_SC_028_Stevens_Creek_LRT", + { + "name": "MAJ_Bay_Area_Forward_all", + "kwargs": {"MODELYEAR": "2050"}, + }, + "EXP_Blueprint", + "FBP_SC_041_Envision_Highway_Minor", + "STIP_ITS_SM", + {"name": "BP_Transbay_Crossing", "variants_exclude": ["Alt2"]}, + ], + "trn": [ + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'6'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'7'"}, + "variants_exclude": ["Alt1"], + }, + "FBP_SC_028_Stevens_Creek_LRT", + {"name": "BP_Transbay_Crossing", "variants_exclude": ["Alt2"]}, + ], + }, + ), + ] +) # Put them together for NETWORK_PROJECTS -NETWORK_PROJECTS = collections.OrderedDict() +NETWORK_PROJECTS = collections.OrderedDict() for YEAR in COMMITTED_PROJECTS.keys(): if NET_VARIANT == "Baseline": # baseline: just committed NETWORK_PROJECTS[YEAR] = { - 'hwy':COMMITTED_PROJECTS[YEAR]['hwy'], - 'trn':COMMITTED_PROJECTS[YEAR]['trn'] + "hwy": COMMITTED_PROJECTS[YEAR]["hwy"], + "trn": COMMITTED_PROJECTS[YEAR]["trn"], } # todo: add sea level rise since it's unprotected @@ -472,28 +879,39 @@ # blueprint, alt1, alt2 NETWORK_PROJECTS[YEAR] = { - 'hwy':COMMITTED_PROJECTS[YEAR]['hwy'] + BLUEPRINT_PROJECTS[YEAR]['hwy'], - 'trn':COMMITTED_PROJECTS[YEAR]['trn'] + BLUEPRINT_PROJECTS[YEAR]['trn'] + "hwy": COMMITTED_PROJECTS[YEAR]["hwy"] + BLUEPRINT_PROJECTS[YEAR]["hwy"], + "trn": COMMITTED_PROJECTS[YEAR]["trn"] + BLUEPRINT_PROJECTS[YEAR]["trn"], } # handle net_remove, nets keywords - for netmode in ['hwy','trn']: + for netmode in ["hwy", "trn"]: # iterate backwards via index to delete cleanly - for project_idx in range(len(NETWORK_PROJECTS[YEAR][netmode])-1,-1,-1): + for project_idx in range(len(NETWORK_PROJECTS[YEAR][netmode]) - 1, -1, -1): project = NETWORK_PROJECTS[YEAR][netmode][project_idx] # special handling requires project to be specified as dictionary - if not isinstance(project, dict): continue + if not isinstance(project, dict): + continue # variants_exclude: specifies list of network variants for which this project should be *excluded* - if 'variants_exclude' in project.keys() and NET_VARIANT in project['variants_exclude']: - Wrangler.WranglerLogger.info("Removing {} {} {}".format(YEAR, netmode, project)) + if ( + "variants_exclude" in project.keys() + and NET_VARIANT in project["variants_exclude"] + ): + Wrangler.WranglerLogger.info( + "Removing {} {} {}".format(YEAR, netmode, project) + ) del NETWORK_PROJECTS[YEAR][netmode][project_idx] continue # variants_include: specifies list of network variants for which this project should be *included* # if this keyword is present, then this project is included *only* for variants in this list - if 'variants_include' in project.keys() and NET_VARIANT not in project['variants_include']: - Wrangler.WranglerLogger.info("Removing {} {} {}".format(YEAR, netmode, project)) + if ( + "variants_include" in project.keys() + and NET_VARIANT not in project["variants_include"] + ): + Wrangler.WranglerLogger.info( + "Removing {} {} {}".format(YEAR, netmode, project) + ) del NETWORK_PROJECTS[YEAR][netmode][project_idx] continue @@ -506,11 +924,15 @@ # for YEAR in NETWORK_PROJECTS.keys(): # if anything is applied - if ((len(NETWORK_PROJECTS[YEAR]['hwy']) > 0) or (len(NETWORK_PROJECTS[YEAR]['trn']) > 0)): - NETWORK_PROJECTS[YEAR]['hwy'].append('No_zero_length_links') + if (len(NETWORK_PROJECTS[YEAR]["hwy"]) > 0) or ( + len(NETWORK_PROJECTS[YEAR]["trn"]) > 0 + ): + NETWORK_PROJECTS[YEAR]["hwy"].append("No_zero_length_links") - if ((len(NETWORK_PROJECTS[YEAR]['hwy']) > 0) or (len(NETWORK_PROJECTS[YEAR]['trn']) > 0)): - NETWORK_PROJECTS[YEAR]['trn'].append('Move_buses_to_HOV_EXP_lanes') + if (len(NETWORK_PROJECTS[YEAR]["hwy"]) > 0) or ( + len(NETWORK_PROJECTS[YEAR]["trn"]) > 0 + ): + NETWORK_PROJECTS[YEAR]["trn"].append("Move_buses_to_HOV_EXP_lanes") # OPTIONAL. The default route network project directory is Y:\networks. If diff --git a/scripts/net_spec_horizon.py b/scripts/net_spec_horizon.py index 8367a7e..2bea3c1 100644 --- a/scripts/net_spec_horizon.py +++ b/scripts/net_spec_horizon.py @@ -1,4 +1,5 @@ import os + # MANDATORY. Set this to be the Project Name. # e.g. "RTP2021", "TIP2021", etc # PROJECT = "FU1" or "PPA", set by build_network_mtc_futures.py based on argument @@ -16,248 +17,432 @@ ########################################################### # For Round 1 and Project Performance Assessment Base both -COMMITTED_PROJECTS = collections.OrderedDict([ - (2015, { - 'hwy':['PROJ_attributes', # adds PROJ attributes to NODE and LINK - {'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2015'}}], - 'trn':[] - }), - (2020, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2020'}}, - {'name':'EXP_237B', 'kwargs':{'FUTURE':SCENARIO}}, - 'EXP_580C', - 'EXP_680D', - 'EXP_680F', - 'SCL130001_237_101_MAT_Int_Mod', - 'REG090003_SCLARA_FIP', - 'ALA130005_Dougherty_road_widening', - 'ALA130006_Dublin_Blvd_widening', - 'ALA130014_7th_St_road_diet', - 'ALA130026_Shattuck_Complete_Streets', - 'ALA170049_Central_AVE_Safety_Improvements', - 'ALA170052_Fruitvale_Ave_ped_improvements', - 'ALA150004_EastBay_BRT', - 'CC_130001_BaileyRd_SR4', - 'CC_130046_I680_SR4_Int_Rec', - 'CC_070035_I80_SPDamRd_Int_Phase1', - 'CC_070011_Brentwood_Blvd_Widening', - 'CC_070075_Kirker_Pass_Truck_Lane', - 'CC_090019_Bollinger_Canyon_Widening', - 'CC_130006_Concord_BART_road_diets', - 'CC_170001_SanRamonValleyBlvd_Lane_Addition', - 'CC_170061_Bus_On_Shoulder_680BRT', - 'MRN150009_San_Rafael_Bridge_Improvements', - 'SF_070027_Yerba_Buena_Ramp_Imp', - 'SF_070005_VanNess_BRT', - 'SF_130011_2ndSt_Road_Diet', - 'SM_110047_SR92_ElCam_Ramp_Mod', - 'SOL110005_Jepson_Van_to_Com', - 'SON070004_101_MarinSonNarrows_Phase1', - 'ALA050014_SR84_Widening', - 'ALA170011_BayBridge_HOV_Connectors', - 'ALA150047_TelegraphAve_Complete_Streets', - 'SM_110047_SR92_ElCam_Ramp_Mod', - 'SCL190002_280_Foothill_improvement', - 'SCL190006_101SB_offramp_improvement', - 'I80_AdaptiveRampMetering', - 'VAR170021_Freeway_Performance_I880', - 'SonomaCounty_Transit_NoBuild2050', - 'SF_MuniForward_Committed'], - 'trn':['ALA050015_BART_to_WarmSprings', - 'ACGo', - 'CC_050025_EBart_to_Antioch', - 'SCL110005_BART_to_Berryessa', - 'SF_010015_Transbay_Terminal', - 'SF_010037_Muni_Central_Subway', - 'SF_070027_Yerba_Buena_Ramp_Imp', - 'SOL030002_FairfieldVacaville_Stn', - 'SON090002_SMART', - 'SON090002_SMART_to_Larkspur', - 'CC_070062_Richmond_Ferry', - 'SF_MuniForward_Committed', - 'VTA_Next', - 'SCL130001_237_101_MAT_Int_Mod', - 'SonomaCounty_Transit_NoBuild2050', - 'SMART_Novato', - 'Xfare_update_2020', - 'ACTransit_Committed', - 'ferry_update_2019', - 'SamTrans_ECR_Rapid'], - }), - (2025, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2025'}}, - 'EXP_CC_050028_I680_SB_HOV_Completion', - 'EXP_101B1', - 'EXP_101B2', - 'EXP_680C1', - 'EXP_85C', - 'EXP_101C', - 'ALA150001_I680_SR84_Int_Wid', - 'ALA150043_Claremont_road_diet', - 'CC_070009_Slatten_Ranch_Rd_Extension', - 'SF_070004_Geary_BRT_Phase1', - 'SON070004_101_MarinSonNarrows_Phase2', - 'SOL110006_Jepson_1B_1C', - 'SCL190008_US101_DLC_Int_Imp', - 'I880_US101_AdaptiveRampMetering', - 'MAJ_SCL050009_VTA_Eastridge_Extension', - 'SOL070020_I80_I680_SR12_Int_1_2A'], - 'trn':['SF_010028_Caltrain_Modernization', - 'SON090002_SMART_to_Windsor', - 'MAJ_SCL050009_VTA_Eastridge_Extension', - 'REG090037_New_BART_Trains', - 'SOL070020_I80_I680_SR12_Int_1_2A'] - }), - (2030, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2030'}}, - 'EXP_880A'], - 'trn':['BART_NoProject'] - }), - (2035, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2035'}}], - 'trn':[] - }), - (2040, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2040'}}], - 'trn':[] - }), - (2045, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2045'}}], - 'trn':[] - }), - (2050, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2050'}}], - 'trn':[] - }) -]) +COMMITTED_PROJECTS = collections.OrderedDict( + [ + ( + 2015, + { + "hwy": [ + "PROJ_attributes", # adds PROJ attributes to NODE and LINK + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2015"}, + }, + ], + "trn": [], + }, + ), + ( + 2020, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2020"}, + }, + {"name": "EXP_237B", "kwargs": {"FUTURE": SCENARIO}}, + "EXP_580C", + "EXP_680D", + "EXP_680F", + "SCL130001_237_101_MAT_Int_Mod", + "REG090003_SCLARA_FIP", + "ALA130005_Dougherty_road_widening", + "ALA130006_Dublin_Blvd_widening", + "ALA130014_7th_St_road_diet", + "ALA130026_Shattuck_Complete_Streets", + "ALA170049_Central_AVE_Safety_Improvements", + "ALA170052_Fruitvale_Ave_ped_improvements", + "ALA150004_EastBay_BRT", + "CC_130001_BaileyRd_SR4", + "CC_130046_I680_SR4_Int_Rec", + "CC_070035_I80_SPDamRd_Int_Phase1", + "CC_070011_Brentwood_Blvd_Widening", + "CC_070075_Kirker_Pass_Truck_Lane", + "CC_090019_Bollinger_Canyon_Widening", + "CC_130006_Concord_BART_road_diets", + "CC_170001_SanRamonValleyBlvd_Lane_Addition", + "CC_170061_Bus_On_Shoulder_680BRT", + "MRN150009_San_Rafael_Bridge_Improvements", + "SF_070027_Yerba_Buena_Ramp_Imp", + "SF_070005_VanNess_BRT", + "SF_130011_2ndSt_Road_Diet", + "SM_110047_SR92_ElCam_Ramp_Mod", + "SOL110005_Jepson_Van_to_Com", + "SON070004_101_MarinSonNarrows_Phase1", + "ALA050014_SR84_Widening", + "ALA170011_BayBridge_HOV_Connectors", + "ALA150047_TelegraphAve_Complete_Streets", + "SM_110047_SR92_ElCam_Ramp_Mod", + "SCL190002_280_Foothill_improvement", + "SCL190006_101SB_offramp_improvement", + "I80_AdaptiveRampMetering", + "VAR170021_Freeway_Performance_I880", + "SonomaCounty_Transit_NoBuild2050", + "SF_MuniForward_Committed", + ], + "trn": [ + "ALA050015_BART_to_WarmSprings", + "ACGo", + "CC_050025_EBart_to_Antioch", + "SCL110005_BART_to_Berryessa", + "SF_010015_Transbay_Terminal", + "SF_010037_Muni_Central_Subway", + "SF_070027_Yerba_Buena_Ramp_Imp", + "SOL030002_FairfieldVacaville_Stn", + "SON090002_SMART", + "SON090002_SMART_to_Larkspur", + "CC_070062_Richmond_Ferry", + "SF_MuniForward_Committed", + "VTA_Next", + "SCL130001_237_101_MAT_Int_Mod", + "SonomaCounty_Transit_NoBuild2050", + "SMART_Novato", + "Xfare_update_2020", + "ACTransit_Committed", + "ferry_update_2019", + "SamTrans_ECR_Rapid", + ], + }, + ), + ( + 2025, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2025"}, + }, + "EXP_CC_050028_I680_SB_HOV_Completion", + "EXP_101B1", + "EXP_101B2", + "EXP_680C1", + "EXP_85C", + "EXP_101C", + "ALA150001_I680_SR84_Int_Wid", + "ALA150043_Claremont_road_diet", + "CC_070009_Slatten_Ranch_Rd_Extension", + "SF_070004_Geary_BRT_Phase1", + "SON070004_101_MarinSonNarrows_Phase2", + "SOL110006_Jepson_1B_1C", + "SCL190008_US101_DLC_Int_Imp", + "I880_US101_AdaptiveRampMetering", + "MAJ_SCL050009_VTA_Eastridge_Extension", + "SOL070020_I80_I680_SR12_Int_1_2A", + ], + "trn": [ + "SF_010028_Caltrain_Modernization", + "SON090002_SMART_to_Windsor", + "MAJ_SCL050009_VTA_Eastridge_Extension", + "REG090037_New_BART_Trains", + "SOL070020_I80_I680_SR12_Int_1_2A", + ], + }, + ), + ( + 2030, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2030"}, + }, + "EXP_880A", + ], + "trn": ["BART_NoProject"], + }, + ), + ( + 2035, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2035"}, + } + ], + "trn": [], + }, + ), + ( + 2040, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2040"}, + } + ], + "trn": [], + }, + ), + ( + 2045, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2045"}, + } + ], + "trn": [], + }, + ), + ( + 2050, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2050"}, + } + ], + "trn": [], + }, + ), + ] +) ########################################################### # Round1 and Round2 Major Projects -MAJOR_PROJECTS = collections.OrderedDict([ - (2015, {'hwy':[{'name':'Bridge_Toll_Updates_3pct', 'kwargs':{'MODELYEAR':'2015'}}], - 'trn':[] - }), - (2020, {'hwy':[{'name':'Bridge_Toll_Updates_3pct', 'kwargs':{'MODELYEAR':'2020'}}], - 'trn':['MAJ_Sonoma_Frequency_Increase'] - }), - (2025, {'hwy':[{'name':'Bridge_Toll_Updates_3pct', 'kwargs':{'MODELYEAR':'2025'}}, - 'MAJ_Geary_BRT_Phase2', - 'MAJ_SF_Congestion_Pricing', - 'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', - 'MAJ_Bay_Area_Forward_committed',], - 'trn':['MAJ_Geary_BRT_Phase2', - 'MAJ_SOL070020_I80_I680_SR12_Int_2B_7', - 'MAJ_SF_Congestion_Pricing'] - }), - (2030, {'hwy':[{'name':'Bridge_Toll_Updates_3pct', 'kwargs':{'MODELYEAR':'2030'}}, - 'MAJ_SanPablo_BRT', - 'MAJ_ElCaminoReal_BRT', - 'MAJ_I680_SR4_Int_Widening_Phases_3_5', - 'MAJ_SR4_Operational_Improvements', - 'MAJ_Better_Market_St'], - 'trn':['MAJ_BRT030001_BART_to_SanJose', - 'MAJ_SF_050002_Caltrain_Ext_TransbayTerminal', - 'MAJ_WETA_Service_Frequency_Increase', - 'MAJ_Alameda_Point_SF_Ferry', - 'MAJ_MissionBay_SF_Ferry', - 'MAJ_RedwoodCity_SF_Ferry', - 'MAJ_MTC050027_Berkeley_Ferry', - 'MAJ_Better_Market_St', - 'MAJ_REG090037_BART_Core_Cap'] - }), - (2035, {'hwy':[{'name':'Bridge_Toll_Updates_3pct', 'kwargs':{'MODELYEAR':'2035'}}, - 'MAJ_Treasure_Island_Congestion_Pricing'], - 'trn':['MAJ_MuniForward_Uncommitted', - 'MAJ_Vasona_LRT_Extension', - 'MAJ_Treasure_Island_Congestion_Pricing'] - }), - (2040, {'hwy':[{'name':'Bridge_Toll_Updates_3pct', 'kwargs':{'MODELYEAR':'2040'}}], - 'trn':[] - }), - (2045, {'hwy':[{'name':'Bridge_Toll_Updates_3pct', 'kwargs':{'MODELYEAR':'2045'}}], - 'trn':[] - }), - (2050, {'hwy':[{'name':'Bridge_Toll_Updates_3pct', 'kwargs':{'MODELYEAR':'2050'}}], - 'trn':[] - }) - ]) +MAJOR_PROJECTS = collections.OrderedDict( + [ + ( + 2015, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_3pct", + "kwargs": {"MODELYEAR": "2015"}, + } + ], + "trn": [], + }, + ), + ( + 2020, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_3pct", + "kwargs": {"MODELYEAR": "2020"}, + } + ], + "trn": ["MAJ_Sonoma_Frequency_Increase"], + }, + ), + ( + 2025, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_3pct", + "kwargs": {"MODELYEAR": "2025"}, + }, + "MAJ_Geary_BRT_Phase2", + "MAJ_SF_Congestion_Pricing", + "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "MAJ_Bay_Area_Forward_committed", + ], + "trn": [ + "MAJ_Geary_BRT_Phase2", + "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "MAJ_SF_Congestion_Pricing", + ], + }, + ), + ( + 2030, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_3pct", + "kwargs": {"MODELYEAR": "2030"}, + }, + "MAJ_SanPablo_BRT", + "MAJ_ElCaminoReal_BRT", + "MAJ_I680_SR4_Int_Widening_Phases_3_5", + "MAJ_SR4_Operational_Improvements", + "MAJ_Better_Market_St", + ], + "trn": [ + "MAJ_BRT030001_BART_to_SanJose", + "MAJ_SF_050002_Caltrain_Ext_TransbayTerminal", + "MAJ_WETA_Service_Frequency_Increase", + "MAJ_Alameda_Point_SF_Ferry", + "MAJ_MissionBay_SF_Ferry", + "MAJ_RedwoodCity_SF_Ferry", + "MAJ_MTC050027_Berkeley_Ferry", + "MAJ_Better_Market_St", + "MAJ_REG090037_BART_Core_Cap", + ], + }, + ), + ( + 2035, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_3pct", + "kwargs": {"MODELYEAR": "2035"}, + }, + "MAJ_Treasure_Island_Congestion_Pricing", + ], + "trn": [ + "MAJ_MuniForward_Uncommitted", + "MAJ_Vasona_LRT_Extension", + "MAJ_Treasure_Island_Congestion_Pricing", + ], + }, + ), + ( + 2040, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_3pct", + "kwargs": {"MODELYEAR": "2040"}, + } + ], + "trn": [], + }, + ), + ( + 2045, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_3pct", + "kwargs": {"MODELYEAR": "2045"}, + } + ], + "trn": [], + }, + ), + ( + 2050, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_3pct", + "kwargs": {"MODELYEAR": "2050"}, + } + ], + "trn": [], + }, + ), + ] +) ########################################################### # Round2 Transformative Projects -- Note that some are not included in Rising Tides, Falling Fortunes # RAWG June 2019 Agenda Item 3: Horizon - Futures Round 2: Final Strategies for Round 2 Analysis # https://mtc.legistar.com/View.ashx?M=F&ID=7281685&GUID=82EACC9C-82E9-4895-85CF-29F8C3CD9EF0 -TRANSFORM_PROJECTS = collections.OrderedDict([ - (2015, {# all futures - 'hwy_all' :[], - 'trn_all' :[], - # CleanAndGreen, BackToTheFuture only - 'hwy_cgbf':[], - 'trn_cgbf':[] - }), - (2020, {# all futures - 'hwy_all' :[], - 'trn_all' :[], - # CleanAndGreen, BackToTheFuture only - 'hwy_cgbf':[], - 'trn_cgbf':[] - }), - (2025, {# all futures - 'hwy_all' :[{'name':'EXP_uncommitted_all', 'kwargs':{'FUTURE':SCENARIO}}, - 'Futures_H10_Vision_Zero'], - 'trn_all' :[{'name':'EXP_uncommitted_all', 'kwargs':{'FUTURE':'"{}"'.format(SCENARIO)}}, # pass as quoted string - 'Futures_H10_Vision_Zero'], - # CleanAndGreen, BackToTheFuture only - 'hwy_cgbf':['Transform_Valley_Link'], - 'trn_cgbf':['Transform_Valley_Link'], - - }), - (2030, {# all futures - 'hwy_all' :['EXP_gapclosure', - 'Futures_C4_ReX_Express'], - 'trn_all' :['EXP_gapclosure', - 'Futures_C4_ReX_Express'], - # CleanAndGreen, BackToTheFuture only - 'hwy_cgbf':['Transform_Dumbarton_Rail'], - 'trn_cgbf':['MAJ_SanJose_LRT_Subway', - 'Transform_Muni_Southwest_Subway', - 'MAJ_EBart_Extension_Brentwood', - 'Futures_SMART_to_Healdsburg', - 'Transform_Dumbarton_Rail'] - }), - (2035, {# all futures - 'hwy_all' :[], - 'trn_all' :[], - # CleanAndGreen, BackToTheFuture only - 'hwy_cgbf':[], - 'trn_cgbf':[] - }), - (2040, {# all futures - 'hwy_all' :[], - 'trn_all' :[], - # CleanAndGreen, BackToTheFuture only - 'hwy_cgbf':['Transform_Richmond_Bridge_Replacement', - 'Transform_SMART_To_Richmond'], - 'trn_cgbf':['Transform_VTA_NorthSJ_Subway', - 'Transform_SMART_To_Richmond', - 'Transform_VTA_LRT_FullGradeSep'] - }), - (2045, {# all futures - 'hwy_all' :[], - 'trn_all' :[], - # CleanAndGreen, BackToTheFuture only - 'hwy_cgbf':[], - 'trn_cgbf':['Transform_VTA_LRT_Automation'] - }), - (2050, {# all futures - 'hwy_all' :['Futures_PBA5_HOT_to_HOV3', - 'Futures_C6_Tolls_on_Freeways'], - 'trn_all' :['Futures_PBA5_HOT_to_HOV3', - 'Futures_C6_Tolls_on_Freeways'], - # CleanAndGreen, BackToTheFuture only - 'hwy_cgbf':['Crossings3'], - 'trn_cgbf':['Crossings3'] - }) -]) +TRANSFORM_PROJECTS = collections.OrderedDict( + [ + ( + 2015, + { # all futures + "hwy_all": [], + "trn_all": [], + # CleanAndGreen, BackToTheFuture only + "hwy_cgbf": [], + "trn_cgbf": [], + }, + ), + ( + 2020, + { # all futures + "hwy_all": [], + "trn_all": [], + # CleanAndGreen, BackToTheFuture only + "hwy_cgbf": [], + "trn_cgbf": [], + }, + ), + ( + 2025, + { # all futures + "hwy_all": [ + {"name": "EXP_uncommitted_all", "kwargs": {"FUTURE": SCENARIO}}, + "Futures_H10_Vision_Zero", + ], + "trn_all": [ + { + "name": "EXP_uncommitted_all", + "kwargs": {"FUTURE": '"{}"'.format(SCENARIO)}, + }, # pass as quoted string + "Futures_H10_Vision_Zero", + ], + # CleanAndGreen, BackToTheFuture only + "hwy_cgbf": ["Transform_Valley_Link"], + "trn_cgbf": ["Transform_Valley_Link"], + }, + ), + ( + 2030, + { # all futures + "hwy_all": ["EXP_gapclosure", "Futures_C4_ReX_Express"], + "trn_all": ["EXP_gapclosure", "Futures_C4_ReX_Express"], + # CleanAndGreen, BackToTheFuture only + "hwy_cgbf": ["Transform_Dumbarton_Rail"], + "trn_cgbf": [ + "MAJ_SanJose_LRT_Subway", + "Transform_Muni_Southwest_Subway", + "MAJ_EBart_Extension_Brentwood", + "Futures_SMART_to_Healdsburg", + "Transform_Dumbarton_Rail", + ], + }, + ), + ( + 2035, + { # all futures + "hwy_all": [], + "trn_all": [], + # CleanAndGreen, BackToTheFuture only + "hwy_cgbf": [], + "trn_cgbf": [], + }, + ), + ( + 2040, + { # all futures + "hwy_all": [], + "trn_all": [], + # CleanAndGreen, BackToTheFuture only + "hwy_cgbf": [ + "Transform_Richmond_Bridge_Replacement", + "Transform_SMART_To_Richmond", + ], + "trn_cgbf": [ + "Transform_VTA_NorthSJ_Subway", + "Transform_SMART_To_Richmond", + "Transform_VTA_LRT_FullGradeSep", + ], + }, + ), + ( + 2045, + { # all futures + "hwy_all": [], + "trn_all": [], + # CleanAndGreen, BackToTheFuture only + "hwy_cgbf": [], + "trn_cgbf": ["Transform_VTA_LRT_Automation"], + }, + ), + ( + 2050, + { # all futures + "hwy_all": ["Futures_PBA5_HOT_to_HOV3", "Futures_C6_Tolls_on_Freeways"], + "trn_all": ["Futures_PBA5_HOT_to_HOV3", "Futures_C6_Tolls_on_Freeways"], + # CleanAndGreen, BackToTheFuture only + "hwy_cgbf": ["Crossings3"], + "trn_cgbf": ["Crossings3"], + }, + ), + ] +) # Put them together for NETWORK_PROJECTS NETWORK_PROJECTS = collections.OrderedDict() @@ -265,108 +450,118 @@ # Future Round1 includes major projects if PROJECT == "FU1": NETWORK_PROJECTS[YEAR] = { - 'hwy':COMMITTED_PROJECTS[YEAR]['hwy'] + MAJOR_PROJECTS[YEAR]['hwy'], - 'trn':COMMITTED_PROJECTS[YEAR]['trn'] + MAJOR_PROJECTS[YEAR]['trn'] + "hwy": COMMITTED_PROJECTS[YEAR]["hwy"] + MAJOR_PROJECTS[YEAR]["hwy"], + "trn": COMMITTED_PROJECTS[YEAR]["trn"] + MAJOR_PROJECTS[YEAR]["trn"], } elif PROJECT == "FU2": # only use those marked hwy_all, trn_all if SCENARIO == "RisingTides": NETWORK_PROJECTS[YEAR] = { - 'hwy':COMMITTED_PROJECTS[YEAR]['hwy'] + MAJOR_PROJECTS[YEAR]['hwy'] + TRANSFORM_PROJECTS[YEAR]['hwy_all'], - 'trn':COMMITTED_PROJECTS[YEAR]['trn'] + MAJOR_PROJECTS[YEAR]['trn'] + TRANSFORM_PROJECTS[YEAR]['trn_all'] + "hwy": COMMITTED_PROJECTS[YEAR]["hwy"] + + MAJOR_PROJECTS[YEAR]["hwy"] + + TRANSFORM_PROJECTS[YEAR]["hwy_all"], + "trn": COMMITTED_PROJECTS[YEAR]["trn"] + + MAJOR_PROJECTS[YEAR]["trn"] + + TRANSFORM_PROJECTS[YEAR]["trn_all"], } - elif SCENARIO in ["CleanAndGreen","BackToTheFuture"]: + elif SCENARIO in ["CleanAndGreen", "BackToTheFuture"]: NETWORK_PROJECTS[YEAR] = { - 'hwy':COMMITTED_PROJECTS[YEAR]['hwy'] + MAJOR_PROJECTS[YEAR]['hwy'] + TRANSFORM_PROJECTS[YEAR]['hwy_all'] + TRANSFORM_PROJECTS[YEAR]['hwy_cgbf'], - 'trn':COMMITTED_PROJECTS[YEAR]['trn'] + MAJOR_PROJECTS[YEAR]['trn'] + TRANSFORM_PROJECTS[YEAR]['trn_all'] + TRANSFORM_PROJECTS[YEAR]['trn_cgbf'] + "hwy": COMMITTED_PROJECTS[YEAR]["hwy"] + + MAJOR_PROJECTS[YEAR]["hwy"] + + TRANSFORM_PROJECTS[YEAR]["hwy_all"] + + TRANSFORM_PROJECTS[YEAR]["hwy_cgbf"], + "trn": COMMITTED_PROJECTS[YEAR]["trn"] + + MAJOR_PROJECTS[YEAR]["trn"] + + TRANSFORM_PROJECTS[YEAR]["trn_all"] + + TRANSFORM_PROJECTS[YEAR]["trn_cgbf"], } elif PROJECT in ["PPA", "PPA_NoSLR"]: # Project Performance Assessment baseline does not include major projects NETWORK_PROJECTS[YEAR] = { - 'hwy':COMMITTED_PROJECTS[YEAR]['hwy'], - 'trn':COMMITTED_PROJECTS[YEAR]['trn'] + "hwy": COMMITTED_PROJECTS[YEAR]["hwy"], + "trn": COMMITTED_PROJECTS[YEAR]["trn"], } # done at the end in case they need to remove transit project links # remove "False and" clauses when these are coded -if SCENARIO=="CleanAndGreen": +if SCENARIO == "CleanAndGreen": if PROJECT == "FU1": # High Speed Rail is only included in Round1, not Project Performance Baseline nor Round2 - NETWORK_PROJECTS[2030]['trn'].append("HSR") + NETWORK_PROJECTS[2030]["trn"].append("HSR") elif PROJECT == "FU2": # this needs to be added before Earthquake or it's lost - NETWORK_PROJECTS[2035]['trn'].append("Caltrain_PCBB10_HSR") + NETWORK_PROJECTS[2035]["trn"].append("Caltrain_PCBB10_HSR") # NOTE: Earthquake is assumed in Round1 2035 but since the effect doesn't stay; this is handled in build_network_mtc_futures.py - if PROJECT in ["FU1","FU2"]: + if PROJECT in ["FU1", "FU2"]: # Haywired Earthquake in 2035 - NETWORK_PROJECTS[2035]['hwy'].append("Earthquake") - NETWORK_PROJECTS[2035]['trn'].append("Earthquake") + NETWORK_PROJECTS[2035]["hwy"].append("Earthquake") + NETWORK_PROJECTS[2035]["trn"].append("Earthquake") # Sea Level Rise in FU1 and PPA (but not PPA_NoSLR) - if PROJECT in ["FU1","PPA"]: + if PROJECT in ["FU1", "PPA"]: # Sea Level Rise in 2045 - NETWORK_PROJECTS[2045]['hwy'].append("SeaLevelRise_1foot") - NETWORK_PROJECTS[2045]['trn'].append("SeaLevelRise_1foot") + NETWORK_PROJECTS[2045]["hwy"].append("SeaLevelRise_1foot") + NETWORK_PROJECTS[2045]["trn"].append("SeaLevelRise_1foot") # But we've adapted in FU2 (also add caltrain PCBB project without HSR) elif PROJECT == "FU2": - NETWORK_PROJECTS[2045]['hwy'].append("SeaLevelRise_HighAdaptation") - NETWORK_PROJECTS[2045]['trn'].append("SeaLevelRise_HighAdaptation") + NETWORK_PROJECTS[2045]["hwy"].append("SeaLevelRise_HighAdaptation") + NETWORK_PROJECTS[2045]["trn"].append("SeaLevelRise_HighAdaptation") -elif SCENARIO=="RisingTides": +elif SCENARIO == "RisingTides": # Sea Level Rise in FU1 and PPA (but not PPA_NoSLR) if PROJECT in ["FU1", "PPA"]: # Sea Level Rise in 2030 - NETWORK_PROJECTS[2030]['hwy'].append("SeaLevelRise_1foot") - NETWORK_PROJECTS[2030]['trn'].append("SeaLevelRise_1foot") + NETWORK_PROJECTS[2030]["hwy"].append("SeaLevelRise_1foot") + NETWORK_PROJECTS[2030]["trn"].append("SeaLevelRise_1foot") # But we've adapted in FU2 elif PROJECT == "FU2": - NETWORK_PROJECTS[2030]['hwy'].append("SeaLevelRise_LowAdaptation") - NETWORK_PROJECTS[2030]['trn'].append("SeaLevelRise_LowAdaptation") + NETWORK_PROJECTS[2030]["hwy"].append("SeaLevelRise_LowAdaptation") + NETWORK_PROJECTS[2030]["trn"].append("SeaLevelRise_LowAdaptation") - NETWORK_PROJECTS[2030]['hwy'].append("SeaLevelRise_HighAdaptation") - NETWORK_PROJECTS[2030]['trn'].append("SeaLevelRise_HighAdaptation") + NETWORK_PROJECTS[2030]["hwy"].append("SeaLevelRise_HighAdaptation") + NETWORK_PROJECTS[2030]["trn"].append("SeaLevelRise_HighAdaptation") - if PROJECT in ["FU1","FU2"]: + if PROJECT in ["FU1", "FU2"]: # Haywired Earthquake in 2035 - NETWORK_PROJECTS[2035]['hwy'].append("Earthquake") - NETWORK_PROJECTS[2035]['trn'].append("Earthquake") + NETWORK_PROJECTS[2035]["hwy"].append("Earthquake") + NETWORK_PROJECTS[2035]["trn"].append("Earthquake") # Sea Level Rise in FU1 and PPA (but not PPA_NoSLR) if PROJECT in ["FU1", "PPA"]: # Sea Level Rise in 2040 - NETWORK_PROJECTS[2040]['hwy'].append("SeaLevelRise_2feet") - NETWORK_PROJECTS[2040]['trn'].append("SeaLevelRise_2feet") + NETWORK_PROJECTS[2040]["hwy"].append("SeaLevelRise_2feet") + NETWORK_PROJECTS[2040]["trn"].append("SeaLevelRise_2feet") # Sea Level Rise in 2050 - NETWORK_PROJECTS[2050]['hwy'].append("SeaLevelRise_3feet") - NETWORK_PROJECTS[2050]['trn'].append("SeaLevelRise_3feet") + NETWORK_PROJECTS[2050]["hwy"].append("SeaLevelRise_3feet") + NETWORK_PROJECTS[2050]["trn"].append("SeaLevelRise_3feet") # In FU2, adaptation still holds but no need to reapply elif PROJECT == "FU2": pass -elif SCENARIO=="BackToTheFuture": +elif SCENARIO == "BackToTheFuture": # Sea Level Rise in FU1 and PPA (but not PPA_NoSLR) - if PROJECT in ["FU1", "PPA"]: - NETWORK_PROJECTS[2035]['hwy'].append("SeaLevelRise_1foot") - NETWORK_PROJECTS[2035]['trn'].append("SeaLevelRise_1foot") + if PROJECT in ["FU1", "PPA"]: + NETWORK_PROJECTS[2035]["hwy"].append("SeaLevelRise_1foot") + NETWORK_PROJECTS[2035]["trn"].append("SeaLevelRise_1foot") # But we've adapted in FU2 (also add caltrain PCBB without HSR) elif PROJECT == "FU2": - NETWORK_PROJECTS[2035]['hwy'].append("SeaLevelRise_HighAdaptation") - NETWORK_PROJECTS[2035]['trn'].append("SeaLevelRise_HighAdaptation") - NETWORK_PROJECTS[2035]['trn'].append("Caltrain_PCBB10_NoHSR") + NETWORK_PROJECTS[2035]["hwy"].append("SeaLevelRise_HighAdaptation") + NETWORK_PROJECTS[2035]["trn"].append("SeaLevelRise_HighAdaptation") + NETWORK_PROJECTS[2035]["trn"].append("Caltrain_PCBB10_NoHSR") # NOTE: Earthquake is assumed in Round1 2035 but since the effect doesn't stay; this is handled in build_network_mtc_futures.py - if PROJECT in ["FU1","FU2"]: + if PROJECT in ["FU1", "FU2"]: # Haywired Earthquake in 2035 - NETWORK_PROJECTS[2035]['hwy'].append("Earthquake") - NETWORK_PROJECTS[2035]['trn'].append("Earthquake") + NETWORK_PROJECTS[2035]["hwy"].append("Earthquake") + NETWORK_PROJECTS[2035]["trn"].append("Earthquake") # Sea Level Rise in FU1 and PPA (but not PPA_NoSLR) if PROJECT in ["FU1", "PPA"]: - NETWORK_PROJECTS[2050]['hwy'].append("SeaLevelRise_2feet") - NETWORK_PROJECTS[2050]['trn'].append("SeaLevelRise_2feet") + NETWORK_PROJECTS[2050]["hwy"].append("SeaLevelRise_2feet") + NETWORK_PROJECTS[2050]["trn"].append("SeaLevelRise_2feet") # In FU2, adaptation still holds but no need to reapply elif PROJECT == "FU2": pass @@ -378,11 +573,15 @@ # for YEAR in NETWORK_PROJECTS.keys(): # if anything is applied - if ((len(NETWORK_PROJECTS[YEAR]['hwy']) > 0) or (len(NETWORK_PROJECTS[YEAR]['trn']) > 0)): - NETWORK_PROJECTS[YEAR]['hwy'].append('No_zero_length_links') + if (len(NETWORK_PROJECTS[YEAR]["hwy"]) > 0) or ( + len(NETWORK_PROJECTS[YEAR]["trn"]) > 0 + ): + NETWORK_PROJECTS[YEAR]["hwy"].append("No_zero_length_links") - if ((len(NETWORK_PROJECTS[YEAR]['hwy']) > 0) or (len(NETWORK_PROJECTS[YEAR]['trn']) > 0)): - NETWORK_PROJECTS[YEAR]['trn'].append('Move_buses_to_HOV_EXP_lanes') + if (len(NETWORK_PROJECTS[YEAR]["hwy"]) > 0) or ( + len(NETWORK_PROJECTS[YEAR]["trn"]) > 0 + ): + NETWORK_PROJECTS[YEAR]["trn"].append("Move_buses_to_HOV_EXP_lanes") # OPTIONAL. The default route network project directory is Y:\networks. If diff --git a/scripts/net_spec_transit_recovery.py b/scripts/net_spec_transit_recovery.py index 9606929..807095d 100644 --- a/scripts/net_spec_transit_recovery.py +++ b/scripts/net_spec_transit_recovery.py @@ -1,15 +1,16 @@ import os import collections + # MANDATORY. Set this to be the Project Name. # e.g. "RTP2021", "TIP2021", etc -PROJECT = "TransitRecov" +PROJECT = "TransitRecov" # MANDATORY. Set this to be the git tag for checking out network projects. -#TAG = "HEAD" # Use this tag if you want NetworkWrangler to use the latest version in the local repo to build the network -#TAG = "PBA50_Blueprint" # Use this tag if you want to replicate the network built for PBA50 +# TAG = "HEAD" # Use this tag if you want NetworkWrangler to use the latest version in the local repo to build the network +# TAG = "PBA50_Blueprint" # Use this tag if you want to replicate the network built for PBA50 TAG = "HEAD" -# MANDATORY. Set this to the directory in which to write your outputs. +# MANDATORY. Set this to the directory in which to write your outputs. # "hwy" and "trn" subdirectories will be created here. OUT_DIR = "{}_network_".format(SCENARIO) + "{}" @@ -19,303 +20,485 @@ # For example: # {'name':"Muni_TEP", 'kwargs':{'servicePlan':"'2012oct'"}} ########################################################### -COMMITTED_PROJECTS = collections.OrderedDict([ - (2015, { - 'hwy':['PROJ_attributes', # adds PROJ attributes to NODE and LINK - {'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2015'}}], - 'trn':[] - }), - (2020, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2020'}}, - {'name':'EXP_237B', 'kwargs':{'FUTURE':"PBA50"}}, # todo: update this to support PBA50 - 'EXP_580C', - 'EXP_680D', - 'EXP_880A', - 'HOV_680F', - 'SCL130001_237_101_MAT_Int_Mod', - 'REG090003_SCLARA_FIP', - 'ALA130005_Dougherty_road_widening', - 'ALA130006_Dublin_Blvd_widening', - 'ALA130014_7th_St_road_diet', - 'ALA130026_Shattuck_Complete_Streets', - 'ALA170049_Central_AVE_Safety_Improvements', - 'ALA150004_EastBay_BRT', - 'CC_130001_BaileyRd_SR4', - 'CC_130046_I680_SR4_Int_Rec', - 'CC_070035_I80_SPDamRd_Int_Phase1', - 'CC_070011_Brentwood_Blvd_Widening', - 'CC_070075_Kirker_Pass_Truck_Lane', - 'CC_090019_Bollinger_Canyon_Widening', - 'CC_130006_Concord_BART_road_diets', - 'CC_170001_SanRamonValleyBlvd_Lane_Addition', - 'MRN150009_San_Rafael_Bridge_Improvements', - 'SF_070027_Yerba_Buena_Ramp_Imp', - 'SF_070005_VanNess_BRT', - 'SF_130011_2ndSt_Road_Diet', - 'SF_Market_Street_Closure', - 'SM_110047_SR92_ElCam_Ramp_Mod', - 'SOL110005_Jepson_Van_to_Com', - 'FBP_SL_042_Jepson_2A', - 'SON070004_101_MarinSonNarrows_Phase1', - 'ALA050014_SR84_Widening', - 'ALA170011_BayBridge_HOV_Connectors', - 'ALA150047_TelegraphAve_Complete_Streets', - 'SM_110047_SR92_ElCam_Ramp_Mod', - 'SCL190002_280_Foothill_improvement', - 'SCL190006_101SB_offramp_improvement', - 'I80_AdaptiveRampMetering', - 'VAR170021_Freeway_Performance_I880', - 'SonomaCounty_Transit_NoBuild2050', - 'SF_MuniForward_Committed', - 'FBP_MU_029_Broadway_Transit_Only_Lanes', - 'EXP_Blueprint_NoProject', - 'FBP_AL_067_Rte84Wide', - 'FBP_AL_065_Bancroft_Bus_Only', - 'FBP_SM_032_US101_Willow_Interchange'], - 'trn':['ALA050015_BART_to_WarmSprings', - 'ACGo', - 'CC_050025_EBart_to_Antioch', - 'GGTransit_Committed', - 'SCL110005_BART_to_Berryessa', - 'SF_010015_Transbay_Terminal', - 'SF_010037_Muni_Central_Subway', - 'SF_070027_Yerba_Buena_Ramp_Imp', - 'SOL030002_FairfieldVacaville_Stn', - 'SON090002_SMART', - 'SON090002_SMART_to_Larkspur', - 'CC_070062_Richmond_Ferry', - 'SF_MuniForward_Committed', - 'VTA_Next', - 'SCL130001_237_101_MAT_Int_Mod', - 'SonomaCounty_Transit_NoBuild2050', - 'SMART_Novato', - 'Xfare_update_2020', - 'ACTransit_Committed', - 'ferry_update_2019', - 'Napa_Solano_Updates_2020', - 'FBP_Beale_Transit_Only_Lane', - 'SamTrans_ECR_Rapid', - 'ALA150004_EastBay_BRT', - {'name':'FBP_SL_026_SolExpressBus', 'kwargs':{'MODELYEAR':'2020'}}], - }), - (2025, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2025'}}, - 'EXP_CC_050028_I680_SB_HOV_Completion', - 'EXP_101B1', - 'EXP_101B2', - 'EXP_680C1', - 'EXP_680F', - 'EXP_85D', - 'EXP_101C', - 'ALA150001_I680_SR84_Int_Wid', - 'ALA150043_Claremont_road_diet', - 'CC_070009_Slatten_Ranch_Rd_Extension', - 'SF_070004_Geary_BRT_Phase1', - 'SON070004_101_MarinSonNarrows_Phase2', - 'SOL110006_Jepson_1B_1C', - 'SCL190008_US101_DLC_Int_Imp', - 'CC_170061_Bus_On_Shoulder_680BRT', - 'I880_US101_AdaptiveRampMetering', - 'MAJ_SCL050009_VTA_Eastridge_Extension', - 'SOL070020_I80_I680_SR12_Int_1_2A', - 'FBP_NP_036_SR29_Imola_PNR', - 'ALA170052_Fruitvale_Ave_ped_improvements', - 'EXP_Blueprint_NoProject'], - 'trn':['SF_010028_Caltrain_Modernization', - 'SON090002_SMART_to_Windsor', - 'MAJ_SCL050009_VTA_Eastridge_Extension', - 'REG090037_New_BART_Trains', - 'FBP_NP_036_SR29_Imola_PNR', - 'SOL070020_I80_I680_SR12_Int_1_2A'] - }), - (2030, { - 'hwy':[{'name':'Bridge_Toll_Updates_2_2pct', 'kwargs':{'MODELYEAR':'2030'}}, - 'EXP_Blueprint_NoProject'], - 'trn':['BART_NoProject'] - }) -]) +COMMITTED_PROJECTS = collections.OrderedDict( + [ + ( + 2015, + { + "hwy": [ + "PROJ_attributes", # adds PROJ attributes to NODE and LINK + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2015"}, + }, + ], + "trn": [], + }, + ), + ( + 2020, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2020"}, + }, + { + "name": "EXP_237B", + "kwargs": {"FUTURE": "PBA50"}, + }, # todo: update this to support PBA50 + "EXP_580C", + "EXP_680D", + "EXP_880A", + "HOV_680F", + "SCL130001_237_101_MAT_Int_Mod", + "REG090003_SCLARA_FIP", + "ALA130005_Dougherty_road_widening", + "ALA130006_Dublin_Blvd_widening", + "ALA130014_7th_St_road_diet", + "ALA130026_Shattuck_Complete_Streets", + "ALA170049_Central_AVE_Safety_Improvements", + "ALA150004_EastBay_BRT", + "CC_130001_BaileyRd_SR4", + "CC_130046_I680_SR4_Int_Rec", + "CC_070035_I80_SPDamRd_Int_Phase1", + "CC_070011_Brentwood_Blvd_Widening", + "CC_070075_Kirker_Pass_Truck_Lane", + "CC_090019_Bollinger_Canyon_Widening", + "CC_130006_Concord_BART_road_diets", + "CC_170001_SanRamonValleyBlvd_Lane_Addition", + "MRN150009_San_Rafael_Bridge_Improvements", + "SF_070027_Yerba_Buena_Ramp_Imp", + "SF_070005_VanNess_BRT", + "SF_130011_2ndSt_Road_Diet", + "SF_Market_Street_Closure", + "SM_110047_SR92_ElCam_Ramp_Mod", + "SOL110005_Jepson_Van_to_Com", + "FBP_SL_042_Jepson_2A", + "SON070004_101_MarinSonNarrows_Phase1", + "ALA050014_SR84_Widening", + "ALA170011_BayBridge_HOV_Connectors", + "ALA150047_TelegraphAve_Complete_Streets", + "SM_110047_SR92_ElCam_Ramp_Mod", + "SCL190002_280_Foothill_improvement", + "SCL190006_101SB_offramp_improvement", + "I80_AdaptiveRampMetering", + "VAR170021_Freeway_Performance_I880", + "SonomaCounty_Transit_NoBuild2050", + "SF_MuniForward_Committed", + "FBP_MU_029_Broadway_Transit_Only_Lanes", + "EXP_Blueprint_NoProject", + "FBP_AL_067_Rte84Wide", + "FBP_AL_065_Bancroft_Bus_Only", + "FBP_SM_032_US101_Willow_Interchange", + ], + "trn": [ + "ALA050015_BART_to_WarmSprings", + "ACGo", + "CC_050025_EBart_to_Antioch", + "GGTransit_Committed", + "SCL110005_BART_to_Berryessa", + "SF_010015_Transbay_Terminal", + "SF_010037_Muni_Central_Subway", + "SF_070027_Yerba_Buena_Ramp_Imp", + "SOL030002_FairfieldVacaville_Stn", + "SON090002_SMART", + "SON090002_SMART_to_Larkspur", + "CC_070062_Richmond_Ferry", + "SF_MuniForward_Committed", + "VTA_Next", + "SCL130001_237_101_MAT_Int_Mod", + "SonomaCounty_Transit_NoBuild2050", + "SMART_Novato", + "Xfare_update_2020", + "ACTransit_Committed", + "ferry_update_2019", + "Napa_Solano_Updates_2020", + "FBP_Beale_Transit_Only_Lane", + "SamTrans_ECR_Rapid", + "ALA150004_EastBay_BRT", + { + "name": "FBP_SL_026_SolExpressBus", + "kwargs": {"MODELYEAR": "2020"}, + }, + ], + }, + ), + ( + 2025, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2025"}, + }, + "EXP_CC_050028_I680_SB_HOV_Completion", + "EXP_101B1", + "EXP_101B2", + "EXP_680C1", + "EXP_680F", + "EXP_85D", + "EXP_101C", + "ALA150001_I680_SR84_Int_Wid", + "ALA150043_Claremont_road_diet", + "CC_070009_Slatten_Ranch_Rd_Extension", + "SF_070004_Geary_BRT_Phase1", + "SON070004_101_MarinSonNarrows_Phase2", + "SOL110006_Jepson_1B_1C", + "SCL190008_US101_DLC_Int_Imp", + "CC_170061_Bus_On_Shoulder_680BRT", + "I880_US101_AdaptiveRampMetering", + "MAJ_SCL050009_VTA_Eastridge_Extension", + "SOL070020_I80_I680_SR12_Int_1_2A", + "FBP_NP_036_SR29_Imola_PNR", + "ALA170052_Fruitvale_Ave_ped_improvements", + "EXP_Blueprint_NoProject", + ], + "trn": [ + "SF_010028_Caltrain_Modernization", + "SON090002_SMART_to_Windsor", + "MAJ_SCL050009_VTA_Eastridge_Extension", + "REG090037_New_BART_Trains", + "FBP_NP_036_SR29_Imola_PNR", + "SOL070020_I80_I680_SR12_Int_1_2A", + ], + }, + ), + ( + 2030, + { + "hwy": [ + { + "name": "Bridge_Toll_Updates_2_2pct", + "kwargs": {"MODELYEAR": "2030"}, + }, + "EXP_Blueprint_NoProject", + ], + "trn": ["BART_NoProject"], + }, + ), + ] +) ########################################################### # Blueprint projects -BLUEPRINT_PROJECTS = collections.OrderedDict([ - (2015, {'hwy':[], - 'trn':[] - }), - (2020, {'hwy':[], - 'trn':[] - }), - (2025, {'hwy':['RRSP_Alameda_Point_Transit_Improvements', - 'MAJ_MTC050027_Berkeley_Ferry', - 'MAJ_WETA_Service_Frequency_Increase', - {'name':'Transform_SR37_Widening_Interim', 'variants_exclude':['Alt1']}, - 'MAJ_SF_Congestion_Pricing', - 'MAJ_Geary_BRT_Phase2', - 'FBP_MU_041_Hovercraft_Pilot', - 'BP_Vision_Zero', - 'EXP_Blueprint', - 'MAJ_AC_Frequency_Improvement', - 'MRN050034_101_MarinSonNarrows_Phase2', - 'FBP_MU_044_SouthSF_Ferry_Serv_Incr', - 'FBP_MU_029_ACRapid_2025', - 'RRSP_E14_Mission_Corridor', - 'FBP_MR_026_NovatoWide', - 'FBP_CC_054_CrowCanyonWide', - 'FBP_NP_038_TSP_On_SR29', - {'name':'FBP_CC_050_SR4_Operation_Improvements_EB', 'variants_exclude':['Alt1']}, - 'FBP_NP_044_Soscol_Junction', - 'FBP_SL_033_FairgroundsWide', - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7','kwargs':{'PHASE':"'2B'"}}, - {'name':'FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5', 'kwargs':{'PHASE':"'1'"}, 'variants_exclude':['Alt1']}, - {'name':'FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5', 'kwargs':{'PHASE':"'2'"}, 'variants_exclude':['Alt1']}, - {'name':'EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2025'}, 'variants_exclude':['Alt1', 'NextGenFwy']}, - {'name':'EXP_uncommitted_noAllLaneTolling', 'kwargs':{'MODELYEAR':'2025'}, 'variants_include':['NextGenFwy']}, - {'name':'EIR1_EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2025'}, 'variants_include':['Alt1']}, - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2025'}}, - {'name':'FBP_CC_15_23rd_St_BRT', 'kwargs':{'MODELYEAR':'2025'}}, - 'FBP_SC_103_MontagueWide', - {'name':'MAJ_Bay_Area_Forward_all', 'kwargs':{'MODELYEAR':'2025'}}, - 'FBP_CC_057_LoneTreeWide', - 'FBP_CC_063_BrentwoodWide', - 'FBP_CC_067_WillowPassWide', - 'FBP_CC_065_LaurelWide', - 'FBP_AL_062_TassajaraWide', - 'FBP_SC_039_SR237WBWide', - 'FBP_AL_051_7St_Grade_Sep_West', - 'FBP_AL_044_I880_Whipple_Imps', - 'FBP_AL_055_DubBlvd_NCanyons_Ext', - 'FBP_SN_017_Arata_Int', - 'FBP_CC_017_Brentwood_Intermodal', - 'FBP_SF_030_Balboa_Park_Area_2', - 'EXP_Blueprint', - 'FBP_AL_039_I580_Interchange_Imps', - 'FBP_CC_056_LaurelExtension', - 'FBP_SC_084_10th_BridgeWide', - 'FBP_SL_053_PeabodyWide', - 'FBP_SC_073_BlossomHill_101Wide', - 'FBP_SC_082_US101_25_Interchange', - 'FBP_SM_035_Peninsula_101_OnOffRamps', - 'FBP_CC_045_SanPabloDam_Interchange_Phase2', - 'FBP_CC_030_OakleyAmtrak', - 'STIP_ProduceAve', - 'FBP_SM_033_US101_Holly_Interchange', - 'FBP_SM_034_Route92_ElCamino_Interchange', - 'FBP_SL_019_BeniciaRoad_Diet', - 'FBP_SL_023_WestTexasRoad_Diet', - 'FBP_SN_012_PetalumaBlvd_Diet', - 'MAJ_MissionBay_SF_Ferry', - {'name':'EIR2_Val_Link_ExpressBus', 'variants_include':['Alt2']}, - {'name':'EIR2_ReXBlue', 'variants_include':['Alt2']}, - 'FBP_SC_072_US101_Trimble_Interchange'], - 'trn':['MAJ_Geary_BRT_Phase2', - 'FBP_AL_001_NewarkFremPDA', - {'name':'FBP_MU_059_ACTransbay_Freq_Incr', 'variants_exclude':['Alt2']}, - 'MAJ_AC_Frequency_Improvement', - 'RRSP_Alameda_Point_Transit_Improvements', - 'MAJ_MTC050027_Berkeley_Ferry', - 'MAJ_WETA_Service_Frequency_Increase', - {'name':'FBP_MU_046_ACE_Freq_Inc', 'kwargs':{'MODELYEAR':'2025'}}, - {'name':'Transform_SR37_Widening_Interim', 'variants_exclude':['Alt1']}, - 'MAJ_SF_Congestion_Pricing', - 'FBP_MU_041_Hovercraft_Pilot', - 'FBP_MU_049_Caltrain_6TPHPD', - {'name':'FBP_MU_060_ReX_Blue', 'variants_exclude':['Alt2']}, - {'name':'EIR2_ReXBlue', 'variants_include':['Alt2']}, - 'FBP_MU_044_SouthSF_Ferry_Serv_Incr', - 'GGT_Service_Imp', - 'FBP_MU_029_ACRapid_2025', - 'RRSP_E14_Mission_Corridor', - 'FBP_NP_044_Soscol_Junction', - 'MAJ_RedwoodCity_SF_Ferry', - 'MAJ_Alameda_Point_SF_Ferry', - {'name':'MAJ_SOL070020_I80_I680_SR12_Int_2B_7','kwargs':{'PHASE':"'2B'"}}, - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2025'}}, - {'name':'FBP_CC_15_23rd_St_BRT', 'kwargs':{'MODELYEAR':'2025'}}, - 'FBP_CC_030_OakleyAmtrak', - 'FBP_SM_020_Regional_Express_Buses', - 'MAJ_MissionBay_SF_Ferry', - 'MAJ_Sonoma_Frequency_Increase', - {'name':'EIR1_Freq_Boosts', 'kwargs':{'MODELYEAR':'2025'}, 'variants_include':['Alt1']}, - {'name':'EIR2_HRA_Freq_Incr', 'kwargs':{'MODELYEAR':'2025'}, 'variants_include':['Alt2']}, - {'name':'EIR2_PDA_Freq_Incr', 'kwargs':{'MODELYEAR':'2025'}, 'variants_include':['Alt2']}, - {'name':'EIR2_Val_Link_ExpressBus', 'variants_include':['Alt2']}, - {'name':'SON090002_SMART_NorthPetaluma', 'variants_exclude':['Baseline']}] - }), - (2030, {'hwy':['MAJ_SanPablo_BRT', - {'name':'BP_Tolls_On_Congested_Freeways_2030', 'variants_exclude':['NextGenFwy']}, - 'BP_Vision_Zero', - {'name':'FBP_AL_021_South_Bay_Connect', 'variants_exclude':['Alt2']}, - 'FBP_MU_044_Richmond_Ferry_Serv_Incr', - 'MAJ_REG090037_BART_Core_Cap', - {'name':'Transform_Valley_Link', 'variants_exclude':['Alt2']}, - 'FBP_NP_040_VINE_Exp_Bus_Enhancements', - 'FBP_AL_045_Oak_Ala_Access_Pr', - {'name':'FBP_MR_021_101_580_Direct_Connector', 'variants_exclude':['Alt1']}, - # 'FBP_MR_018_US101_BOS', - 'FBP_CC_036_I80_ExpBus_Impr', - {'name':'FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5','kwargs':{'PHASE':"'4'"}, 'variants_exclude':['Alt1']}, - 'FBP_CC_021_Ant_Mart_Herc_Ferry', - {'name':'EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2030'}, 'variants_exclude':['Alt1', 'NextGenFwy']}, - {'name':'EXP_uncommitted_noAllLaneTolling', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['NextGenFwy']}, - {'name':'EIR1_EXP_uncommitted_all', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['Alt1']}, - 'FBP_SC_104_OaklandWide', - {'name':'FBP_CC_15_23rd_St_BRT', 'kwargs':{'MODELYEAR':'2030'}}, - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2030'}}, - {'name':'MAJ_Bay_Area_Forward_all', 'kwargs':{'MODELYEAR':'2030'}}, - 'FBP_CC_064_CaminoTassajaraWide', - 'FBP_CC_066_CypressWide', - 'FBP_SC_059_SR237EBWide', - 'FBP_AL_064_UnionCityWide', - 'FBP_SC_074_US101_BuenaVista_Int', - 'EXP_Blueprint', - 'FBP_SC_054_SR17_Corridor_Relief', - 'FBP_AL_043_A_StreetWide', - 'FBP_CC_061_062_West_Leland_Ext_Phases1_2', - 'FBP_SM_042_Hwy1_ManorDrive', - 'FBP_SL_042_Jepson_2B_2C', - 'FBP_CC_024_Oakley_PNR_Tri_Delta', - 'FBP_SC_083_US101_Zanker_Skyport_Interchange', - 'FBP_SL_022_SonomaBlvd_Diet', - 'FBP_SM_027_US101_92', - 'FBP_SM_007_ElCamino_CompleteStreets'], - 'trn':['BP_PDA_Transit_Enhancements', - {'name':'FBP_MU_046_ACE_Freq_Inc', 'kwargs':{'MODELYEAR':'2030'}}, - 'MAJ_BRT030001_BART_to_SanJose', - 'BART_Irvington_Infill', - 'MAJ_REG090037_BART_Core_Cap', - {'name':'FBP_AL_021_South_Bay_Connect', 'variants_exclude':['Alt2']}, - 'FBP_MU_049_Caltrain_8TPHPD', - 'FBP_MU_061_ReX_Green', - 'MAJ_SanPablo_BRT', - 'FBP_MU_044_Richmond_Ferry_Serv_Incr', - {'name':'Transform_Valley_Link', 'variants_exclude':['Alt2']}, - 'FBP_SF_028_SF_Express_Bus_On_Exp_Lanes', - {'name':'MAJ_SF_050002_Caltrain_Ext_TransbayTerminal', 'variants_exclude':['Alt2']}, - 'FBP_SF_024_Historic_Streetcar_Ext', - 'FBP_MuniForward_Uncommitted_Rail', - 'FBP_CC_036_I80_ExpBus_Impr', - 'FBP_CC_021_Ant_Mart_Herc_Ferry', - 'FBP_AL_045_Oak_Ala_Access_Pr', - 'FBP_CC_028_Hercules_Station', - {'name':'FBP_SF_012_Geneva_Harney_BRT', 'kwargs':{'MODELYEAR':'2030'}}, - {'name':'FBP_CC_15_23rd_St_BRT', 'kwargs':{'MODELYEAR':'2030'}}, - 'FBP_CC_024_Oakley_PNR_Tri_Delta', - {'name':'EIR1_Freq_Boosts', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['Alt1']}, - {'name':'EIR2_HRA_Freq_Incr', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['Alt2']}, - {'name':'EIR2_PDA_Freq_Incr', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['Alt2']}, - {'name':'EIR2_Fix_Alt2', 'kwargs':{'MODELYEAR':'2030'}, 'variants_include':['Alt2']}] - }) - ]) +BLUEPRINT_PROJECTS = collections.OrderedDict( + [ + (2015, {"hwy": [], "trn": []}), + (2020, {"hwy": [], "trn": []}), + ( + 2025, + { + "hwy": [ + "RRSP_Alameda_Point_Transit_Improvements", + "MAJ_MTC050027_Berkeley_Ferry", + "MAJ_WETA_Service_Frequency_Increase", + { + "name": "Transform_SR37_Widening_Interim", + "variants_exclude": ["Alt1"], + }, + "MAJ_SF_Congestion_Pricing", + "MAJ_Geary_BRT_Phase2", + "FBP_MU_041_Hovercraft_Pilot", + "BP_Vision_Zero", + "EXP_Blueprint", + "MAJ_AC_Frequency_Improvement", + "MRN050034_101_MarinSonNarrows_Phase2", + "FBP_MU_044_SouthSF_Ferry_Serv_Incr", + "FBP_MU_029_ACRapid_2025", + "RRSP_E14_Mission_Corridor", + "FBP_MR_026_NovatoWide", + "FBP_CC_054_CrowCanyonWide", + "FBP_NP_038_TSP_On_SR29", + { + "name": "FBP_CC_050_SR4_Operation_Improvements_EB", + "variants_exclude": ["Alt1"], + }, + "FBP_NP_044_Soscol_Junction", + "FBP_SL_033_FairgroundsWide", + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'2B'"}, + }, + { + "name": "FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5", + "kwargs": {"PHASE": "'1'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5", + "kwargs": {"PHASE": "'2'"}, + "variants_exclude": ["Alt1"], + }, + { + "name": "EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2025"}, + "variants_exclude": ["Alt1", "NextGenFwy"], + }, + { + "name": "EXP_uncommitted_noAllLaneTolling", + "kwargs": {"MODELYEAR": "2025"}, + "variants_include": ["NextGenFwy"], + }, + { + "name": "EIR1_EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2025"}, + "variants_include": ["Alt1"], + }, + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2025"}, + }, + {"name": "FBP_CC_15_23rd_St_BRT", "kwargs": {"MODELYEAR": "2025"}}, + "FBP_SC_103_MontagueWide", + { + "name": "MAJ_Bay_Area_Forward_all", + "kwargs": {"MODELYEAR": "2025"}, + }, + "FBP_CC_057_LoneTreeWide", + "FBP_CC_063_BrentwoodWide", + "FBP_CC_067_WillowPassWide", + "FBP_CC_065_LaurelWide", + "FBP_AL_062_TassajaraWide", + "FBP_SC_039_SR237WBWide", + "FBP_AL_051_7St_Grade_Sep_West", + "FBP_AL_044_I880_Whipple_Imps", + "FBP_AL_055_DubBlvd_NCanyons_Ext", + "FBP_SN_017_Arata_Int", + "FBP_CC_017_Brentwood_Intermodal", + "FBP_SF_030_Balboa_Park_Area_2", + "EXP_Blueprint", + "FBP_AL_039_I580_Interchange_Imps", + "FBP_CC_056_LaurelExtension", + "FBP_SC_084_10th_BridgeWide", + "FBP_SL_053_PeabodyWide", + "FBP_SC_073_BlossomHill_101Wide", + "FBP_SC_082_US101_25_Interchange", + "FBP_SM_035_Peninsula_101_OnOffRamps", + "FBP_CC_045_SanPabloDam_Interchange_Phase2", + "FBP_CC_030_OakleyAmtrak", + "STIP_ProduceAve", + "FBP_SM_033_US101_Holly_Interchange", + "FBP_SM_034_Route92_ElCamino_Interchange", + "FBP_SL_019_BeniciaRoad_Diet", + "FBP_SL_023_WestTexasRoad_Diet", + "FBP_SN_012_PetalumaBlvd_Diet", + "MAJ_MissionBay_SF_Ferry", + {"name": "EIR2_Val_Link_ExpressBus", "variants_include": ["Alt2"]}, + {"name": "EIR2_ReXBlue", "variants_include": ["Alt2"]}, + "FBP_SC_072_US101_Trimble_Interchange", + ], + "trn": [ + "MAJ_Geary_BRT_Phase2", + "FBP_AL_001_NewarkFremPDA", + { + "name": "FBP_MU_059_ACTransbay_Freq_Incr", + "variants_exclude": ["Alt2"], + }, + "MAJ_AC_Frequency_Improvement", + "RRSP_Alameda_Point_Transit_Improvements", + "MAJ_MTC050027_Berkeley_Ferry", + "MAJ_WETA_Service_Frequency_Increase", + { + "name": "FBP_MU_046_ACE_Freq_Inc", + "kwargs": {"MODELYEAR": "2025"}, + }, + { + "name": "Transform_SR37_Widening_Interim", + "variants_exclude": ["Alt1"], + }, + "MAJ_SF_Congestion_Pricing", + "FBP_MU_041_Hovercraft_Pilot", + "FBP_MU_049_Caltrain_6TPHPD", + {"name": "FBP_MU_060_ReX_Blue", "variants_exclude": ["Alt2"]}, + {"name": "EIR2_ReXBlue", "variants_include": ["Alt2"]}, + "FBP_MU_044_SouthSF_Ferry_Serv_Incr", + "GGT_Service_Imp", + "FBP_MU_029_ACRapid_2025", + "RRSP_E14_Mission_Corridor", + "FBP_NP_044_Soscol_Junction", + "MAJ_RedwoodCity_SF_Ferry", + "MAJ_Alameda_Point_SF_Ferry", + { + "name": "MAJ_SOL070020_I80_I680_SR12_Int_2B_7", + "kwargs": {"PHASE": "'2B'"}, + }, + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2025"}, + }, + {"name": "FBP_CC_15_23rd_St_BRT", "kwargs": {"MODELYEAR": "2025"}}, + "FBP_CC_030_OakleyAmtrak", + "FBP_SM_020_Regional_Express_Buses", + "MAJ_MissionBay_SF_Ferry", + "MAJ_Sonoma_Frequency_Increase", + { + "name": "EIR1_Freq_Boosts", + "kwargs": {"MODELYEAR": "2025"}, + "variants_include": ["Alt1"], + }, + { + "name": "EIR2_HRA_Freq_Incr", + "kwargs": {"MODELYEAR": "2025"}, + "variants_include": ["Alt2"], + }, + { + "name": "EIR2_PDA_Freq_Incr", + "kwargs": {"MODELYEAR": "2025"}, + "variants_include": ["Alt2"], + }, + {"name": "EIR2_Val_Link_ExpressBus", "variants_include": ["Alt2"]}, + { + "name": "SON090002_SMART_NorthPetaluma", + "variants_exclude": ["Baseline"], + }, + ], + }, + ), + ( + 2030, + { + "hwy": [ + "MAJ_SanPablo_BRT", + { + "name": "BP_Tolls_On_Congested_Freeways_2030", + "variants_exclude": ["NextGenFwy"], + }, + "BP_Vision_Zero", + { + "name": "FBP_AL_021_South_Bay_Connect", + "variants_exclude": ["Alt2"], + }, + "FBP_MU_044_Richmond_Ferry_Serv_Incr", + "MAJ_REG090037_BART_Core_Cap", + {"name": "Transform_Valley_Link", "variants_exclude": ["Alt2"]}, + "FBP_NP_040_VINE_Exp_Bus_Enhancements", + "FBP_AL_045_Oak_Ala_Access_Pr", + { + "name": "FBP_MR_021_101_580_Direct_Connector", + "variants_exclude": ["Alt1"], + }, + # 'FBP_MR_018_US101_BOS', + "FBP_CC_036_I80_ExpBus_Impr", + { + "name": "FBP_CC_040_041_042_I680_SR4_Int_Phases_1_2_4_5", + "kwargs": {"PHASE": "'4'"}, + "variants_exclude": ["Alt1"], + }, + "FBP_CC_021_Ant_Mart_Herc_Ferry", + { + "name": "EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2030"}, + "variants_exclude": ["Alt1", "NextGenFwy"], + }, + { + "name": "EXP_uncommitted_noAllLaneTolling", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["NextGenFwy"], + }, + { + "name": "EIR1_EXP_uncommitted_all", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["Alt1"], + }, + "FBP_SC_104_OaklandWide", + {"name": "FBP_CC_15_23rd_St_BRT", "kwargs": {"MODELYEAR": "2030"}}, + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2030"}, + }, + { + "name": "MAJ_Bay_Area_Forward_all", + "kwargs": {"MODELYEAR": "2030"}, + }, + "FBP_CC_064_CaminoTassajaraWide", + "FBP_CC_066_CypressWide", + "FBP_SC_059_SR237EBWide", + "FBP_AL_064_UnionCityWide", + "FBP_SC_074_US101_BuenaVista_Int", + "EXP_Blueprint", + "FBP_SC_054_SR17_Corridor_Relief", + "FBP_AL_043_A_StreetWide", + "FBP_CC_061_062_West_Leland_Ext_Phases1_2", + "FBP_SM_042_Hwy1_ManorDrive", + "FBP_SL_042_Jepson_2B_2C", + "FBP_CC_024_Oakley_PNR_Tri_Delta", + "FBP_SC_083_US101_Zanker_Skyport_Interchange", + "FBP_SL_022_SonomaBlvd_Diet", + "FBP_SM_027_US101_92", + "FBP_SM_007_ElCamino_CompleteStreets", + ], + "trn": [ + "BP_PDA_Transit_Enhancements", + { + "name": "FBP_MU_046_ACE_Freq_Inc", + "kwargs": {"MODELYEAR": "2030"}, + }, + "MAJ_BRT030001_BART_to_SanJose", + "BART_Irvington_Infill", + "MAJ_REG090037_BART_Core_Cap", + { + "name": "FBP_AL_021_South_Bay_Connect", + "variants_exclude": ["Alt2"], + }, + "FBP_MU_049_Caltrain_8TPHPD", + "FBP_MU_061_ReX_Green", + "MAJ_SanPablo_BRT", + "FBP_MU_044_Richmond_Ferry_Serv_Incr", + {"name": "Transform_Valley_Link", "variants_exclude": ["Alt2"]}, + "FBP_SF_028_SF_Express_Bus_On_Exp_Lanes", + { + "name": "MAJ_SF_050002_Caltrain_Ext_TransbayTerminal", + "variants_exclude": ["Alt2"], + }, + "FBP_SF_024_Historic_Streetcar_Ext", + "FBP_MuniForward_Uncommitted_Rail", + "FBP_CC_036_I80_ExpBus_Impr", + "FBP_CC_021_Ant_Mart_Herc_Ferry", + "FBP_AL_045_Oak_Ala_Access_Pr", + "FBP_CC_028_Hercules_Station", + { + "name": "FBP_SF_012_Geneva_Harney_BRT", + "kwargs": {"MODELYEAR": "2030"}, + }, + {"name": "FBP_CC_15_23rd_St_BRT", "kwargs": {"MODELYEAR": "2030"}}, + "FBP_CC_024_Oakley_PNR_Tri_Delta", + { + "name": "EIR1_Freq_Boosts", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["Alt1"], + }, + { + "name": "EIR2_HRA_Freq_Incr", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["Alt2"], + }, + { + "name": "EIR2_PDA_Freq_Incr", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["Alt2"], + }, + { + "name": "EIR2_Fix_Alt2", + "kwargs": {"MODELYEAR": "2030"}, + "variants_include": ["Alt2"], + }, + ], + }, + ), + ] +) # Put them together for NETWORK_PROJECTS -NETWORK_PROJECTS = collections.OrderedDict() +NETWORK_PROJECTS = collections.OrderedDict() for YEAR in COMMITTED_PROJECTS.keys(): if SCENARIO == "Baseline": # baseline: just committed NETWORK_PROJECTS[YEAR] = { - 'hwy':COMMITTED_PROJECTS[YEAR]['hwy'], - 'trn':COMMITTED_PROJECTS[YEAR]['trn'] + "hwy": COMMITTED_PROJECTS[YEAR]["hwy"], + "trn": COMMITTED_PROJECTS[YEAR]["trn"], } # todo: add sea level rise since it's unprotected @@ -323,28 +506,39 @@ # blueprint, alt1, alt2 NETWORK_PROJECTS[YEAR] = { - 'hwy':COMMITTED_PROJECTS[YEAR]['hwy'] + BLUEPRINT_PROJECTS[YEAR]['hwy'], - 'trn':COMMITTED_PROJECTS[YEAR]['trn'] + BLUEPRINT_PROJECTS[YEAR]['trn'] + "hwy": COMMITTED_PROJECTS[YEAR]["hwy"] + BLUEPRINT_PROJECTS[YEAR]["hwy"], + "trn": COMMITTED_PROJECTS[YEAR]["trn"] + BLUEPRINT_PROJECTS[YEAR]["trn"], } # handle net_remove, nets keywords - for netmode in ['hwy','trn']: + for netmode in ["hwy", "trn"]: # iterate backwards via index to delete cleanly - for project_idx in range(len(NETWORK_PROJECTS[YEAR][netmode])-1,-1,-1): + for project_idx in range(len(NETWORK_PROJECTS[YEAR][netmode]) - 1, -1, -1): project = NETWORK_PROJECTS[YEAR][netmode][project_idx] # special handling requires project to be specified as dictionary - if not isinstance(project, dict): continue + if not isinstance(project, dict): + continue # variants_exclude: specifies list of network variants for which this project should be *excluded* - if 'variants_exclude' in project.keys() and SCENARIO in project['variants_exclude']: - Wrangler.WranglerLogger.info("Removing {} {} {}".format(YEAR, netmode, project)) + if ( + "variants_exclude" in project.keys() + and SCENARIO in project["variants_exclude"] + ): + Wrangler.WranglerLogger.info( + "Removing {} {} {}".format(YEAR, netmode, project) + ) del NETWORK_PROJECTS[YEAR][netmode][project_idx] continue # variants_include: specifies list of network variants for which this project should be *included* # if this keyword is present, then this project is included *only* for variants in this list - if 'variants_include' in project.keys() and SCENARIO not in project['variants_include']: - Wrangler.WranglerLogger.info("Removing {} {} {}".format(YEAR, netmode, project)) + if ( + "variants_include" in project.keys() + and SCENARIO not in project["variants_include"] + ): + Wrangler.WranglerLogger.info( + "Removing {} {} {}".format(YEAR, netmode, project) + ) del NETWORK_PROJECTS[YEAR][netmode][project_idx] continue @@ -357,11 +551,15 @@ # for YEAR in NETWORK_PROJECTS.keys(): # if anything is applied - if ((len(NETWORK_PROJECTS[YEAR]['hwy']) > 0) or (len(NETWORK_PROJECTS[YEAR]['trn']) > 0)): - NETWORK_PROJECTS[YEAR]['hwy'].append('No_zero_length_links') + if (len(NETWORK_PROJECTS[YEAR]["hwy"]) > 0) or ( + len(NETWORK_PROJECTS[YEAR]["trn"]) > 0 + ): + NETWORK_PROJECTS[YEAR]["hwy"].append("No_zero_length_links") - if ((len(NETWORK_PROJECTS[YEAR]['hwy']) > 0) or (len(NETWORK_PROJECTS[YEAR]['trn']) > 0)): - NETWORK_PROJECTS[YEAR]['trn'].append('Move_buses_to_HOV_EXP_lanes') + if (len(NETWORK_PROJECTS[YEAR]["hwy"]) > 0) or ( + len(NETWORK_PROJECTS[YEAR]["trn"]) > 0 + ): + NETWORK_PROJECTS[YEAR]["trn"].append("Move_buses_to_HOV_EXP_lanes") # OPTIONAL. The default route network project directory is Y:\networks. If diff --git a/scripts/tag_project_repo.py b/scripts/tag_project_repo.py index fff5268..adb2316 100644 --- a/scripts/tag_project_repo.py +++ b/scripts/tag_project_repo.py @@ -20,88 +20,97 @@ import os, argparse -if __name__ == '__main__': - +if __name__ == "__main__": + # NetworkProjects directory, this is where the local repos are located - networkProjects_folder = 'M:\\Application\\Model One\\NetworkProjects' + networkProjects_folder = "M:\\Application\\Model One\\NetworkProjects" # arguments - parser = argparse.ArgumentParser(description=USAGE, formatter_class=argparse.RawDescriptionHelpFormatter,) - parser.add_argument('tag_name', help='name of the tag to be created') - parser.add_argument('tag_message', help='tagging message') - parser.add_argument('network_creation_log', help='network creation log listing all projects to be tagged') + parser = argparse.ArgumentParser( + description=USAGE, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("tag_name", help="name of the tag to be created") + parser.add_argument("tag_message", help="tagging message") + parser.add_argument( + "network_creation_log", + help="network creation log listing all projects to be tagged", + ) args = parser.parse_args() - print('tag name: {}'.format(args.tag_name)) - print('tagging message: {}'.format(args.tag_message)) - print('network creation log: {}'.format(args.network_creation_log)) - + print("tag name: {}".format(args.tag_name)) + print("tagging message: {}".format(args.tag_message)) + print("network creation log: {}".format(args.network_creation_log)) # Step 1: create a dataframe to store project_name and the commit (SHA1_id) to tag - projects_df = pd.DataFrame(columns=['project_name', 'SHA1_id']) + projects_df = pd.DataFrame(columns=["project_name", "SHA1_id"]) with open(args.network_creation_log) as f: log_lines = list(enumerate(f)) for line_num, line in log_lines: # get name of project - if 'Applying project' in line: - project_name = line.split('] of type')[0].split('Applying project [')[1] + if "Applying project" in line: + project_name = line.split("] of type")[0].split("Applying project [")[1] # print(project_name) # with NetworkWrangler log file format, the SHA1_id is usually in the next line # which also contains the project's name - next_line = log_lines[line_num+1][1] + next_line = log_lines[line_num + 1][1] if project_name in next_line: - SHA1_id = next_line.split('| '+project_name)[0].split('|')[-1].strip() + SHA1_id = ( + next_line.split("| " + project_name)[0].split("|")[-1].strip() + ) # print(SHA1_id) - + # add 'project_name', 'SHA1_id' to the dataframe projects_df.loc[len(projects_df.index)] = [project_name, SHA1_id] - + elif project_name not in next_line: - print('No SHA1_id in the next line for project: ', project_name) + print("No SHA1_id in the next line for project: ", project_name) # manually add SHA1_id for project 'Move_buses_to_HOV_EXP_lanes' which has # a different format in the log - projects_df.loc[len(projects_df.index)] = ['Move_buses_to_HOV_EXP_lanes', - 'b8ac63bfe873df1c80e2c8ecd67904ad3970b721'] + projects_df.loc[len(projects_df.index)] = [ + "Move_buses_to_HOV_EXP_lanes", + "b8ac63bfe873df1c80e2c8ecd67904ad3970b721", + ] # drop duplicates projects_df.drop_duplicates(inplace=True) - print('tagging {} projects'.format(projects_df.shape[0])) + print("tagging {} projects".format(projects_df.shape[0])) # set project_name as index - projects_df.set_index('project_name', inplace=True) - + projects_df.set_index("project_name", inplace=True) # Step 2: loop through the projects and add tag to the corresponding commit for project in projects_df.index: - print('Project: ', project) + print("Project: ", project) # try to open the existing repo try: repo = git.Repo(os.path.join(networkProjects_folder, project)) - # optional: print out existing tags, sorted by time of creation + # optional: print out existing tags, sorted by time of creation # existing_tags = sorted(repo.tags, key=lambda t: t.commit.committed_date) # print('existing_tags: {}'.format(existing_tags)) - # try creating the tag to the right commit commit_ref = repo.commit(projects_df.SHA1_id[project]) - print('commit_reference: {}'.format(commit_ref)) + print("commit_reference: {}".format(commit_ref)) try: - print('create tag {} with comment {}'.format(args.tag_name, args.tag_message)) - repo.create_tag(args.tag_name, - ref=commit_ref, - message = args.tag_message) - + print( + "create tag {} with comment {}".format( + args.tag_name, args.tag_message + ) + ) + repo.create_tag(args.tag_name, ref=commit_ref, message=args.tag_message) + # if the tag already exists, cannot create, will skip except: print('cannot create tag "{}"'.format(args.tag_name)) except: - print('repo {} doest not exist'.format(project)) + print("repo {} doest not exist".format(project)) diff --git a/setup.py b/setup.py index 6333eef..07f0caa 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ import os import setuptools -VERSION="1.5" +VERSION = "1.5" classifiers = [ "Development Status :: 5 - Production/Stable", @@ -27,19 +27,19 @@ install_requires = [r.strip() for r in requirements] setuptools.setup( - name = "NetworkWrangler", - version = VERSION, - description = "Wrangles networks for MTC Travel Model 1/1.5", - long_description = long_description, - long_description_content_type = "text/markdown", - url = "https://github.com/BayAreaMetro/NetworkWrangler", - license = "Apache 2", - platforms = "any", - packages = ["Wrangler"], - include_package_data = True, - install_requires = install_requires, - scripts = [ + name="NetworkWrangler", + version=VERSION, + description="Wrangles networks for MTC Travel Model 1/1.5", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/BayAreaMetro/NetworkWrangler", + license="Apache 2", + platforms="any", + packages=["Wrangler"], + include_package_data=True, + install_requires=install_requires, + scripts=[ "scripts/build_network_mtc.py", "scripts/build_network_mtc_blueprint.py", ], -) \ No newline at end of file +) diff --git a/unittests/TransitNetworkTest.py b/unittests/TransitNetworkTest.py index 89076d9..49e22ee 100644 --- a/unittests/TransitNetworkTest.py +++ b/unittests/TransitNetworkTest.py @@ -6,11 +6,10 @@ import Wrangler -class TestTransitNetwork(unittest.TestCase): +class TestTransitNetwork(unittest.TestCase): def setUp(self): - """ Initialize the TransitNetwork and read in the unittests dir - """ + """Initialize the TransitNetwork and read in the unittests dir""" self.tn = Wrangler.TransitNetwork() thisdir = os.path.dirname(os.path.realpath(__file__)) @@ -20,41 +19,45 @@ def test_transit_network_iterator(self): count = 0 for line in self.tn: count += 1 - self.assertTrue(isinstance(line,Wrangler.TransitLine)) - self.assertEqual(count,2) + self.assertTrue(isinstance(line, Wrangler.TransitLine)) + self.assertEqual(count, 2) count = 0 for line in self.tn: count += 1 - self.assertTrue(isinstance(line,Wrangler.TransitLine)) - self.assertEqual(count,2) + self.assertTrue(isinstance(line, Wrangler.TransitLine)) + self.assertEqual(count, 2) def test_transit_line_iterator(self): count = 0 for stop in self.tn.line("TEST_A"): count += 1 - self.assertTrue(isinstance(stop,int)) - self.assertEqual(count,10) + self.assertTrue(isinstance(stop, int)) + self.assertEqual(count, 10) def test_transit_line_hasLink(self): - self.assertTrue(self.tn.line("TEST_A").hasLink(2,3)) - self.assertTrue(self.tn.line("TEST_A").hasLink(-2,-3)) - self.assertFalse(self.tn.line("TEST_A").hasLink(3,2)) - self.assertFalse(self.tn.line("TEST_A").hasLink(2,4)) + self.assertTrue(self.tn.line("TEST_A").hasLink(2, 3)) + self.assertTrue(self.tn.line("TEST_A").hasLink(-2, -3)) + self.assertFalse(self.tn.line("TEST_A").hasLink(3, 2)) + self.assertFalse(self.tn.line("TEST_A").hasLink(2, 4)) def test_transit_line_hasSegment(self): - self.assertTrue(self.tn.line("TEST_A").hasSegment(2,3)) - self.assertTrue(self.tn.line("TEST_A").hasSegment(-2,-3)) - self.assertFalse(self.tn.line("TEST_A").hasSegment(3,2)) - self.assertTrue(self.tn.line("TEST_A").hasSegment(2,4)) + self.assertTrue(self.tn.line("TEST_A").hasSegment(2, 3)) + self.assertTrue(self.tn.line("TEST_A").hasSegment(-2, -3)) + self.assertFalse(self.tn.line("TEST_A").hasSegment(3, 2)) + self.assertTrue(self.tn.line("TEST_A").hasSegment(2, 4)) def test_transit_line_extendLine(self): - self.assertRaises(ValueError, - self.tn.line("TEST_B").extendLine, - 14, [24,25,26,-27,28], False) + self.assertRaises( + ValueError, + self.tn.line("TEST_B").extendLine, + 14, + [24, 25, 26, -27, 28], + False, + ) - self.tn.line("TEST_B").extendLine(-14,[24,25,26,-27,28], beginning=False) - self.assertEqual(len(self.tn.line("TEST_B").n),8) + self.tn.line("TEST_B").extendLine(-14, [24, 25, 26, -27, 28], beginning=False) + self.assertEqual(len(self.tn.line("TEST_B").n), 8) # for stop in self.tn.line("TEST_B"): print stop # test doing an extend at the beginning @@ -62,5 +65,6 @@ def test_transit_line_extendLine(self): def test_transit_line_index(self): self.assertEqual(self.tn.line("TEST_A").n.index(4), 3) -if __name__ == '__main__': - unittest.main() \ No newline at end of file + +if __name__ == "__main__": + unittest.main() diff --git a/unittests/TransitNodeTest.py b/unittests/TransitNodeTest.py index 418f51f..11a6fa5 100644 --- a/unittests/TransitNodeTest.py +++ b/unittests/TransitNodeTest.py @@ -6,19 +6,18 @@ import Wrangler -class TestTransitNode(unittest.TestCase): +class TestTransitNode(unittest.TestCase): def setUp(self): - """ Initialize the TransitNetwork and read in the unittests dir - """ + """Initialize the TransitNetwork and read in the unittests dir""" self.embarc = Wrangler.Node(16511) self.embarc.attr["ACCESS"] = 2 - + self.invalid = Wrangler.Node(1) def test_transit_node_description(self): - self.assertEqual(self.embarc.description(),"Embarcadero BART") + self.assertEqual(self.embarc.description(), "Embarcadero BART") self.assertEqual(self.invalid.description(), None) def test_transit_node_boards_disallowed(self): @@ -26,5 +25,5 @@ def test_transit_node_boards_disallowed(self): self.assertEqual(self.invalid.boardsDisallowed(), False) -if __name__ == '__main__': - unittest.main() \ No newline at end of file +if __name__ == "__main__": + unittest.main()