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) polygons = BBB_SharedFunctions.MakeServiceAreasAroundStops( outStops, inNetworkDataset, impedanceAttribute, 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"] if BBB_SharedFunctions.ArcVersion == "10.0":
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
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