Example #1
0
def runTool(inGTFSdir, SQLDbase):
    try:

        BBB_SharedFunctions.CheckArcVersion(min_version_pro="1.2")

        #----- SQLize the GTFS data-----
        arcpy.AddMessage("SQLizing the GTFS data...")
        arcpy.AddMessage("(This will take a while for large datasets.)")

        # GTFS files
        if not SQLDbase.lower().endswith(".sql"):
            SQLDbase = SQLDbase + ".sql"

        # Fix up list of GTFS datasets
        inGTFSdirList = inGTFSdir.split(";")
        # Remove single quotes ArcGIS puts in if there are spaces in the filename.
        for d in inGTFSdirList:
            if d[0] == "'" and d[-1] == "'":
                loc = inGTFSdirList.index(d)
                inGTFSdirList[loc] = d[1:-1]

        # The main SQLizing work is done in the sqlize_csv module
        # written by Luitien Pan.
        # Connect to or create the SQL file.
        sqlize_csv.connect(SQLDbase)
        # Create tables.
        for tblname in sqlize_csv.sql_schema:
            sqlize_csv.create_table(tblname)
        # SQLize all the GTFS files, for each separate GTFS dataset.
        for gtfs_dir in inGTFSdirList:
            # handle_agency checks for blank values in arrival_time and departure_time
            sqlize_csv.handle_agency(gtfs_dir)

        # Create indices to make queries faster.
        sqlize_csv.create_indices()

        # Check for non-overlapping date ranges to prevent double-counting.
        overlapwarning = sqlize_csv.check_nonoverlapping_dateranges()
        if overlapwarning:
            arcpy.AddWarning(overlapwarning)

        arcpy.AddMessage("Successfully created SQL database of GTFS data:")
        arcpy.AddMessage("- " + SQLDbase)

    except BBB_SharedFunctions.CustomError:
        arcpy.AddMessage("Failed to create SQL database of GTFS data.")
        pass

    except:
        arcpy.AddMessage("Failed to create SQL database of GTFS data.")
        raise
Example #2
0
    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
Example #3
0
                          reset_headway_if_low_trip_count=180):
    """Used to adjust headways if there are low trips per hour observed in the GTFS dataset.
    If the number of trips per hour is below the trip frequency interval, headways are changed to
    reset_headway_if_low_trip_count_value (defaults to 180 minutes)."""
    if number_of_trips_per_hour <= trip_per_hr_threshold:  # If Number of Trips Per Hour is less than .5, set to 180.
        avg_headway = reset_headway_if_low_trip_count
    return avg_headway


try:
    # ------ Get input parameters and set things up. -----
    try:
        arcpy.env.overwriteOutput = True

        # Figure out what version of ArcGIS they're running
        BBB_SharedFunctions.DetermineArcVersion()
        if (BBB_SharedFunctions.ProductName != "ArcGISPro") and (
                BBB_SharedFunctions.ArcVersion
                in ["10.1", "10.2", "10.2.1", "10.2.2", "10.3", "10.3.1"]):
            arcpy.AddError(
                "This tool requires ArcGIS version 10.4 or higher or ArcGIS Pro."
            )
            raise CustomError
        if BBB_SharedFunctions.ProductName == "ArcGISPro" and BBB_SharedFunctions.ArcVersion in [
                "1.0", "1.1", "1.1.1"
        ]:
            arcpy.AddError(
                "The BetterBusBuffers toolbox does not work in versions of ArcGIS Pro prior to 1.2.\
You have ArcGIS Pro version %s." % BBB_SharedFunctions.ArcVersion)
            raise CustomError
Example #4
0
def runTool(inStep1GDB, outFile, day, start_time, end_time, DepOrArrChoice):
    try:

        # ----- Set up the run -----
        try:
            BBB_SharedFunctions.CheckArcVersion(min_version_pro="1.2")

            # Get the files from Step 1 to work with.
            # Step1_GTFS.sql and Step1_FlatPolys must exist in order for the tool to run.
            # Their existence is checked in the GUI validation logic.
            FlatPolys = os.path.join(inStep1GDB, "Step1_FlatPolys")
            SQLDbase = os.path.join(inStep1GDB, "Step1_GTFS.sql")
            # Connect to the SQL database
            conn = BBB_SharedFunctions.conn = sqlite3.connect(SQLDbase)
            c = BBB_SharedFunctions.c = conn.cursor()

            # Output file designated by user
            outDir = os.path.dirname(outFile)
            outFilename = os.path.basename(outFile)

            Specific, day = BBB_SharedFunctions.CheckSpecificDate(day)
            start_sec, end_sec = BBB_SharedFunctions.ConvertTimeWindowToSeconds(
                start_time, end_time)

            # Will we calculate the max wait time? This slows down the calculation, so leave it optional.
            CalcWaitTime = True

            # It's okay to overwrite stuff.
            OverwriteOutput = arcpy.env.overwriteOutput  # Get the orignal value so we can reset it.
            arcpy.env.overwriteOutput = True

        except:
            arcpy.AddError("Error setting up run.")
            raise

        #----- Query the GTFS data to count the trips at each stop -----
        try:
            arcpy.AddMessage(
                "Counting transit trips 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(
                "Failed to count transit trips during the time window.")
            raise

        #----- Find which stops serve each polygon -----
        try:
            arcpy.AddMessage(
                "Retrieving list of stops associated with each polygon...")
            # Find the stop_ids associated with each flattened polygon and put them in
            # a dictionary. {ORIG_FID: [stop_id, stop_id,...]}
            stackedpointdict = {}
            GetStackedPtsStmt = "SELECT * FROM StackedPoints"
            c.execute(GetStackedPtsStmt)
            for PolyFID in c:
                stackedpointdict.setdefault(PolyFID[0],
                                            []).append(str(PolyFID[1]))
        except:
            arcpy.AddError(
                "Error retrieving list of stops associated with each polygon.")
            raise

        # ----- Generate output data -----
        try:
            arcpy.AddMessage("Writing output data...")

            # Create the output file from FlatPolys.  We don't want to overwrite the
            # original Step 1 template file.
            arcpy.management.CopyFeatures(FlatPolys, outFile)
            badpolys = []

            if ".shp" in outFilename:
                ucursor = arcpy.da.UpdateCursor(outFile, [
                    "PolyID", "NumTrips", "NumTripsPe", "NumStopsIn",
                    "MaxWaitTim"
                ])
            else:
                ucursor = arcpy.da.UpdateCursor(outFile, [
                    "PolyID", "NumTrips", "NumTripsPerHr", "NumStopsInRange",
                    "MaxWaitTime"
                ])
            for row in ucursor:
                try:
                    ImportantStops = stackedpointdict[int(row[0])]
                except KeyError:
                    # If we got a KeyError here, then an output polygon never
                    # got a point associated with it, probably the result of a
                    # geometry problem because of the large cluster tolerance
                    # used to generate the polygons in Step 1. Just skip this
                    # polygon and alert the user.
                    badpolys.append(row[0])
                    continue
                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)

            if badpolys:
                arcpy.AddWarning(
                    "Warning! BetterBusBuffers could not calculate trip \
statistics for one or more polygons due to a geometry issue. These polygons will \
appear in your output data, but all output values will be null. Bad polygon \
PolyID values: " + str(badpolys))

        except:
            arcpy.AddMessage("Error writing output.")
            raise

        arcpy.AddMessage("Finished!")
        arcpy.AddMessage("Your output is located at " + outFile)

    except CustomError:
        arcpy.AddError("Error counting transit trips in polygons.")
        pass

    except:
        arcpy.AddError("Error counting transit trips in polygons.")
        raise

    finally:
        # Reset overwriteOutput to what it was originally.
        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
Example #6
0
from shutil import copyfile
import arcpy
import BBB_SharedFunctions


class CustomError(Exception):
    pass


try:

    # ----- Set up the run -----
    try:

        # Figure out what version of ArcGIS they're running
        BBB_SharedFunctions.DetermineArcVersion()
        if BBB_SharedFunctions.ProductName == "ArcGISPro" and BBB_SharedFunctions.ArcVersion in [
                "1.0", "1.1", "1.1.1"
        ]:
            arcpy.AddError(
                "The BetterBusBuffers toolbox does not work in versions of ArcGIS Pro prior to 1.2.\
You have ArcGIS Pro version %s." % BBB_SharedFunctions.ArcVersion)
            raise CustomError

        ArcLicense = arcpy.ProductInfo()
        if ArcLicense != "ArcInfo":
            arcpy.AddError("To run this tool, you must have the Desktop \
Advanced (ArcInfo) license.  Your license type is: %s." % ArcLicense)
            raise CustomError

        #Check out the Network Analyst extension license
Example #7
0
################################################################################

import arcpy
import BBB_SharedFunctions


class CustomError(Exception):
    pass


try:
    # ------ Get input parameters and set things up. -----
    try:

        # Figure out what version of ArcGIS they're running
        BBB_SharedFunctions.DetermineArcVersion()
        if BBB_SharedFunctions.ProductName == "ArcGISPro" and BBB_SharedFunctions.ArcVersion in [
                "1.0", "1.1", "1.1.1"
        ]:
            arcpy.AddError(
                "The BetterBusBuffers toolbox does not work in versions of ArcGIS Pro prior to 1.2.\
You have ArcGIS Pro version %s." % BBB_SharedFunctions.ArcVersion)
            raise CustomError
        if BBB_SharedFunctions.ArcVersion == "10.0":
            arcpy.AddError(
                "You must have ArcGIS 10.1 or higher (or ArcGIS Pro) to run this \
tool. You have ArcGIS version %s." % BBB_SharedFunctions.ArcVersion)
            raise CustomError

        # Get the files from Step 1 to work with.
        step1LinesFC = arcpy.GetParameter(0)
import arcpy
import BBB_SharedFunctions


class CustomError(Exception):
    pass


OverwriteOutput = None
conn = None

try:
    # ------ Get input parameters and set things up. -----
    try:
        # Figure out what version of ArcGIS they're running
        BBB_SharedFunctions.DetermineArcVersion()
        if BBB_SharedFunctions.ProductName == "ArcGISPro" and BBB_SharedFunctions.ArcVersion in [
                "1.0", "1.1", "1.1.1"
        ]:
            arcpy.AddError(
                "The BetterBusBuffers toolbox does not work in versions of ArcGIS Pro prior to 1.2.\
You have ArcGIS Pro version %s." % BBB_SharedFunctions.ArcVersion)
            raise CustomError

        #Check out the Network Analyst extension license
        if arcpy.CheckExtension("Network") == "Available":
            arcpy.CheckOutExtension("Network")
        else:
            arcpy.AddError(
                "You must have a Network Analyst license to use this tool.")
            raise CustomError
Example #9
0
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
            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
    
    # Figure out what version of ArcGIS they're running
    BBB_SharedFunctions.DetermineArcVersion()
    ArcVersion = BBB_SharedFunctions.ArcVersion
    ProductName = BBB_SharedFunctions.ProductName
    if ArcVersion == "10.0":
        arcpy.AddError("You must have ArcGIS 10.1 or higher (or ArcGIS Pro) to run this \
tool. You have ArcGIS version %s." % ArcVersion)
        raise CustomError
    if ProductName == "ArcGISPro" and ArcVersion in ["1.0", "1.1", "1.1.1"]:
        arcpy.AddError("The BetterBusBuffers toolbox does not work in versions of ArcGIS Pro prior to 1.2.\
You have ArcGIS Pro version %s." % ArcVersion)
        raise CustomError


    #----- Get input parameters -----
    
    # Output files and location
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
Example #13
0
# when the tool completes, but this ensures they don't conflict with any existing feature
# classes in the gdb and makes it easier to know what they are and delete them if they
# don't automatically get deleted.
guid = uuid.uuid4().hex
outStopPairsFCName = "StopPairs_" + guid
outStopPairsFC = os.path.join(outGDB, outStopPairsFCName)

# Get the original overwrite output setting so we can reset it at the end.
OverwriteOutput = arcpy.env.overwriteOutput
# It's okay to overwrite stuff in this tool
arcpy.env.overwriteOutput = True

try:

    # Figure out what version of ArcGIS they're running
    BBB_SharedFunctions.DetermineArcVersion()
    if BBB_SharedFunctions.ProductName == "ArcGISPro" and BBB_SharedFunctions.ArcVersion in [
            "1.0", "1.1", "1.1.1"
    ]:
        arcpy.AddError(
            "The BetterBusBuffers toolbox does not work in versions of ArcGIS Pro prior to 1.2.\
You have ArcGIS Pro version %s." % BBB_SharedFunctions.ArcVersion)
        raise CustomError
    if BBB_SharedFunctions.ArcVersion == "10.0":
        arcpy.AddError(
            "You must have ArcGIS 10.1 or higher (or ArcGIS Pro) to run this \
tool. You have ArcGIS version %s." % BBB_SharedFunctions.ArcVersion)
        raise CustomError

# ----- Connect to SQL locally for further queries and entries -----
Example #14
0
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
Example #15
0
################################################################################

import arcpy
import BBB_SharedFunctions


class CustomError(Exception):
    pass


try:
    # ------ Get input parameters and set things up. -----
    try:

        # Figure out what version of ArcGIS they're running
        BBB_SharedFunctions.DetermineArcVersion()
        if BBB_SharedFunctions.ProductName == "ArcGISPro" and BBB_SharedFunctions.ArcVersion in [
                "1.0", "1.1", "1.1.1"
        ]:
            arcpy.AddError(
                "The BetterBusBuffers toolbox does not work in versions of ArcGIS Pro prior to 1.2.\
You have ArcGIS Pro version %s." % BBB_SharedFunctions.ArcVersion)
            raise CustomError

        # Path for output feature class of GTFS stops.
        # Must be a file geodatabase feature class, not a shapefile.
        outStops = arcpy.GetParameterAsText(0)

        # GTFS SQL dbase - must be created ahead of time.
        SQLDbase = arcpy.GetParameterAsText(1)
        BBB_SharedFunctions.ConnectToSQLDatabase(SQLDbase)
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.'''
################################################################################

import os
import arcpy
import BBB_SharedFunctions

class CustomError(Exception):
    pass


try:
    # Figure out what version of ArcGIS they're running
    BBB_SharedFunctions.DetermineArcVersion()
    ArcVersion = BBB_SharedFunctions.ArcVersion
    ProductName = BBB_SharedFunctions.ProductName
    if BBB_SharedFunctions.ProductName == "ArcGISPro" and ArcVersion in ["1.0", "1.1", "1.1.1"]:
        arcpy.AddError("The BetterBusBuffers toolbox does not work in versions of ArcGIS Pro prior to 1.2.\
You have ArcGIS Pro version %s." % ArcVersion)
        raise CustomError

    #----- Get input parameters -----
    # Output files and location
    outFile = arcpy.GetParameterAsText(0)
    # GTFS SQL dbase - must be created ahead of time.
    SQLDbase = arcpy.GetParameterAsText(1)
    BBB_SharedFunctions.ConnectToSQLDatabase(SQLDbase)
    # Points to Analyze
    inPointsLayer = arcpy.GetParameterAsText(2)
import arcpy
import BBB_SharedFunctions


class CustomError(Exception):
    pass


OverwriteOutput = None

try:

    # ----- Set up the run -----
    try:
        # Figure out what version of ArcGIS they're running
        BBB_SharedFunctions.DetermineArcVersion()
        if BBB_SharedFunctions.ProductName == "ArcGISPro" and BBB_SharedFunctions.ArcVersion in [
                "1.0", "1.1", "1.1.1"
        ]:
            arcpy.AddError(
                "The BetterBusBuffers toolbox does not work in versions of ArcGIS Pro prior to 1.2.\
You have ArcGIS Pro version %s." % BBB_SharedFunctions.ArcVersion)
            raise CustomError

        # Get the files from Step 1 to work with.
        inStep1GDB = arcpy.GetParameterAsText(0)
        # Step1_GTFS.sql and Step1_FlatPolys must exist in order for the tool to run.
        # Their existence is checked in the GUI validation logic.
        FlatPolys = os.path.join(inStep1GDB, "Step1_FlatPolys")
        SQLDbase = os.path.join(inStep1GDB, "Step1_GTFS.sql")
        # Connect to the SQL database
Example #18
0
def runTool(outLinesFC, SQLDbase, combine_corridors):
    try:

        BBB_SharedFunctions.CheckArcVersion(min_version_pro="1.2")

        ispy3 = sys.version_info >= (3, 0)

        # Derived inputs
        outGDB = os.path.dirname(
            outLinesFC)  # Must be in fgdb. Validated in tool validation.
        # Create a guid to make sure temporary outputs have unique names. They should be deleted
        # when the tool completes, but this ensures they don't conflict with any existing feature
        # classes in the gdb and makes it easier to know what they are and delete them if they
        # don't automatically get deleted.
        guid = uuid.uuid4().hex
        outStopPairsFCName = "StopPairs_" + guid
        outStopPairsFC = os.path.join(outGDB, outStopPairsFCName)

        # Get the original overwrite output setting so we can reset it at the end.
        OverwriteOutput = arcpy.env.overwriteOutput
        # It's okay to overwrite stuff in this tool
        arcpy.env.overwriteOutput = True

        conn = BBB_SharedFunctions.conn = sqlite3.connect(SQLDbase)

        triproute_dict = BBB_SharedFunctions.MakeTripRouteDict()

        # ----- Initialize a dictionary of stop geometry -----

        # Get the stops table (exclude parent stations and station entrances)
        c = conn.cursor()
        selectstoptablestmt = "SELECT stop_id, stop_lat, stop_lon, location_type FROM stops;"
        c.execute(selectstoptablestmt)

        # Initialize a dictionary of stop lat/lon
        # {stop_id: <stop geometry object>} in the output coordinate system
        stoplatlon_dict = {}
        for stop in c:
            stop_id = stop[0]
            stop_lat = stop[1]
            stop_lon = stop[2]
            location_type = stop[3]
            if location_type not in [0, '0', None, ""]:
                # Skip parent stations and station entrances
                continue
            pt = arcpy.Point()
            pt.X = float(stop_lon)
            pt.Y = float(stop_lat)
            # GTFS stop lat/lon is written in WGS1984
            ptGeometry = arcpy.PointGeometry(pt, BBB_SharedFunctions.WGSCoords)
            stoplatlon_dict[stop_id] = ptGeometry

    # ----- Obtain schedule info from the stop_times.txt file and convert it to a line-based model -----

        arcpy.AddMessage(
            "Obtaining and processing transit schedule and line information..."
        )
        arcpy.AddMessage("(This will take a few minutes for large datasets.)")

        # Create a line-based schedule table
        c2 = conn.cursor()
        c2.execute("DROP TABLE IF EXISTS schedules;")
        c2.execute(
            "CREATE TABLE schedules (key TEXT, start_time REAL, end_time REAL, trip_id TEXT);"
        )

        # Find pairs of directly-connected stops
        linefeature_dict = {}
        stoptimefetch = '''
        SELECT trip_id, stop_id, arrival_time, departure_time
        FROM stop_times
        ORDER BY trip_id, stop_sequence
        ;'''
        c.execute(stoptimefetch)
        current_trip = None
        previous_stop = None
        start_time = None
        end_time = None
        for st in c:
            trip_id = st[0]
            stop_id = st[1]
            arrival_time = st[2]
            departure_time = st[3]
            if trip_id != current_trip:
                current_trip = trip_id
                previous_stop = stop_id
                start_time = departure_time  # Start time of segment is the departure time from the stop
                continue
            start_stop = previous_stop
            end_stop = stop_id
            end_time = arrival_time
            SourceOIDkey = "%s , %s" % (start_stop, end_stop)
            if combine_corridors:
                # All trips between each pair of stops will be combined, regardless of route_id
                linefeature_dict[SourceOIDkey] = True
            else:
                # A separate line will be created for each separate route between the same two stops
                linefeature_dict[SourceOIDkey + " , " +
                                 triproute_dict[trip_id]] = True
            stmt = """INSERT INTO schedules (key, start_time, end_time, trip_id) VALUES ('%s', %s, %s, '%s');""" % (
                SourceOIDkey, start_time, end_time, trip_id)
            c2.execute(stmt)
            previous_stop = stop_id
            start_time = departure_time
        conn.commit()
        c2.execute(
            "CREATE INDEX schedules_index_tripsstend ON schedules (trip_id, start_time, end_time);"
        )
        conn.commit()

        # ----- Write pairs to a points feature class (this is intermediate and will NOT go into the final output) -----

        # Create a points feature class for the point pairs.
        arcpy.management.CreateFeatureclass(outGDB, outStopPairsFCName,
                                            "POINT", "", "", "",
                                            BBB_SharedFunctions.WGSCoords)
        arcpy.management.AddField(outStopPairsFC, "stop_id", "TEXT")
        arcpy.management.AddField(outStopPairsFC, "pair_id", "TEXT")
        arcpy.management.AddField(outStopPairsFC, "sequence", "SHORT")

        # Add pairs of stops to the feature class in preparation for generating line features
        badStops = []
        badkeys = []
        with arcpy.da.InsertCursor(
                outStopPairsFC,
            ["SHAPE@", "stop_id", "pair_id", "sequence"]) as cur:
            # linefeature_dict = {"start_stop , end_stop , route_type": True}
            for SourceOIDkey in linefeature_dict:
                stopPair = SourceOIDkey.split(" , ")
                # {stop_id: [stop_lat, stop_lon]}
                try:
                    stop1 = stopPair[0]
                    stop1_geom = stoplatlon_dict[stop1]
                except KeyError:
                    badStops.append(stop1)
                    badkeys.append(SourceOIDkey)
                    continue
                try:
                    stop2 = stopPair[1]
                    stop2_geom = stoplatlon_dict[stop2]
                except KeyError:
                    badStops.append(stop2)
                    badkeys.append(SourceOIDkey)
                    continue
                cur.insertRow((stop1_geom, stop1, SourceOIDkey, 1))
                cur.insertRow((stop2_geom, stop2, SourceOIDkey, 2))

        if badStops:
            badStops = list(set(badStops))
            if ispy3:
                badStops_str = str(badStops)
            else:
                badStops_str = unicode(badStops)
            arcpy.AddWarning(
                "Your stop_times.txt lists times for the following \
stops which are not included in your stops.txt file. Schedule information for \
these stops will be ignored. " + badStops_str)

        # Remove these entries from the linefeatures dictionary so it doesn't cause false records later
        if badkeys:
            badkeys = list(set(badkeys))
            for key in badkeys:
                del linefeature_dict[key]

    # ----- Generate lines between all stops (for the final output) -----

        arcpy.management.PointsToLine(outStopPairsFC, outLinesFC, "pair_id",
                                      "sequence")
        if not combine_corridors:
            arcpy.management.AddField(outLinesFC, "route_id", "TEXT")

        # We don't need the points for anything anymore, so delete them.
        arcpy.management.Delete(outStopPairsFC)

        # Clean up lines with 0 length.  They will just produce build errors and
        # are not valuable for visualization anyway.
        expression = """"Shape_Length" = 0"""
        with arcpy.da.UpdateCursor(outLinesFC, ["pair_id"],
                                   expression) as cur2:
            for row in cur2:
                del linefeature_dict[row[0]]
                cur2.deleteRow()

        if not combine_corridors:
            with arcpy.da.UpdateCursor(outLinesFC,
                                       ["pair_id", "route_id"]) as cur4:
                for row in cur4:
                    row[1] = row[0].split(" , ")[2]
                    cur4.updateRow(row)

    # ----- Finish up. -----

        conn.close()

        arcpy.AddMessage("Finished!")
        arcpy.AddMessage("Your transit lines template feature class is:")
        arcpy.AddMessage("- " + outLinesFC)

    except BBB_SharedFunctions.CustomError:
        arcpy.AddError("Failed to generate transit lines.")
        pass

    except:
        arcpy.AddError("Failed to generate transit lines.")
        raise

    finally:
        # Reset the overwrite output to the user's original setting
        arcpy.env.overwriteOutput = OverwriteOutput
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
    BBB_SharedFunctions.ConnectToSQLDatabase(SQLDbase)
    # Points to Analyze
    inPointsLayer = arcpy.GetParameterAsText(2)
    # Unique ID for input points
    inLocUniqueID = arcpy.GetParameterAsText(3)
    inLocUniqueID_qualified = inLocUniqueID + "_Input"

    # Day and time window to analyze
    DayOfWeek = arcpy.GetParameterAsText(4)
    # Lower end of time window (HH:MM in 24-hour time)
    start_time = arcpy.GetParameterAsText(5)
    # Default start time is midnight if they leave it blank.
    if start_time == "":
        start_time = "00:00"
    # Convert to seconds
    start_sec = BBB_SharedFunctions.parse_time(start_time + ":00")
    # Upper end of time window (HH:MM in 24-hour time)
    end_time = arcpy.GetParameterAsText(6)
    # Default end time is 11:59pm if they leave it blank.
    if end_time == "":
        end_time = "23:59"
    # Convert to seconds
    end_sec = BBB_SharedFunctions.parse_time(end_time + ":00")

    # OD Cost Matrix stuff
    inNetworkDataset = arcpy.GetParameterAsText(7)
    imp = arcpy.GetParameterAsText(8)
    BufferSize = arcpy.GetParameterAsText(9)
    restrictions = arcpy.GetParameterAsText(10)

    # Will we calculate the max wait time? This slows down the calculation, so leave it optional.
Example #21
0
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.'''
################################################################################

import arcpy
import sqlize_csv
import BBB_SharedFunctions

class CustomError(Exception):
    pass

try:
    
    # Figure out what version of ArcGIS they're running
    BBB_SharedFunctions.DetermineArcVersion()
    if BBB_SharedFunctions.ProductName == "ArcGISPro" and BBB_SharedFunctions.ArcVersion in ["1.0", "1.1", "1.1.1"]:
        arcpy.AddError("The BetterBusBuffers toolbox does not work in versions of ArcGIS Pro prior to 1.2.\
You have ArcGIS Pro version %s." % BBB_SharedFunctions.ArcVersion)
        raise CustomError

    #----- SQLize the GTFS data-----
    arcpy.AddMessage("SQLizing the GTFS data...")
    arcpy.AddMessage("(This will take a while for large datasets.)")

    # GTFS files
    inGTFSdir = arcpy.GetParameterAsText(0)
    SQLDbase = arcpy.GetParameterAsText(1)
    if not SQLDbase.lower().endswith(".sql"):
        SQLDbase = SQLDbase + ".sql"