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)
-
Is your problem in creating the tool with parameters? have a read of desktop.arcgis.com/en/arcmap/10.3/analyze/creating-tools/… that talks about parameters...Michael Stimson– Michael Stimson2016年04月26日 22:13:54 +00:00Commented Apr 26, 2016 at 22:13
-
Thought I would try this using a Python Toolbox as it is supposed to be the new way of doing things. I've been all over the help but there just aren't enough examples. desktop.arcgis.com/en/arcmap/10.3/analyze/creating-tools/…Rayner– Rayner2016年04月27日 06:58:42 +00:00Commented Apr 27, 2016 at 6:58
-
Maybe I'm better off working with a script tool instead, as long as I can hook up a feature set as one of the input parameters. This is so the user can interactively input one or more points by clicking on the data frame. Didn't think the Python Toolbox would be so difficult to work with.Rayner– Rayner2016年04月27日 07:08:16 +00:00Commented Apr 27, 2016 at 7:08
-
I think you are getting mixed up on what type of parameter to use. The difference between a script tool and a python toolbox isn't huge, and it's actually possible to write code that can be used as either. A derived parameter is used when the user has no input on what the parameter actually is. Either the tool automatically creates it (name and all) based on input data, or the output is one of the inputs, just modified. The output of AddField is a derived parameter, since the output is actually just the input layer, with an additional field.Evil Genius– Evil Genius2016年04月27日 11:23:43 +00:00Commented Apr 27, 2016 at 11:23
-
2Is the spacing in your example code in the post the same as your actual code? If so, you're getting the no parameters error because your Python syntax is likely valid, but the getParameterInfo, execute, and other methods are standalone methods and not part of the CalculateStationObject class. They need to be indented to be part of the class.Josh Werts– Josh Werts2016年04月27日 12:17:57 +00:00Commented Apr 27, 2016 at 12:17
2 Answers 2
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.
So the answer to my original question "Is it possible to have two input feature layers and a derived output feature layer in an ArcGIS Python Toolbox tool?" is of course yes.
Finally got the Python Toolbox to work. Was a combination of parts of my code not being indented correctly and working out the correct input and output tools in model builder to expose the parameters to the geoprocessing widget in Web AppBuilder. Next step is to figure out how to get it to do the calculation in TN State Plane NAD83 rather than Web Mercator (auxiliary sphere). Thanks everyone for your help.
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)
-
You can use the projectas option on your point and polyline geometry before doing the queryPointAndDistance.dslamb– dslamb2016年04月27日 18:23:06 +00:00Commented Apr 27, 2016 at 18:23
-
I've been trying to figure out how to do this next step. Thinking of posting this as a new question.Rayner– Rayner2016年04月28日 12:46:11 +00:00Commented Apr 28, 2016 at 12:46
-
1There are a few examples: gis.stackexchange.com/questions/189883/… and gis.stackexchange.com/questions/137700/… but you will get better responses by breaking this out as another question with the code you have so far.dslamb– dslamb2016年04月28日 12:50:22 +00:00Commented Apr 28, 2016 at 12:50
-
Here's the link to my follow-up question: gis.stackexchange.com/questions/191543/…Rayner– Rayner2016年04月28日 13:41:47 +00:00Commented Apr 28, 2016 at 13:41