ArcGIS Python Toolbox – Using Two Input Feature Layers and Derived Output Feature Layer

arcgis-10.3arcpypython-toolbox

I'm unable to find an example of an ArcGIS Python Toolbox similar to the one I'm trying to get to work. The standalone python script works beautifully. Turning it into a Python Toolbox so I can use it in model builder / GP tool is where I'm having problems.

I'd like to be able to connect a point feature layer and a polyline feature layer to this tool as inputs, do some calculations, store the results in fields in the input point feature layer, and have the results available as an output feature layer. EDIT: My python script doesn't seem to have any output, per se. My thinking now is that I need the python script to create a feature class with all the fields, and then calculate them in the output (rather than the input).

The first input parameter, in_features should be a point feature set. The second input parameter is a single polyline, which in my case is a street centerline. The tool calculates station, offset and orientation values and places the results in their corresponding fields in the output feature layer. This means I can specify the output layer as "derived", right?

ArcCatalog reports no syntax errors. I get the message "This tool has no parameters" when trying to run the tool. The goal is to use this tool as a GP tool in ArcGIS Server. I'd like to be able to drop it into model builder and hook up the inputs and output.

Of course all this could be avoided if ESRI made the Near tool available to ArcGIS Standard license holders. Anyway, here's my CalculateStationOffset.pyt file:


Maybe I'm going about this all wrong. Perhaps I should be creating an output feature class in my Python code and calculating fields there. If I want the results to show up via the Web AppBuilder Geoprocessing widget, anyway. That's a whole other issue.

import arcpy

class Toolbox(object):
    def __init__(self):
        self.label = "Station Offset toolbox"
        self.alias = "StationOffset"

        # List of tool classes associated with this toolbox
        self.tools = [CalculateStationOffset]

class CalculateStationOffset(object):
    def __init__(self):
        self.label = "Calculate Station Offset"
        self.description = "Calculate Station Offset"

def getParameterInfo(self):
    #Define parameter definitions

    # Input Features parameter
    in_features = arcpy.Parameter(
        displayName="Input Features",
        name="in_features",
        datatype="GPFeatureLayer",
        parameterType="Required",
        direction="Input")

    in_features.filter.list = ["Point"]

    # Alignment parameter
    in_alignment = arcpy.Parameter(
        displayName="Alignment",
        name="in_alignment",
        datatype="GPFeatureLayer",
        parameterType="Required",
        direction="Input")

    in_alignment.filter.list = ["Polyline"]

    # Derived Output Features parameter
    out_features = arcpy.Parameter(
        displayName="Output Features",
        name="out_features",
        datatype="GPFeatureLayer",
        parameterType="Derived",
        direction="Output")

    out_features.parameterDependencies = [in_features.name]
    out_features.schema.clone = True

    parameters = [in_features, in_alignment, out_features] 

    return parameters

    def isLicensed(self):
        return True

    def updateParameters(self, parameters):
        if parameters[0].altered:
            parameters[1].value = arcpy.ValidateFieldName(parameters[1].value, parameters[0].value)
        return

    def updateMessages(self, parameters):
        return

def execute(self, parameters, messages):
    fc_pnt         = parameters[0].valueAsText
    fc_line        = parameters[1].valueAsText

    # Fields
    fields = ['SHAPE@', 'MEAS', 'Distance', 'NEAR_X', 'NEAR_Y', 'Orient', 'POINT_X', 'POINT_Y', 'NEAR_ANGLE']

    # Get line geometry - assumes only one feature in feature class
    polyline = arcpy.da.SearchCursor(fc_line, "SHAPE@").next()[0]

    # Loop over the point feature class
    with arcpy.da.UpdateCursor(fc_pnt, fields) as cursor:
        for row in cursor:
            dist=polyline.queryPointAndDistance(row[0], False)
            row[1] = dist[1]
            row[2] = dist[2]
            row[3] = dist[0].centroid.X
            row[4] = dist[0].centroid.Y
            if dist[3] == 0:
                row[5]="Left"
            else:
                row[5]="Right"
            row[6] = row[0].centroid.X
            row[7] = row[0].centroid.Y
            print(dist)
            cursor.updateRow(row)

Best Answer

The output parameter, when I've used it, seems to really only work to add the output as a layer to the map document the tool has been called in.

I noticed something that may be giving you errors in your code:

if parameters[0].altered:
     parameters[1].value = arcpy.ValidateFieldName(parameters[1].value, parameters[0].value)

parameter[1] is equivalent to in_alignment which is a feature layer. The ValidateFieldName tool requires the name of a field as a string. The tool is meant to check that a string can be used as a field name in a particular feature class.

Also, the steps you are currently doing the execute, is you are updating the input point feature class with all the attributes. This presumes that the list of fields are in the point feature class.