# ===== Create output ===== # ----- Create a feature class of stops ------ try: arcpy.AddMessage("Creating feature class of GTFS stops...") for direction in stoplist: stops = stoplist[direction] outputname = outStopsname if direction != None: outputname += str(direction) outStops = os.path.join(outGDB, outputname) outStops, outStopList = BBB_SharedFunctions.MakeStopsFeatureClass( outStops, stops) # Add a route_id and direction_id field and populate it arcpy.management.AddField(outStops, "route_id", "TEXT") arcpy.management.AddField(outStops, "direction_id", "TEXT") fields = ["route_id", "direction_id"] if BBB_SharedFunctions.ArcVersion == "10.0": cursor = arcpy.UpdateCursor(outStops) for row in cursor: row.setValue("route_id", route_id) row.setValue("direction_id", direction) cursor.updateRow(row) del cursor else: with arcpy.da.UpdateCursor(outStops, fields) as cursor: for row in cursor:
def runTool(outGDB, SQLDbase, RouteText, inNetworkDataset, imp, BufferSize, restrictions, TrimSettings): try: OverwriteOutput = arcpy.env.overwriteOutput # Get the orignal value so we can reset it. arcpy.env.overwriteOutput = True # Source FC names are not prepended to field names. arcpy.env.qualifiedFieldNames = False BBB_SharedFunctions.CheckArcVersion(min_version_pro="1.2") BBB_SharedFunctions.CheckOutNALicense() BBB_SharedFunctions.CheckWorkspace() # ===== Get trips and stops associated with this route ===== # ----- Figure out which route the user wants to analyze based on the text input ----- try: arcpy.AddMessage("Gathering route, trip, and stop information...") # Connect to or create the SQL file. conn = sqlite3.connect(SQLDbase) c = BBB_SharedFunctions.c = conn.cursor() # Get list of routes in the GTFS data routefetch = "SELECT route_short_name, route_long_name, route_id FROM routes;" c.execute(routefetch) # Extract the route_id based on what the user picked from the GUI list # It's better to do it by searching the database instead of trying to extract # the route_id from the text they chose because we don't know what kind of # characters will be in the route names and id, so parsing could be unreliable route_id = "" for route in c: routecheck = route[0] + ": " + route[1] + " [" + route[2] + "]" if routecheck == RouteText: route_id = route[2] route_short_name = route[0] break if not route_id: arcpy.AddError("Could not parse route selection.") raise BBB_SharedFunctions.CustomError # Name feature classes outStopsname = arcpy.ValidateTableName("Stops_" + route_short_name, outGDB) outPolysname = arcpy.ValidateTableName( "Buffers_" + route_short_name, outGDB) except: arcpy.AddError("Error determining route_id for analysis.") raise # ----- Get trips associated with route and split into directions ----- try: # Some GTFS datasets use the same route_id to identify trips traveling in # either direction along a route. Others identify it as a different route. # We will consider each direction separately if there is more than one. # Get list of trips trip_route_dict = {} triproutefetch = ''' SELECT trip_id, direction_id FROM trips WHERE route_id='%s' ;''' % route_id c.execute(triproutefetch) # Fill some dictionaries for use later. trip_dir_dict = {} # {Direction: [trip_id, trip_id, ...]} for triproute in c: trip_dir_dict.setdefault(triproute[1], []).append(triproute[0]) if not trip_dir_dict: arcpy.AddError( "There are no trips in the GTFS data for the route \ you have selected (%s). Please select a different route or fix your GTFS \ dataset." % RouteText) raise BBB_SharedFunctions.CustomError except: arcpy.AddError("Error getting trips associated with route.") raise # ----- Get list of stops associated with trips and split into directions ----- try: # If a stop is used for trips going in both directions, count them separately. # Select unique set of stops used by trips in each direction stoplist = {} # {Direction: [stop_id, stop_id, ...]} for direction in trip_dir_dict: stops = [] for trip in trip_dir_dict[direction]: stopsfetch = '''SELECT stop_id FROM stop_times WHERE trip_id == ?''' c.execute(stopsfetch, (trip, )) for stop in c: stops.append(stop[0]) stoplist[direction] = list(set(stops)) # If there is more than one direction, we will append the direction number # to the output fc names, so add an _ here for prettiness. if len(stoplist) > 1: arcpy.AddMessage( "Route %s contains trips going in more than one \ direction. A separate feature class will be created for each direction, and the \ GTFS direction_id will be appended to the feature class name." % route_short_name) outStopsname += "_" outPolysname += "_" except: arcpy.AddError("Error getting stops associated with route.") raise # ===== Create output ===== # ----- Create a feature class of stops ------ try: arcpy.AddMessage("Creating feature class of GTFS stops...") for direction in stoplist: stops = stoplist[direction] outputname = outStopsname if direction != None: outputname += str(direction) outStops = os.path.join(outGDB, outputname) outStops, outStopList = BBB_SharedFunctions.MakeStopsFeatureClass( outStops, stops) # Add a route_id and direction_id field and populate it arcpy.management.AddField(outStops, "route_id", "TEXT") arcpy.management.AddField(outStops, "direction_id", "TEXT") fields = ["route_id", "direction_id"] with arcpy.da.UpdateCursor(outStops, fields) as cursor: for row in cursor: row[0] = route_id row[1] = direction cursor.updateRow(row) except: arcpy.AddError("Error creating feature class of GTFS stops.") raise #----- Create Service Areas around stops ----- try: arcpy.AddMessage("Creating buffers around stops...") for direction in stoplist: outputname = outStopsname if direction != None: outputname += str(direction) outStops = os.path.join(outGDB, outputname) TrimPolys, TrimPolysValue = BBB_SharedFunctions.CleanUpTrimSettings( TrimSettings) polygons = BBB_SharedFunctions.MakeServiceAreasAroundStops( outStops, inNetworkDataset, BBB_SharedFunctions.CleanUpImpedance(imp), BufferSize, restrictions, TrimPolys, TrimPolysValue) # Join stop information to polygons and save as feature class arcpy.management.AddJoin(polygons, "stop_id", outStops, "stop_id") outPolys = outPolysname if direction != None: outPolys += str(direction) outPolysFC = os.path.join(outGDB, outPolys) arcpy.management.CopyFeatures(polygons, outPolysFC) # Add a route_id and direction_id field and populate it arcpy.management.AddField(outPolysFC, "route_id", "TEXT") arcpy.management.AddField(outPolysFC, "direction_id", "TEXT") fields = ["route_id", "direction_id"] with arcpy.da.UpdateCursor(outPolysFC, fields) as cursor: for row in cursor: row[0] = route_id row[1] = direction cursor.updateRow(row) except: arcpy.AddError("Error creating buffers around stops.") raise arcpy.AddMessage("Done!") arcpy.AddMessage("Output written to %s is:" % outGDB) outFClist = [] for direction in stoplist: outPolysFC = outPolysname outStopsFC = outStopsname if direction != None: outStopsFC += str(direction) outPolysFC += str(direction) outFClist.append(outStopsFC) outFClist.append(outPolysFC) arcpy.AddMessage("- " + outStopsFC) arcpy.AddMessage("- " + outPolysFC) # Tell the tool that this is output. This will add the output to the map. outFClistwpaths = [os.path.join(outGDB, fc) for fc in outFClist] arcpy.SetParameterAsText(8, ';'.join(outFClistwpaths)) except BBB_SharedFunctions.CustomError: arcpy.AddError("Failed to create buffers around stops for this route.") pass except: arcpy.AddError("Failed to create buffers around stops for this route.") raise finally: if OverwriteOutput: arcpy.env.overwriteOutput = OverwriteOutput
arcpy.management.AddField(inPointsLayer, inLocUniqueID, "LONG") arcpy.management.CalculateField(inPointsLayer, inLocUniqueID, "!" + pointsOID + "!", "PYTHON_9.3") except: arcpy.AddError("Unable to add or calculate new unique ID field. Please fix your data or choose a different unique ID field.") raise inLocUniqueID_qualified = inLocUniqueID + "_Input" arcpy.AddMessage("Run set up successfully.") # ----- Create a feature class of stops ------ try: arcpy.AddMessage("Getting GTFS stops...") tempstopsname = "Temp_Stops" if ".shp" in outFilename: tempstopsname += ".shp" StopsLayer, StopList = BBB_SharedFunctions.MakeStopsFeatureClass(os.path.join(outDir, tempstopsname)) except: arcpy.AddError("Error creating feature class of GTFS stops.") raise #----- Create OD Matrix between stops and user's points ----- try: arcpy.AddMessage("Creating OD matrix between points and stops...") arcpy.AddMessage("(This step could take a while for large datasets or buffer sizes.)") # Name to refer to OD matrix layer outNALayer_OD = "ODMatrix" # ODLayer is the NA Layer object returned by getOutput(0) ODLayer = arcpy.na.MakeODCostMatrixLayer(inNetworkDataset, outNALayer_OD,
def runTool(outStops, SQLDbase, day, start_time, end_time, DepOrArrChoice, FrequencyThreshold, SnapToNearest5MinuteBool): def RetrieveFrequencyStatsForStop(stop_id, rtdirtuple, snap_to_nearest_5_minutes=False): '''For a given stop, query the stop_time_dictionaries {stop_id: [[trip_id, stop_time]]} and return the NumTrips, NumTripsPerHr, MaxWaitTime, and AvgHeadway given a specific route_id and direction. If snap to nearest five minutes is true, then this function will return headways snapped to the closest 5 minute interval.''' try: stop_time_dictionaries = stoptimedict_rtedirpair[rtdirtuple] except KeyError: # We will get a KeyError if there were no trips found for the route/direction # pair, which usually happens if the wrong SQL database was selected. stop_time_dictionaries = {} # Make a list of stop_times StopTimesAtThisPoint = [] try: for trip in stop_time_dictionaries[stop_id]: StopTimesAtThisPoint.append(trip[1]) except KeyError: pass StopTimesAtThisPoint.sort() # Calculate the number of trips NumTrips = len(StopTimesAtThisPoint) NumTripsPerHr = float(NumTrips) / TimeWindowLength # Get the max wait time and the average headway MaxWaitTime = BBB_SharedFunctions.CalculateMaxWaitTime( StopTimesAtThisPoint, start_sec, end_sec) AvgHeadway = None if NumTrips > 1: AvgHeadway = max( 1, int( round( float( sum( abs(x - y) for (x, y) in zip(StopTimesAtThisPoint[1:], StopTimesAtThisPoint[:-1])) / (len(StopTimesAtThisPoint) - 1)) / 60, 0))) # minutes if snap_to_nearest_5_minutes: AvgHeadway = round(AvgHeadway / 5.0) * 5 return NumTrips, NumTripsPerHr, MaxWaitTime, AvgHeadway try: # ------ Get input parameters and set things up. ----- try: arcpy.env.overwriteOutput = True BBB_SharedFunctions.CheckArcVersion(min_version_pro="1.2", min_version_10x="10.4") try: import pandas as pd except: # Pandas is shipped with ArcGIS Pro and ArcGIS 10.4 and higher. The previous logic should hopefully prevent users from ever hitting this error. arcpy.AddError( "This BetterBusBuffers tool requires the python library pandas, but the tool was unable to import the library." ) raise BBB_SharedFunctions.CustomError # GTFS SQL dbase - must be created ahead of time. conn = BBB_SharedFunctions.conn = sqlite3.connect(SQLDbase) c = BBB_SharedFunctions.c = conn.cursor() Specific, day = BBB_SharedFunctions.CheckSpecificDate(day) start_sec, end_sec = BBB_SharedFunctions.ConvertTimeWindowToSeconds( start_time, end_time) # Window of Time In Hours TimeWindowLength = (end_sec - start_sec) / 3600 # threshold for headways to be counted FrequencyThreshold = float(FrequencyThreshold) # boolean will snap headways to nearest 5 minute increment (ie 11.5 minutes snaps to 10, but 13 snaps to 15) SnapToNearest5MinuteBool = bool(SnapToNearest5MinuteBool) # Does the user want to count arrivals or departures at the stops? DepOrArr = BBB_SharedFunctions.CleanUpDepOrArr(DepOrArrChoice) time_period = start_time + ":" + end_time except: arcpy.AddError("Error getting user inputs.") raise # ----- Create a feature class of stops and add fields for transit trip counts ------ try: arcpy.AddMessage("Creating feature class of GTFS stops...") # Create a feature class of transit stops outStops, StopIDList = BBB_SharedFunctions.MakeStopsFeatureClass( outStops) except: arcpy.AddError("Error creating feature class of GTFS stops.") raise # ----- Query the GTFS data to count the trips at each stop ----- try: arcpy.AddMessage( "Calculating the determining trips for route-direction pairs..." ) # Get the service_ids serving the correct days serviceidlist, serviceidlist_yest, serviceidlist_tom = \ BBB_SharedFunctions.GetServiceIDListsAndNonOverlaps(day, start_sec, end_sec, DepOrArr, Specific) # Assemble Route and Direction IDS triproutefetch = '''SELECT DISTINCT route_id,direction_id FROM trips;''' c.execute(triproutefetch) #route_dir_list = c.fetchall() # Some GTFS datasets use the same route_id to identify trips traveling in # either direction along a route. Others identify it as a different route. # We will consider each direction separately if there is more than one. trip_route_warning_counter = 0 trip_route_dict = { } # {(route_id, direction_id): [trip_id, trip_id,..]} trip_route_dict_yest = {} trip_route_dict_tom = {} triproutelist = [] c2 = conn.cursor() for rtpair in c: key = tuple(rtpair) route_id = rtpair[0] direction_id = rtpair[1] # Get list of trips # Ignore direction if this route doesn't have a direction if direction_id not in [ None, "" ]: # GTFS can have direction IDs of zero triproutefetch = ''' SELECT trip_id, service_id FROM trips WHERE route_id='%s' AND direction_id=%s ;''' % (route_id, direction_id) else: triproutefetch = ''' SELECT trip_id, service_id FROM trips WHERE route_id='%s' ;''' % route_id c2.execute(triproutefetch) triproutelist = c2.fetchall() if not triproutelist: arcpy.AddWarning( "Your GTFS dataset does not contain any trips \ corresponding to Route %s and Direction %s. Please ensure that \ you have selected the correct GTFS SQL file for this input file or that your \ GTFS data is good. Output fields will be generated, but \ the values will be 0 or <Null>." % (route_id, str(direction_id))) for triproute in triproutelist: # Only keep trips running on the correct day if triproute[1] in serviceidlist: trip_route_dict.setdefault(key, []).append(triproute[0]) if triproute[1] in serviceidlist_tom: trip_route_dict_tom.setdefault(key, []).append(triproute[0]) if triproute[1] in serviceidlist_yest: trip_route_dict_yest.setdefault(key, []).append( triproute[0]) if not trip_route_dict and not trip_route_dict_tom and not trip_route_dict_yest: arcpy.AddWarning( "There is no service for route %s in direction %s \ on %s during the time window you selected. Output fields will be generated, but \ the values will be 0 or <Null>." % (route_id, str(direction_id), str(day))) except: arcpy.AddError("Error getting trips associated with route.") raise # ----- Query the GTFS data to count the trips at each stop for this time period ----- try: arcpy.AddMessage( "Calculating the number of transit trips available during the time window of time period ID {0}..." .format(str(time_period))) frequencies_dict = BBB_SharedFunctions.MakeFrequenciesDict() stoptimedict_rtedirpair = {} # #{rtdir tuple:stoptimedict}} stoptimedict_service_check_counter = 0 for rtdirpair in list( set([ rt for rt in list(trip_route_dict.keys()) + list(trip_route_dict_yest.keys()) + list(trip_route_dict_tom.keys()) ])): # Get the stop_times that occur during this time window stoptimedict = {} stoptimedict_yest = {} stoptimedict_tom = {} try: triplist = trip_route_dict[rtdirpair] stoptimedict = BBB_SharedFunctions.GetStopTimesForStopsInTimeWindow( start_sec, end_sec, DepOrArr, triplist, "today", frequencies_dict) except KeyError: # No trips pass try: triplist_yest = trip_route_dict_yest[rtdirpair] stoptimedict_yest = BBB_SharedFunctions.GetStopTimesForStopsInTimeWindow( start_sec, end_sec, DepOrArr, triplist_yest, "yesterday", frequencies_dict) except KeyError: # No trips pass try: triplist_tom = trip_route_dict_tom[rtdirpair] stoptimedict_tom = BBB_SharedFunctions.GetStopTimesForStopsInTimeWindow( start_sec, end_sec, DepOrArr, triplist_tom, "tomorrow", frequencies_dict) except KeyError: # No trips pass # Combine the three dictionaries into one master for stop in stoptimedict_yest: # Update Dictionaries based on setdefault returns values. stoptimedict[stop] = stoptimedict.setdefault( stop, []) + stoptimedict_yest[stop] for stop in stoptimedict_tom: stoptimedict[stop] = stoptimedict.setdefault( stop, []) + stoptimedict_tom[stop] # PD here stoptimedict_rtedirpair[ rtdirpair] = stoptimedict # {rtdir tuple:{stoptimedict}} # Add a minor warning if there is no service for at least one route-direction combination. if not stoptimedict: stoptimedict_service_check_counter += 1 if stoptimedict_service_check_counter > 0: arcpy.AddWarning( "There is no service for %s route-direction pair(s) \ on %s during the time window you selected. Output fields will be generated, but \ the values will be 0 or <Null>." % (str(stoptimedict_service_check_counter), str(day))) except: arcpy.AddError( "Error counting arrivals or departures at stop during time window." ) raise # ----- Write to output ----- try: arcpy.AddMessage( "Calculating frequency statistics from route direction pairs..." ) frequency_record_table = [ ] #[(rtedirpair_id,route_id,direction_id,stop_id,NumTripsPerHr,MaxWaitTime,AvgHeadway)] labels = [ "rtedir_id", "rte_count", "stop_id", "NumTrips", "NumTripsPerHr", "MaxWaitTime", "AvgHeadway" ] for rtedirpair in stoptimedict_rtedirpair: route_id = rtedirpair[0] stops = stoptimedict_rtedirpair[rtedirpair].keys() for stop_id in stops: NumTrips, NumTripsPerHr, MaxWaitTime, AvgHeadway = RetrieveFrequencyStatsForStop( stop_id, rtedirpair, snap_to_nearest_5_minutes=SnapToNearest5MinuteBool) AvgHeadway = post_process_headways(AvgHeadway, NumTripsPerHr) frequency_record_table.append( (rtedirpair, route_id, stop_id, NumTrips, NumTripsPerHr, MaxWaitTime, AvgHeadway)) frequency_dataframe = pd.DataFrame.from_records( frequency_record_table, columns=labels) #Count the number of routes that meet threshold frequency_dataframe["MetHdWyLim"] = 1 frequency_dataframe["MetHdWyLim"].where( frequency_dataframe["AvgHeadway"] <= FrequencyThreshold, np.nan, inplace=True) #Add Fields for frequency aggregation frequency_dataframe["MinHeadway"] = frequency_dataframe[ "AvgHeadway"] frequency_dataframe["MaxHeadway"] = frequency_dataframe[ "AvgHeadway"] output_stats = collections.OrderedDict([("NumTrips", ("sum")), ("NumTripsPerHr", ("sum")), ("MaxWaitTime", ("max")), ("rte_count", ("count")), ("AvgHeadway", ("mean")), ("MinHeadway", ("min")), ("MaxHeadway", ("max")), ("MetHdWyLim", ("sum"))]) stop_groups = frequency_dataframe.groupby("stop_id") stop_frequency_statistics = stop_groups.agg(output_stats) if ".shp" in outStops: # Set up shapefile accommodations for long fields (>10 chars) & null values stop_frequency_statistics.rename(columns={ "NumTripsPerHr": "TripsPerHr", "MaxWaitTime": "MxWtTime" }, inplace=True) stop_frequency_statistics = stop_frequency_statistics.fillna( value=-1) except: arcpy.AddError("Error calculating frequency statistics...") raise try: arcpy.AddMessage("Writing output data...") # Create an update cursor to add numtrips, trips/hr, maxwaittime, and headway stats to stops frequency_records = stop_frequency_statistics.to_records() arcpy.da.ExtendTable(outStops, "stop_id", frequency_records, "stop_id", append_only=False) arcpy.AddMessage("Script complete!") except: arcpy.AddError("Error writing to output.") raise arcpy.AddMessage("Finished!") arcpy.AddMessage("Your output is located at " + outStops) except BBB_SharedFunctions.CustomError: arcpy.AddError("Failed to count high frequency routes at stops.") pass except: arcpy.AddError("Failed to count high frequency routes at stops.") raise
def runTool(outDir, outGDB, inSQLDbase, inNetworkDataset, imp, BufferSize, restrictions, TrimSettings): try: # ----- Set up the run ----- try: BBB_SharedFunctions.CheckArcVersion(min_version_pro="1.2") BBB_SharedFunctions.CheckArcInfoLicense() BBB_SharedFunctions.CheckOutNALicense() BBB_SharedFunctions.CheckWorkspace() # It's okay to overwrite in-memory stuff. OverwriteOutput = arcpy.env.overwriteOutput # Get the orignal value so we can reset it. arcpy.env.overwriteOutput = True # Append .gdb to geodatabase name. if not outGDB.lower().endswith(".gdb"): outGDB += ".gdb" outGDBwPath = os.path.join(outDir, outGDB) # Create a file geodatabase for the results. arcpy.management.CreateFileGDB(outDir, outGDB) # Make a copy of the input SQL file in the Step 1 output so we can modify it. SQLDbase = os.path.join(outGDBwPath, "Step1_GTFS.sql") copyfile(inSQLDbase, SQLDbase) # Connect to or create the SQL file. conn = sqlite3.connect(SQLDbase) c = BBB_SharedFunctions.c = conn.cursor() impedanceAttribute = BBB_SharedFunctions.CleanUpImpedance(imp) TrimPolys, TrimPolysValue = BBB_SharedFunctions.CleanUpTrimSettings( TrimSettings) except: arcpy.AddError("Error setting up run.") raise #----- Make a feature class of GTFS stops that we can use for buffers ----- try: # Create a feature class of transit stops arcpy.AddMessage("Creating a feature class of GTFS stops...") StopsLayer, StopIDList = BBB_SharedFunctions.MakeStopsFeatureClass( os.path.join(outGDBwPath, "Step1_Stops")) except: arcpy.AddError("Error creating a feature class of GTFS stops.") raise #----- Create Service Areas around all stops in the system ----- try: arcpy.AddMessage("Creating service areas around stops...") arcpy.AddMessage( "(This step will take a while for large networks.)") polygons = BBB_SharedFunctions.MakeServiceAreasAroundStops( StopsLayer, inNetworkDataset, impedanceAttribute, BufferSize, restrictions, TrimPolys, TrimPolysValue) except: arcpy.AddError("Error creating service areas around stops.") raise #----- Post-process the polygons to prepare for Step 2 ----- try: arcpy.AddMessage("Reformatting polygons for further analysis...") arcpy.AddMessage( "(This step will take a while for large networks.)") # ----- Flatten the overlapping service area polygons ----- # Use World Cylindrical Equal Area (WKID 54034) to ensure proper use of cluster tolerance in meters arcpy.env.outputCoordinateSystem = BBB_SharedFunctions.WorldCylindrical # Flatten the overlapping polygons. This will ultimately be our output. # Dummy points to use in FeatureToPolygon to get rid of unnecessary fields. dummypoints = arcpy.management.CreateFeatureclass( "in_memory", "DummyPoints", "POINT") # The flattened polygons will be our ultimate output in the end (final # output of step 2). FlatPolys = os.path.join(outGDBwPath, "Step1_FlatPolys") # FeatureToPolygon flattens overalpping polys. # Set a large cluster tolerance to eliminate small sliver polygons and to # keep the output file size down. Boundaries may move up to the distance # specified in the cluster tolerance, but some amount of movement is # acceptable, as service area polygons are inexact anyway. # The large cluster tolerance may cause some geometry issues with the output # later, but this is the best solution I've found so far that doesn't eat # up too much analysis time and memory clusTol = "5 meters" arcpy.management.FeatureToPolygon(polygons, FlatPolys, clusTol, "", dummypoints) arcpy.management.Delete(dummypoints) # Add a field to the output file for number of trips and num trips / hour. # Also create a polygon id field so we can keep track of them. arcpy.management.AddField(FlatPolys, "PolyID", "LONG") arcpy.management.AddField(FlatPolys, "NumTrips", "LONG") arcpy.management.AddField(FlatPolys, "NumTripsPerHr", "DOUBLE") arcpy.management.AddField(FlatPolys, "NumStopsInRange", "LONG") arcpy.management.AddField(FlatPolys, "MaxWaitTime", "DOUBLE") # ----- Create stacked points, one for each original SA polygon ----- # Create points for use in the Identity tool (one point per poly) FlattenedPoints = os.path.join(outGDBwPath, "Step1_FlattenedPoints") arcpy.management.FeatureToPoint(FlatPolys, FlattenedPoints, "INSIDE") # Use Identity to stack points and keep the stop_ids from the original SAs. # Results in a points layer with fields ORIG_FID for the IDs of the # flattened polygons and a stop_id column with the stop ids. # Points are stacked, and each has only one stop_id. StackedPoints = os.path.join(outGDBwPath, "Step1_StackedPoints") arcpy.analysis.Identity(FlattenedPoints, polygons, StackedPoints) arcpy.management.Delete(FlattenedPoints) # ----- Read the Stacked Points into an SQL table ----- # Create a SQL table associating the Polygon FID with the stop_ids that serve it. c.execute("DROP TABLE IF EXISTS StackedPoints;") schema = "Polygon_FID LONG, stop_id TEXT" create_stmt = "CREATE TABLE StackedPoints (%s);" % schema c.execute(create_stmt) # Add data to the table. Track Polygon IDs with no associated stop_ids so we can delete them. FIDsToDelete = [] AddToStackedPts = [] with arcpy.da.SearchCursor( StackedPoints, ["ORIG_FID", "stop_id"]) as StackedPtCursor: for row in StackedPtCursor: if not row[1]: FIDsToDelete.append(row[0]) else: AddToStackedPts.append(( row[0], row[1], )) # Add the OD items to the SQL table c.executemany( '''INSERT INTO StackedPoints \ (Polygon_FID, stop_id) \ VALUES (?, ?);''', AddToStackedPts) conn.commit() arcpy.management.Delete(StackedPoints) FIDsToDelete = set(FIDsToDelete) # ----- Delete polygons not associated with any stop_ids ----- # These were generated by the FeatureToPolygon tool in areas completely # surrounded by other polygons and aren't associated with any stops. # Make feature layer containing only the polygons we want to delete. desc2 = arcpy.Describe(FlatPolys) OutputOIDName = desc2.OIDFieldName # Anything with 0 area will just cause problems later. WhereClause = '"Shape_Area" = 0' if FIDsToDelete: WhereClause += ' OR "' + OutputOIDName + '" IN (' for FID in FIDsToDelete: WhereClause += str(FID) + ", " WhereClause = WhereClause[:-2] + ")" arcpy.management.MakeFeatureLayer(FlatPolys, "FlatPolysLayer", WhereClause) # Delete the polygons that don't correspond to any stop_ids. arcpy.management.DeleteFeatures("FlatPolysLayer") # ----- Populate the PolyID field ----- # Set PolyID equal to the OID. expression = "!" + OutputOIDName + "!" arcpy.management.CalculateField(FlatPolys, "PolyID", expression, "PYTHON") except: arcpy.AddError("Error post-processing polygons") raise arcpy.AddMessage("Done!") arcpy.AddMessage("Files written to output geodatabase " + outGDBwPath + ":") arcpy.AddMessage("- Step1_Stops") arcpy.AddMessage("- Step1_FlatPolys") arcpy.AddMessage("- Step1_GTFS.sql") # Tell the tool that this is output. This will add the output to the map. arcpy.SetParameterAsText(8, os.path.join(outGDBwPath, "Step1_Stops")) arcpy.SetParameterAsText(9, os.path.join(outGDBwPath, "Step1_FlatPolys")) arcpy.SetParameterAsText(10, os.path.join(outGDBwPath, "Step1_GTFS.sql")) except BBB_SharedFunctions.CustomError: arcpy.AddError("Failed to create BetterBusBuffers polygons.") pass except: arcpy.AddError("Failed to create BetterBusBuffers polygons.") raise
if TrimSettings: TrimPolys = "TRIM_POLYS" TrimPolysValue = str(TrimSettings) + " meters" else: TrimPolys = "NO_TRIM_POLYS" TrimPolysValue = "" except: arcpy.AddError("Error setting up run.") raise #----- Make a feature class of GTFS stops that we can use for buffers ----- try: # Create a feature class of transit stops arcpy.AddMessage("Creating a feature class of GTFS stops...") StopsLayer, StopIDList = BBB_SharedFunctions.MakeStopsFeatureClass( os.path.join(outGDBwPath, "Step1_Stops")) except: arcpy.AddError("Error creating a feature class of GTFS stops.") raise #----- Create Service Areas around all stops in the system ----- try: arcpy.AddMessage("Creating service areas around stops...") arcpy.AddMessage("(This step will take a while for large networks.)") polygons = BBB_SharedFunctions.MakeServiceAreasAroundStops( StopsLayer, inNetworkDataset, impedanceAttribute, BufferSize, restrictions, TrimPolys, TrimPolysValue) except: arcpy.AddError("Error creating service areas around stops.") raise
def runTool(outFile, SQLDbase, inPointsLayer, inLocUniqueID, day, start_time, end_time, BufferSize, BufferUnits, DepOrArrChoice, username, password): def runOD(Points, Stops): # Call the OD Cost Matrix service for this set of chunks result = ODservice.GenerateOriginDestinationCostMatrix( Points, Stops, TravelMode, Distance_Units=BufferUnits, Cutoff=BufferSize, Origin_Destination_Line_Shape=PathShape) # Check the status of the result object every 0.5 seconds # until it has a value of 4(succeeded) or greater while result.status < 4: time.sleep(0.5) # Print any warning or error messages returned from the tool result_severity = result.maxSeverity if result_severity == 2: errors = result.getMessages(2) if "No solution found." in errors: # No destinations were found for the origins, which probably just means they were too far away. pass else: arcpy.AddError("An error occured when running the tool") arcpy.AddError(result.getMessages(2)) raise BBB_SharedFunctions.CustomError elif result_severity == 1: arcpy.AddWarning("Warnings were returned when running the tool") arcpy.AddWarning(result.getMessages(1)) # Get the resulting OD Lines and store the stops that are reachable from points. if result_severity != 2: linesSubLayer = result.getOutput(1) with arcpy.da.SearchCursor( linesSubLayer, ["OriginOID", "DestinationOID"]) as ODCursor: for row in ODCursor: UID = pointsOIDdict[row[0]] SID = stopOIDdict[row[1]] PointsAndStops.setdefault(str(UID), []).append(str(SID)) try: # Source FC names are not prepended to field names. arcpy.env.qualifiedFieldNames = False # It's okay to overwrite in-memory stuff. OverwriteOutput = arcpy.env.overwriteOutput # Get the orignal value so we can reset it. arcpy.env.overwriteOutput = True BBB_SharedFunctions.CheckArcVersion(min_version_pro="1.2") BBB_SharedFunctions.ConnectToSQLDatabase(SQLDbase) Specific, day = BBB_SharedFunctions.CheckSpecificDate(day) start_sec, end_sec = BBB_SharedFunctions.ConvertTimeWindowToSeconds( start_time, end_time) # Distance between stops and points BufferSize_padded = BufferSize + (.2 * BufferSize) BufferLinearUnit = str(BufferSize_padded) + " " + BufferUnits # Will we calculate the max wait time? CalcWaitTime = True # Output file designated by user outDir = os.path.dirname(outFile) outFilename = os.path.basename(outFile) ispgdb = "esriDataSourcesGDB.AccessWorkspaceFactory" in arcpy.Describe( outDir).workspaceFactoryProgID inLocUniqueID = BBB_SharedFunctions.HandleOIDUniqueID( inPointsLayer, inLocUniqueID) # ----- Prepare OD service ----- try: arcpy.AddMessage( "Obtaining credentials for and information about OD Cost Matrix service..." ) # Hard-wired OD variables TravelMode = "Walking Distance" PathShape = "None" OD_service_name = "World/OriginDestinationCostMatrix" Utility_service_name = "World/Utilities" # Get the credentials from the signed in user and import the service if username and password: ODservice = BBB_SharedFunctions.import_AGOLservice( OD_service_name, username=username, password=password) Utilityservice = BBB_SharedFunctions.import_AGOLservice( Utility_service_name, username=username, password=password) else: credentials = arcpy.GetSigninToken() if not credentials: arcpy.AddError( "Please sign into ArcGIS Online or pass a username and password to the tool." ) raise BBB_SharedFunctions.CustomError token = credentials["token"] referer = credentials["referer"] ODservice = BBB_SharedFunctions.import_AGOLservice( OD_service_name, token=token, referer=referer) Utilityservice = BBB_SharedFunctions.import_AGOLservice( Utility_service_name, token=token, referer=referer) # Get the service limits from the OD service (how many origins and destinations allowed) utilresult = Utilityservice.GetToolInfo( "asyncODCostMatrix", "GenerateOriginDestinationCostMatrix") utilresultstring = utilresult.getOutput(0) utilresultjson = json.loads(utilresultstring) origin_limit = int( utilresultjson['serviceLimits']['maximumDestinations']) destination_limit = int( utilresultjson['serviceLimits']['maximumOrigins']) except: arcpy.AddError( "Failed to obtain credentials for and information about OD Cost Matrix service." ) raise # ----- Create a feature class of stops ------ try: arcpy.AddMessage("Getting GTFS stops...") tempstopsname = "Temp_Stops" StopsLayer, StopList = BBB_SharedFunctions.MakeStopsFeatureClass( os.path.join(outDir, tempstopsname)) # Select only the stops within a reasonable distance of points to reduce problem size arcpy.management.MakeFeatureLayer(StopsLayer, "StopsToRemove") arcpy.management.SelectLayerByLocation( "StopsToRemove", "WITHIN_A_DISTANCE_GEODESIC", inPointsLayer, BufferLinearUnit, invert_spatial_relationship="INVERT") arcpy.management.DeleteRows("StopsToRemove") arcpy.management.Delete("StopsToRemove") # Make Feature Layer of stops to use later arcpy.management.MakeFeatureLayer(StopsLayer, "StopsLayer") stopsOID = arcpy.Describe("StopsLayer").OIDFieldName except: arcpy.AddError("Error creating feature class of GTFS stops.") raise # ----- Prepare input data ----- try: arcpy.AddMessage("Preparing input points...") # Select only the points within a reasonable distance of stops to reduce problem size temppointsname = outFilename + "_Temp" relevantPoints = os.path.join(outDir, temppointsname) arcpy.management.MakeFeatureLayer(inPointsLayer, "PointsToKeep") arcpy.management.SelectLayerByLocation( "PointsToKeep", "WITHIN_A_DISTANCE_GEODESIC", StopsLayer, BufferLinearUnit) num_points = int( arcpy.management.GetCount("PointsToKeep").getOutput(0)) # If the number of points is large, sort them spatially for smart chunking if num_points > origin_limit: shapeFieldName = arcpy.Describe("PointsToKeep").shapeFieldName arcpy.management.Sort("PointsToKeep", relevantPoints, shapeFieldName, "PEANO") # Otherwise, just copy them. else: arcpy.management.CopyFeatures("PointsToKeep", relevantPoints) arcpy.management.Delete("PointsToKeep") # Store OIDs in a dictionary for later joining pointsOIDdict = {} # {OID: inLocUniqueID} with arcpy.da.SearchCursor(relevantPoints, ["OID@", inLocUniqueID]) as cur: for row in cur: pointsOIDdict[row[0]] = row[1] relevantpointsOID = arcpy.Describe(relevantPoints).OIDFieldName except: arcpy.AddError("Error preparing input points for analysis.") raise #----- Create OD Matrix between stops and user's points ----- try: arcpy.AddMessage("Creating OD matrix between points and stops...") arcpy.AddMessage( "(This step could take a while for large datasets or buffer sizes.)" ) global PointsAndStops # PointsAndStops = {LocID: [stop_1, stop_2, ...]} PointsAndStops = {} # Chunk the points to fit the service limits and loop through chunks points_numchunks = int(math.ceil(float(num_points) / origin_limit)) points_chunkstart = 0 points_chunkend = origin_limit current_chunk = 0 for x in range(0, points_numchunks): current_chunk += 1 arcpy.AddMessage("Handling input points chunk %i of %i" % (current_chunk, points_numchunks)) # Select only the points belonging to this chunk points_chunk = sorted( pointsOIDdict.keys())[points_chunkstart:points_chunkend] points_chunkstart = points_chunkend points_chunkend = points_chunkstart + origin_limit if ispgdb: points_selection_query = '[{0}] IN ({1})'.format( relevantpointsOID, ','.join(map(str, points_chunk))) else: points_selection_query = '"{0}" IN ({1})'.format( relevantpointsOID, ','.join(map(str, points_chunk))) arcpy.MakeFeatureLayer_management(relevantPoints, "PointsLayer", points_selection_query) # Select only the stops within the safe buffer of these points arcpy.management.SelectLayerByLocation( "StopsLayer", "WITHIN_A_DISTANCE_GEODESIC", "PointsLayer", BufferLinearUnit) num_stops = int( arcpy.GetCount_management("StopsLayer").getOutput(0)) stopOIDdict = {} # {OID: stop_id} with arcpy.da.SearchCursor("StopsLayer", ["OID@", "stop_id"]) as cur: for row in cur: stopOIDdict[row[0]] = row[1] # If the number of stops in range exceeds the destination limit, we have to chunk these as well. if num_stops > destination_limit: stops_numchunks = int( math.ceil(float(num_stops) / destination_limit)) stops_chunkstart = 0 stops_chunkend = destination_limit for x in range(0, stops_numchunks): stops_chunk = sorted(stopOIDdict.keys() )[stops_chunkstart:stops_chunkend] stops_chunkstart = stops_chunkend stops_chunkend = stops_chunkstart + destination_limit if ispgdb: stops_selection_query = '[{0}] IN ({1})'.format( stopsOID, ','.join(map(str, stops_chunk))) else: stops_selection_query = '"{0}" IN ({1})'.format( stopsOID, ','.join(map(str, stops_chunk))) arcpy.MakeFeatureLayer_management( "StopsLayer", "StopsLayer_Chunk", stops_selection_query) runOD("PointsLayer", "StopsLayer_Chunk") arcpy.management.Delete("StopsLayer_Chunk") # Otherwise, just run them all. else: runOD("PointsLayer", "StopsLayer") # Clean up arcpy.management.Delete("StopsLayer") arcpy.management.Delete("PointsLayer") arcpy.management.Delete(StopsLayer) arcpy.management.Delete(relevantPoints) except: arcpy.AddError( "Error creating OD matrix between stops and input points.") raise #----- Query the GTFS data to count the trips at each stop ----- try: arcpy.AddMessage( "Calculating the number of transit trips available during the time window..." ) # Get a dictionary of stop times in our time window {stop_id: [[trip_id, stop_time]]} stoptimedict = BBB_SharedFunctions.CountTripsAtStops( day, start_sec, end_sec, BBB_SharedFunctions.CleanUpDepOrArr(DepOrArrChoice), Specific) except: arcpy.AddError( "Error calculating the number of transit trips available during the time window." ) raise # ----- Generate output data ----- try: arcpy.AddMessage("Writing output data...") arcpy.management.CopyFeatures(inPointsLayer, outFile) # Add a field to the output file for number of trips and num trips / hour. arcpy.management.AddField(outFile, "NumTrips", "SHORT") arcpy.management.AddField(outFile, "NumTripsPerHr", "DOUBLE") arcpy.management.AddField(outFile, "NumStopsInRange", "SHORT") arcpy.management.AddField(outFile, "MaxWaitTime", "SHORT") with arcpy.da.UpdateCursor(outFile, [ inLocUniqueID, "NumTrips", "NumTripsPerHr", "NumStopsInRange", "MaxWaitTime" ]) as ucursor: for row in ucursor: try: ImportantStops = PointsAndStops[str(row[0])] except KeyError: # This point had no stops in range ImportantStops = [] NumTrips, NumTripsPerHr, NumStopsInRange, MaxWaitTime =\ BBB_SharedFunctions.RetrieveStatsForSetOfStops( ImportantStops, stoptimedict, CalcWaitTime, start_sec, end_sec) row[1] = NumTrips row[2] = NumTripsPerHr row[3] = NumStopsInRange row[4] = MaxWaitTime ucursor.updateRow(row) except: arcpy.AddError("Error writing output.") raise arcpy.AddMessage("Done!") arcpy.AddMessage("Output files written:") arcpy.AddMessage("- " + outFile) except BBB_SharedFunctions.CustomError: arcpy.AddError("Error counting transit trips at input locations.") pass except: arcpy.AddError("Error counting transit trips at input locations.") raise finally: # Reset overwriteOutput to what it was originally. arcpy.env.overwriteOutput = OverwriteOutput
def runTool(outFile, SQLDbase, inPointsLayer, inLocUniqueID, day, start_time, end_time, inNetworkDataset, imp, BufferSize, restrictions, DepOrArrChoice): try: # Source FC names are not prepended to field names. arcpy.env.qualifiedFieldNames = False # It's okay to overwrite in-memory stuff. OverwriteOutput = arcpy.env.overwriteOutput # Get the orignal value so we can reset it. arcpy.env.overwriteOutput = True BBB_SharedFunctions.CheckArcVersion(min_version_pro="1.2") ProductName = BBB_SharedFunctions.ProductName BBB_SharedFunctions.CheckWorkspace() BBB_SharedFunctions.CheckOutNALicense() BBB_SharedFunctions.ConnectToSQLDatabase(SQLDbase) Specific, day = BBB_SharedFunctions.CheckSpecificDate(day) start_sec, end_sec = BBB_SharedFunctions.ConvertTimeWindowToSeconds( start_time, end_time) # Will we calculate the max wait time? CalcWaitTime = True impedanceAttribute = BBB_SharedFunctions.CleanUpImpedance(imp) # Hard-wired OD variables ExcludeRestricted = "EXCLUDE" PathShape = "NO_LINES" accumulate = "" uturns = "ALLOW_UTURNS" hierarchy = "NO_HIERARCHY" # Output file designated by user outDir = os.path.dirname(outFile) outFilename = os.path.basename(outFile) inLocUniqueID = BBB_SharedFunctions.HandleOIDUniqueID( inPointsLayer, inLocUniqueID) inLocUniqueID_qualified = inLocUniqueID + "_Input" arcpy.AddMessage("Run set up successfully.") # ----- Create a feature class of stops ------ try: arcpy.AddMessage("Getting GTFS stops...") tempstopsname = "Temp_Stops" if ".shp" in outFilename: tempstopsname += ".shp" StopsLayer, StopList = BBB_SharedFunctions.MakeStopsFeatureClass( os.path.join(outDir, tempstopsname)) except: arcpy.AddError("Error creating feature class of GTFS stops.") raise #----- Create OD Matrix between stops and user's points ----- try: arcpy.AddMessage("Creating OD matrix between points and stops...") arcpy.AddMessage( "(This step could take a while for large datasets or buffer sizes.)" ) # Name to refer to OD matrix layer outNALayer_OD = "ODMatrix" # ODLayer is the NA Layer object returned by getOutput(0) ODLayer = arcpy.na.MakeODCostMatrixLayer( inNetworkDataset, outNALayer_OD, impedanceAttribute, BufferSize, "", accumulate, uturns, restrictions, hierarchy, "", PathShape).getOutput(0) # To refer to the OD sublayers, get the sublayer names. This is essential for localization. naSubLayerNames = arcpy.na.GetNAClassNames(ODLayer) points = naSubLayerNames["Origins"] stops = naSubLayerNames["Destinations"] # Add a field for stop_id as a unique identifier for stops. arcpy.na.AddFieldToAnalysisLayer(outNALayer_OD, stops, "stop_id", "TEXT") # Specify the field mappings for the stop_id field. fieldMappingStops = arcpy.na.NAClassFieldMappings(ODLayer, stops) fieldMappingStops["Name"].mappedFieldName = "stop_id" fieldMappingStops["stop_id"].mappedFieldName = "stop_id" # Add the GTFS stops as locations for the analysis. arcpy.na.AddLocations(outNALayer_OD, stops, StopsLayer, fieldMappingStops, "500 meters", "", "", "", "", "", "", ExcludeRestricted) # Clear out the memory because we don't need this anymore. arcpy.management.Delete(StopsLayer) # Add a field for unique identifier for points. arcpy.na.AddFieldToAnalysisLayer(outNALayer_OD, points, inLocUniqueID_qualified, "TEXT") # Specify the field mappings for the unique id field. fieldMappingPoints = arcpy.na.NAClassFieldMappings(ODLayer, points) fieldMappingPoints["Name"].mappedFieldName = inLocUniqueID fieldMappingPoints[ inLocUniqueID_qualified].mappedFieldName = inLocUniqueID # Add the input points as locations for the analysis. arcpy.na.AddLocations(outNALayer_OD, points, inPointsLayer, fieldMappingPoints, "500 meters", "", "", "", "", "", "", ExcludeRestricted) # Solve the OD matrix. try: arcpy.na.Solve(outNALayer_OD) except: errs = arcpy.GetMessages(2) if "No solution found" in errs: impunits = imp.split(" (Units: ")[1].split(")")[0] arcpy.AddError( "No transit stops were found within a %s %s walk of any of your input points. \ Consequently, there is no transit service available to your input points, so no output will be generated." % (str(BufferSize), impunits)) else: arcpy.AddError( "Failed to calculate travel time or distance between transit stops and input points. OD Cost Matrix error messages:" ) arcpy.AddError(errs) raise BBB_SharedFunctions.CustomError # Make layer objects for each sublayer we care about. if ProductName == 'ArcGISPro': naSubLayerNames = arcpy.na.GetNAClassNames(ODLayer) subLayerDict = dict( (lyr.name, lyr) for lyr in ODLayer.listLayers()) subLayers = {} for subL in naSubLayerNames: subLayers[subL] = subLayerDict[naSubLayerNames[subL]] else: subLayers = dict( (lyr.datasetName, lyr) for lyr in arcpy.mapping.ListLayers(ODLayer)[1:]) linesSubLayer = subLayers["ODLines"] pointsSubLayer = subLayers["Origins"] stopsSubLayer = subLayers["Destinations"] # Get the OID fields, just to be thorough desc1 = arcpy.Describe(pointsSubLayer) points_OID = desc1.OIDFieldName desc2 = arcpy.Describe(stopsSubLayer) stops_OID = desc2.OIDFieldName # Join polygons layer with input facilities to port over the stop_id arcpy.management.JoinField(linesSubLayer, "OriginID", pointsSubLayer, points_OID, [inLocUniqueID_qualified]) arcpy.management.JoinField(linesSubLayer, "DestinationID", stopsSubLayer, stops_OID, ["stop_id"]) # Use searchcursor on lines to find the stops that are reachable from points. global PointsAndStops # PointsAndStops = {LocID: [stop_1, stop_2, ...]} PointsAndStops = {} ODCursor = arcpy.da.SearchCursor( linesSubLayer, [inLocUniqueID_qualified, "stop_id"]) for row in ODCursor: PointsAndStops.setdefault(str(row[0]), []).append(str(row[1])) del ODCursor except: arcpy.AddError( "Error creating OD matrix between stops and input points.") raise #----- Query the GTFS data to count the trips at each stop ----- try: arcpy.AddMessage( "Calculating the number of transit trips available during the time window..." ) # Get a dictionary of stop times in our time window {stop_id: [[trip_id, stop_time]]} stoptimedict = BBB_SharedFunctions.CountTripsAtStops( day, start_sec, end_sec, BBB_SharedFunctions.CleanUpDepOrArr(DepOrArrChoice), Specific) except: arcpy.AddError( "Error calculating the number of transit trips available during the time window." ) raise # ----- Generate output data ----- try: arcpy.AddMessage("Writing output data...") arcpy.management.CopyFeatures(inPointsLayer, outFile) # Add a field to the output file for number of trips and num trips / hour. if ".shp" in outFilename: arcpy.management.AddField(outFile, "NumTrips", "SHORT") arcpy.management.AddField(outFile, "TripsPerHr", "DOUBLE") arcpy.management.AddField(outFile, "NumStops", "SHORT") arcpy.management.AddField(outFile, "MaxWaitTm", "SHORT") else: arcpy.management.AddField(outFile, "NumTrips", "SHORT") arcpy.management.AddField(outFile, "NumTripsPerHr", "DOUBLE") arcpy.management.AddField(outFile, "NumStopsInRange", "SHORT") arcpy.management.AddField(outFile, "MaxWaitTime", "SHORT") if ".shp" in outFilename: ucursor = arcpy.da.UpdateCursor(outFile, [ inLocUniqueID[0:10], "NumTrips", "TripsPerHr", "NumStops", "MaxWaitTm" ]) else: ucursor = arcpy.da.UpdateCursor(outFile, [ inLocUniqueID, "NumTrips", "NumTripsPerHr", "NumStopsInRange", "MaxWaitTime" ]) for row in ucursor: try: ImportantStops = PointsAndStops[str(row[0])] except KeyError: # This point had no stops in range ImportantStops = [] NumTrips, NumTripsPerHr, NumStopsInRange, MaxWaitTime =\ BBB_SharedFunctions.RetrieveStatsForSetOfStops( ImportantStops, stoptimedict, CalcWaitTime, start_sec, end_sec) row[1] = NumTrips row[2] = NumTripsPerHr row[3] = NumStopsInRange if ".shp" in outFilename and MaxWaitTime == None: row[4] = -1 else: row[4] = MaxWaitTime ucursor.updateRow(row) except: arcpy.AddError("Error writing output.") raise arcpy.AddMessage("Done!") arcpy.AddMessage("Output files written:") arcpy.AddMessage("- " + outFile) except BBB_SharedFunctions.CustomError: arcpy.AddError("Error counting transit trips at input locations.") pass except: arcpy.AddError("Error counting transit trips at input locations.") raise finally: # Reset overwriteOutput to what it was originally. arcpy.env.overwriteOutput = OverwriteOutput
def runTool(outStops, SQLDbase, day, start_time, end_time, DepOrArrChoice): try: BBB_SharedFunctions.CheckArcVersion(min_version_pro="1.2") BBB_SharedFunctions.ConnectToSQLDatabase(SQLDbase) Specific, day = BBB_SharedFunctions.CheckSpecificDate(day) start_sec, end_sec = BBB_SharedFunctions.ConvertTimeWindowToSeconds( start_time, end_time) # Will we calculate the max wait time? CalcWaitTime = True # ----- Create a feature class of stops and add fields for transit trip counts ------ try: arcpy.AddMessage("Creating feature class of GTFS stops...") # Create a feature class of transit stops outStops, StopIDList = BBB_SharedFunctions.MakeStopsFeatureClass( outStops) # Add a field to the output file for number of trips, num trips / hour, and max wait time if ".shp" in outStops: # Shapefiles can't have long field names arcpy.management.AddField(outStops, "NumTrips", "SHORT") arcpy.management.AddField(outStops, "TripsPerHr", "DOUBLE") arcpy.management.AddField(outStops, "MaxWaitTm", "SHORT") else: arcpy.management.AddField(outStops, "NumTrips", "SHORT") arcpy.management.AddField(outStops, "NumTripsPerHr", "DOUBLE") arcpy.management.AddField(outStops, "MaxWaitTime", "SHORT") except: arcpy.AddError("Error creating feature class of GTFS stops.") raise #----- Query the GTFS data to count the trips at each stop ----- try: arcpy.AddMessage( "Calculating the number of transit trips available during the time window..." ) # Get a dictionary of {stop_id: [[trip_id, stop_time]]} for our time window stoptimedict = BBB_SharedFunctions.CountTripsAtStops( day, start_sec, end_sec, BBB_SharedFunctions.CleanUpDepOrArr(DepOrArrChoice), Specific) except: arcpy.AddError( "Error counting arrivals or departures at stop during time window." ) raise # ----- Write to output ----- try: arcpy.AddMessage("Writing output data...") # Create an update cursor to add numtrips, trips/hr, and maxwaittime to stops if ".shp" in outStops: ucursor = arcpy.da.UpdateCursor( outStops, ["stop_id", "NumTrips", "TripsPerHr", "MaxWaitTm"]) else: ucursor = arcpy.da.UpdateCursor( outStops, ["stop_id", "NumTrips", "NumTripsPerHr", "MaxWaitTime"]) for row in ucursor: NumTrips, NumTripsPerHr, NumStopsInRange, MaxWaitTime = \ BBB_SharedFunctions.RetrieveStatsForSetOfStops( [row[0]], stoptimedict, CalcWaitTime, start_sec, end_sec) row[1] = NumTrips row[2] = NumTripsPerHr if ".shp" in outStops and MaxWaitTime == None: row[3] = -1 else: row[3] = MaxWaitTime ucursor.updateRow(row) except: arcpy.AddError("Error writing to output.") raise arcpy.AddMessage("Finished!") arcpy.AddMessage("Your output is located at " + outStops) except BBB_SharedFunctions.CustomError: arcpy.AddError("Failed to count trips at stops.") pass except: arcpy.AddError("Failed to count trips at stops.") raise