示例#1
0
def runTool(outGDB, SQLDbase, RouteText, inNetworkDataset, imp, BufferSize,
            restrictions, TrimSettings):
    try:
        OverwriteOutput = arcpy.env.overwriteOutput  # Get the orignal value so we can reset it.
        arcpy.env.overwriteOutput = True
        # Source FC names are not prepended to field names.
        arcpy.env.qualifiedFieldNames = False

        BBB_SharedFunctions.CheckArcVersion(min_version_pro="1.2")
        BBB_SharedFunctions.CheckOutNALicense()
        BBB_SharedFunctions.CheckWorkspace()

        # ===== Get trips and stops associated with this route =====

        # ----- Figure out which route the user wants to analyze based on the text input -----
        try:

            arcpy.AddMessage("Gathering route, trip, and stop information...")

            # Connect to or create the SQL file.
            conn = sqlite3.connect(SQLDbase)
            c = BBB_SharedFunctions.c = conn.cursor()

            # Get list of routes in the GTFS data
            routefetch = "SELECT route_short_name, route_long_name, route_id FROM routes;"
            c.execute(routefetch)
            # Extract the route_id based on what the user picked from the GUI list
            # It's better to do it by searching the database instead of trying to extract
            # the route_id from the text they chose because we don't know what kind of
            # characters will be in the route names and id, so parsing could be unreliable
            route_id = ""
            for route in c:
                routecheck = route[0] + ": " + route[1] + " [" + route[2] + "]"
                if routecheck == RouteText:
                    route_id = route[2]
                    route_short_name = route[0]
                    break

            if not route_id:
                arcpy.AddError("Could not parse route selection.")
                raise BBB_SharedFunctions.CustomError

            # Name feature classes
            outStopsname = arcpy.ValidateTableName("Stops_" + route_short_name,
                                                   outGDB)
            outPolysname = arcpy.ValidateTableName(
                "Buffers_" + route_short_name, outGDB)

        except:
            arcpy.AddError("Error determining route_id for analysis.")
            raise

        # ----- Get trips associated with route and split into directions -----
        try:
            # Some GTFS datasets use the same route_id to identify trips traveling in
            # either direction along a route. Others identify it as a different route.
            # We will consider each direction separately if there is more than one.

            # Get list of trips
            trip_route_dict = {}
            triproutefetch = '''
                SELECT trip_id, direction_id FROM trips
                WHERE route_id='%s'
                ;''' % route_id
            c.execute(triproutefetch)

            # Fill some dictionaries for use later.
            trip_dir_dict = {}  # {Direction: [trip_id, trip_id, ...]}
            for triproute in c:
                trip_dir_dict.setdefault(triproute[1], []).append(triproute[0])
            if not trip_dir_dict:
                arcpy.AddError(
                    "There are no trips in the GTFS data for the route \
you have selected (%s).  Please select a different route or fix your GTFS \
dataset." % RouteText)
                raise BBB_SharedFunctions.CustomError

        except:
            arcpy.AddError("Error getting trips associated with route.")
            raise

        # ----- Get list of stops associated with trips and split into directions -----
        try:
            # If a stop is used for trips going in both directions, count them separately.

            # Select unique set of stops used by trips in each direction
            stoplist = {}  # {Direction: [stop_id, stop_id, ...]}
            for direction in trip_dir_dict:
                stops = []
                for trip in trip_dir_dict[direction]:
                    stopsfetch = '''SELECT stop_id FROM stop_times
                                WHERE trip_id == ?'''
                    c.execute(stopsfetch, (trip, ))
                    for stop in c:
                        stops.append(stop[0])
                stoplist[direction] = list(set(stops))

            # If there is more than one direction, we will append the direction number
            # to the output fc names, so add an _ here for prettiness.
            if len(stoplist) > 1:
                arcpy.AddMessage(
                    "Route %s contains trips going in more than one \
direction. A separate feature class will be created for each direction, and the \
GTFS direction_id will be appended to the feature class name." %
                    route_short_name)
                outStopsname += "_"
                outPolysname += "_"

        except:
            arcpy.AddError("Error getting stops associated with route.")
            raise

        # ===== Create output =====

        # ----- Create a feature class of stops ------
        try:

            arcpy.AddMessage("Creating feature class of GTFS stops...")

            for direction in stoplist:
                stops = stoplist[direction]
                outputname = outStopsname
                if direction != None:
                    outputname += str(direction)
                outStops = os.path.join(outGDB, outputname)

                outStops, outStopList = BBB_SharedFunctions.MakeStopsFeatureClass(
                    outStops, stops)

                # Add a route_id and direction_id field and populate it
                arcpy.management.AddField(outStops, "route_id", "TEXT")
                arcpy.management.AddField(outStops, "direction_id", "TEXT")
                fields = ["route_id", "direction_id"]
                with arcpy.da.UpdateCursor(outStops, fields) as cursor:
                    for row in cursor:
                        row[0] = route_id
                        row[1] = direction
                        cursor.updateRow(row)

        except:
            arcpy.AddError("Error creating feature class of GTFS stops.")
            raise

        #----- Create Service Areas around stops -----
        try:

            arcpy.AddMessage("Creating buffers around stops...")

            for direction in stoplist:
                outputname = outStopsname
                if direction != None:
                    outputname += str(direction)
                outStops = os.path.join(outGDB, outputname)

                TrimPolys, TrimPolysValue = BBB_SharedFunctions.CleanUpTrimSettings(
                    TrimSettings)
                polygons = BBB_SharedFunctions.MakeServiceAreasAroundStops(
                    outStops, inNetworkDataset,
                    BBB_SharedFunctions.CleanUpImpedance(imp), BufferSize,
                    restrictions, TrimPolys, TrimPolysValue)

                # Join stop information to polygons and save as feature class
                arcpy.management.AddJoin(polygons, "stop_id", outStops,
                                         "stop_id")
                outPolys = outPolysname
                if direction != None:
                    outPolys += str(direction)
                outPolysFC = os.path.join(outGDB, outPolys)
                arcpy.management.CopyFeatures(polygons, outPolysFC)

                # Add a route_id and direction_id field and populate it
                arcpy.management.AddField(outPolysFC, "route_id", "TEXT")
                arcpy.management.AddField(outPolysFC, "direction_id", "TEXT")
                fields = ["route_id", "direction_id"]
                with arcpy.da.UpdateCursor(outPolysFC, fields) as cursor:
                    for row in cursor:
                        row[0] = route_id
                        row[1] = direction
                        cursor.updateRow(row)

        except:
            arcpy.AddError("Error creating buffers around stops.")
            raise

        arcpy.AddMessage("Done!")
        arcpy.AddMessage("Output written to %s is:" % outGDB)
        outFClist = []
        for direction in stoplist:
            outPolysFC = outPolysname
            outStopsFC = outStopsname
            if direction != None:
                outStopsFC += str(direction)
                outPolysFC += str(direction)
            outFClist.append(outStopsFC)
            outFClist.append(outPolysFC)
            arcpy.AddMessage("- " + outStopsFC)
            arcpy.AddMessage("- " + outPolysFC)

        # Tell the tool that this is output. This will add the output to the map.
        outFClistwpaths = [os.path.join(outGDB, fc) for fc in outFClist]
        arcpy.SetParameterAsText(8, ';'.join(outFClistwpaths))

    except BBB_SharedFunctions.CustomError:
        arcpy.AddError("Failed to create buffers around stops for this route.")
        pass

    except:
        arcpy.AddError("Failed to create buffers around stops for this route.")
        raise

    finally:
        if OverwriteOutput:
            arcpy.env.overwriteOutput = OverwriteOutput
def runTool(outDir, outGDB, inSQLDbase, inNetworkDataset, imp, BufferSize,
            restrictions, TrimSettings):
    try:

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

            BBB_SharedFunctions.CheckArcVersion(min_version_pro="1.2")
            BBB_SharedFunctions.CheckArcInfoLicense()
            BBB_SharedFunctions.CheckOutNALicense()

            BBB_SharedFunctions.CheckWorkspace()

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

            # Append .gdb to geodatabase name.
            if not outGDB.lower().endswith(".gdb"):
                outGDB += ".gdb"
            outGDBwPath = os.path.join(outDir, outGDB)
            # Create a file geodatabase for the results.
            arcpy.management.CreateFileGDB(outDir, outGDB)

            # Make a copy of the input SQL file in the Step 1 output so we can modify it.
            SQLDbase = os.path.join(outGDBwPath, "Step1_GTFS.sql")
            copyfile(inSQLDbase, SQLDbase)
            # Connect to or create the SQL file.
            conn = sqlite3.connect(SQLDbase)
            c = BBB_SharedFunctions.c = conn.cursor()

            impedanceAttribute = BBB_SharedFunctions.CleanUpImpedance(imp)
            TrimPolys, TrimPolysValue = BBB_SharedFunctions.CleanUpTrimSettings(
                TrimSettings)

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

    #----- Make a feature class of GTFS stops that we can use for buffers -----
        try:
            # Create a feature class of transit stops
            arcpy.AddMessage("Creating a feature class of GTFS stops...")
            StopsLayer, StopIDList = BBB_SharedFunctions.MakeStopsFeatureClass(
                os.path.join(outGDBwPath, "Step1_Stops"))
        except:
            arcpy.AddError("Error creating a feature class of GTFS stops.")
            raise

    #----- Create Service Areas around all stops in the system -----
        try:
            arcpy.AddMessage("Creating service areas around stops...")
            arcpy.AddMessage(
                "(This step will take a while for large networks.)")
            polygons = BBB_SharedFunctions.MakeServiceAreasAroundStops(
                StopsLayer, inNetworkDataset, impedanceAttribute, BufferSize,
                restrictions, TrimPolys, TrimPolysValue)
        except:
            arcpy.AddError("Error creating service areas around stops.")
            raise

    #----- Post-process the polygons to prepare for Step 2 -----
        try:

            arcpy.AddMessage("Reformatting polygons for further analysis...")
            arcpy.AddMessage(
                "(This step will take a while for large networks.)")

            # ----- Flatten the overlapping service area polygons -----

            # Use World Cylindrical Equal Area (WKID 54034) to ensure proper use of cluster tolerance in meters
            arcpy.env.outputCoordinateSystem = BBB_SharedFunctions.WorldCylindrical

            # Flatten the overlapping polygons.  This will ultimately be our output.
            # Dummy points to use in FeatureToPolygon to get rid of unnecessary fields.
            dummypoints = arcpy.management.CreateFeatureclass(
                "in_memory", "DummyPoints", "POINT")

            # The flattened polygons will be our ultimate output in the end (final
            # output of step 2).
            FlatPolys = os.path.join(outGDBwPath, "Step1_FlatPolys")
            # FeatureToPolygon flattens overalpping polys.
            # Set a large cluster tolerance to eliminate small sliver polygons and to
            # keep the output file size down.  Boundaries may move up to the distance
            # specified in the cluster tolerance, but some amount of movement is
            # acceptable, as service area polygons are inexact anyway.
            # The large cluster tolerance may cause some geometry issues with the output
            # later, but this is the best solution I've found so far that doesn't eat
            # up too much analysis time and memory
            clusTol = "5 meters"
            arcpy.management.FeatureToPolygon(polygons, FlatPolys, clusTol, "",
                                              dummypoints)
            arcpy.management.Delete(dummypoints)

            # Add a field to the output file for number of trips and num trips / hour.
            # Also create a polygon id field so we can keep track of them.
            arcpy.management.AddField(FlatPolys, "PolyID", "LONG")
            arcpy.management.AddField(FlatPolys, "NumTrips", "LONG")
            arcpy.management.AddField(FlatPolys, "NumTripsPerHr", "DOUBLE")
            arcpy.management.AddField(FlatPolys, "NumStopsInRange", "LONG")
            arcpy.management.AddField(FlatPolys, "MaxWaitTime", "DOUBLE")

            # ----- Create stacked points, one for each original SA polygon -----

            # Create points for use in the Identity tool (one point per poly)
            FlattenedPoints = os.path.join(outGDBwPath,
                                           "Step1_FlattenedPoints")
            arcpy.management.FeatureToPoint(FlatPolys, FlattenedPoints,
                                            "INSIDE")

            # Use Identity to stack points and keep the stop_ids from the original SAs.
            # Results in a points layer with fields ORIG_FID for the IDs of the
            # flattened polygons and a stop_id column with the stop ids.
            # Points are stacked, and each has only one stop_id.
            StackedPoints = os.path.join(outGDBwPath, "Step1_StackedPoints")
            arcpy.analysis.Identity(FlattenedPoints, polygons, StackedPoints)
            arcpy.management.Delete(FlattenedPoints)

            # ----- Read the Stacked Points into an SQL table -----

            # Create a SQL table associating the Polygon FID with the stop_ids that serve it.
            c.execute("DROP TABLE IF EXISTS StackedPoints;")
            schema = "Polygon_FID LONG, stop_id TEXT"
            create_stmt = "CREATE TABLE StackedPoints (%s);" % schema
            c.execute(create_stmt)

            # Add data to the table. Track Polygon IDs with no associated stop_ids so we can delete them.
            FIDsToDelete = []
            AddToStackedPts = []
            with arcpy.da.SearchCursor(
                    StackedPoints, ["ORIG_FID", "stop_id"]) as StackedPtCursor:
                for row in StackedPtCursor:
                    if not row[1]:
                        FIDsToDelete.append(row[0])
                    else:
                        AddToStackedPts.append((
                            row[0],
                            row[1],
                        ))
            # Add the OD items to the SQL table
            c.executemany(
                '''INSERT INTO StackedPoints \
                            (Polygon_FID, stop_id) \
                            VALUES (?, ?);''', AddToStackedPts)
            conn.commit()
            arcpy.management.Delete(StackedPoints)
            FIDsToDelete = set(FIDsToDelete)

            # ----- Delete polygons not associated with any stop_ids -----
            # These were generated by the FeatureToPolygon tool in areas completely
            # surrounded by other polygons and aren't associated with any stops.

            # Make feature layer containing only the polygons we want to delete.
            desc2 = arcpy.Describe(FlatPolys)
            OutputOIDName = desc2.OIDFieldName
            # Anything with 0 area will just cause problems later.
            WhereClause = '"Shape_Area" = 0'
            if FIDsToDelete:
                WhereClause += ' OR "' + OutputOIDName + '" IN ('
                for FID in FIDsToDelete:
                    WhereClause += str(FID) + ", "
                WhereClause = WhereClause[:-2] + ")"
            arcpy.management.MakeFeatureLayer(FlatPolys, "FlatPolysLayer",
                                              WhereClause)

            # Delete the polygons that don't correspond to any stop_ids.
            arcpy.management.DeleteFeatures("FlatPolysLayer")

            # ----- Populate the PolyID field -----

            # Set PolyID equal to the OID.
            expression = "!" + OutputOIDName + "!"
            arcpy.management.CalculateField(FlatPolys, "PolyID", expression,
                                            "PYTHON")

        except:
            arcpy.AddError("Error post-processing polygons")
            raise

        arcpy.AddMessage("Done!")
        arcpy.AddMessage("Files written to output geodatabase " + outGDBwPath +
                         ":")
        arcpy.AddMessage("- Step1_Stops")
        arcpy.AddMessage("- Step1_FlatPolys")
        arcpy.AddMessage("- Step1_GTFS.sql")

        # Tell the tool that this is output. This will add the output to the map.
        arcpy.SetParameterAsText(8, os.path.join(outGDBwPath, "Step1_Stops"))
        arcpy.SetParameterAsText(9, os.path.join(outGDBwPath,
                                                 "Step1_FlatPolys"))
        arcpy.SetParameterAsText(10, os.path.join(outGDBwPath,
                                                  "Step1_GTFS.sql"))

    except BBB_SharedFunctions.CustomError:
        arcpy.AddError("Failed to create BetterBusBuffers polygons.")
        pass
    except:
        arcpy.AddError("Failed to create BetterBusBuffers polygons.")
        raise