There are two ways to create toolboxes in ArcGIS: script tools in custom toolboxes that we covered in the last recipe, and script tools in Python toolboxes. Python toolboxes were introduced in version 10.1 of ArcGIS and they encapsulate everything in one place: parameters, validation code, and source code. This is not the case with custom toolboxes, which are created using a wizard and a separate script that processes business logic.
A Python Toolbox is similar to any other toolbox in ArcToolbox, but it is created entirely in Python and has a file extension of .pyt
. It is created programmatically as a class named Toolbox
. In this recipe, you will learn how to create a Python Toolbox and add a custom tool. After creating the basic structure of Toolbox
and Tool
, you'll complete the functionality of the tool by adding code that connects to an ArcGIS Server map service, downloads real-time data, and inserts it into a feature class.
Complete these steps to create a Python Toolbox and create a custom tool that connects to an ArcGIS Server map service, downloads real-time data, and inserts it into a feature class:
InsertWildfires.pyt
:.pyt
) can be edited in any text or code editor. By default, the code will open in Notepad. You can change this by setting the default editor for your script by going to Geoprocessing | Geoprocessing Options and going to the Editor section. You'll notice in the following screenshot that I have set my editor to PyScripter, which is my preferred environment. You may want to change this to IDLE or whatever development environment you are currently using. Please note that this step is not required though. As mentioned, by default, it will open your code in Notepad.InsertWildfires.pyt
and select Edit. This will open your development environment. Your development environment will vary depending on the editor that you have defined.Toolbox
. However, you will rename the Tool
class to reflect the name of the tool you want to create. Each tool will have various methods, including __init__()
, which is the constructor for the tool along with getParameterInfo()
, isLicensed()
, updateParameters()
, updateMessages()
, and execute()
. You can use the __init__()
method to set initialization properties, such as the tool's label and description. Look for the Tool
class and change the name to USGSDownload
. Also, set the label, and description, as seen in this code:class USGSDownload(object): def __init__(self): """Define the tool (tool name is the name of the class).""" self.label = "USGS Download" self.description = "Download from USGS ArcGIS Server instance"
Tool
class as a template for other tools you'd like to add to the toolbox by copying and pasting the class and its methods. We're not going to do this in this particular exercise, but I wanted you to be aware of this fact. You will need to add each tool to the tools
property of Toolbox
. Add the USGS Download
tool, as seen in this code:class Toolbox(object): def __init__(self): """Define the toolbox (the name of the toolbox is the name of the .pyt file).""" self.label = "Toolbox" self.alias = "" # List of tool classes associated with this toolbox self.tools = [USGSDownload]
getParameterInfo()
method to define the parameters for your tool. Individual Parameter
objects are created as part of this process. Add the following parameters in the getParameterInfo()
method and then we'll discuss them:Each Parameter
object is created using arcpy.Parameter
and is passed a number of arguments that define the object.
For the first Parameter
object (param0
), we are going to capture a URL for an ArcGIS Server map service containing current wildfire data. We give it a display name (ArcGIS Server Wildfire URL), which will be displayed in the dialog box for the tool, a name for the parameter, data type, parameter type (this is mandatory), and direction.
In the case of the first parameter (param0
), we also assign an initial value, which is the URL for an existing map service containing wildfire data.
For the second parameter, we define an output feature class where the wildfire data that is read from the map service will be written. An empty feature class to store the data has already been created for you. Finally, we added both parameters to a Python list called params
and return the list to the calling function
execute()
method. This is where the geoprocessing of your tool takes place. The execute()
method, seen in the following code, can accept a number of arguments, including the tool (self), parameters, and messages:def execute(self, parameters, messages): """The source code of the tool. """ return
valueAsText()
method. Add the following code to access the parameter values that will be passed into your tool. Remember, as seen in a previously mentioned step, that the first parameter will contain a URL for a map service containing wildfire data, and the second parameter is the output feature class where the data will be written:def execute(self, parameters, messages): inFeatures = parameters[0].valueAsText outFeatureClass = parameters[1].valueAsText
requests
(refer to http://docs.python-requests.org/en/latest/) module using pip
(refer to https://pip.pypa.io/en/latest/installing.html). Do this now before proceeding to the next step. Installation instructions for both pip
and requests
can be found at the links provided.QueryString
parameters that will be passed into the query of the map service. First, we'll import the requests
and json
modules by adding this code:import requests import json
QueryString
parameters. Notice that in this case we have defined a where
clause so that only the fires greater than 5
acres in size will be returned. The inFeatures
variable holds the URL:def execute(self, parameters, messages): inFeatures = parameters[0].valueAsText outFeatureClass = parameters[1].valueAsText agisurl = inFeatures payload = { 'where': 'acres > 5','f': 'pjson', 'outFields': 'latitude,longitude,fire_name,acres'}
r
. Print a message to the dialog indicating the response:def execute(self, parameters, messages): inFeatures = parameters[0].valueAsText outFeatureClass = parameters[1].valueAsText agisurl = inFeatures payload = { 'where': 'acres > 5','f': 'pjson', 'outFields': 'latitude,longitude,fire_name,acres'} r = requests.get(inFeatures, params=payload)
execute()
method and convert the JSON object to a Python dictionary:def execute(self, parameters, messages): inFeatures = parameters[0].valueAsText outFeatureClass = parameters[1].valueAsText agisurl = inFeatures payload = { 'where': 'acres > 5','f': 'pjson', 'outFields': 'latitude,longitude,fire_name,acres'} r = requests.get(inFeatures, params=payload) decoded = json.loads(r.text)
InsertCursor
by passing the output feature class defined in the tool dialog along with the fields that will be populated. We then start a for
loop that loops through each of the features (wildfires) that have been returned from the request to the ArcGIS Server map service. The decoded
variable is a Python dictionary. Inside the for
loop, we retrieve the fire name, latitude, longitude, and acres from the attributes
dictionary. Finally, we call the insertRow()
method to insert a new row into the feature class along with the fire name and acres as attributes. The progress information is written to Progress Dialog and the counter is updated. The execute()
method should now appear as follows:c:ArcpyBookcodeCh6InsertWildfires_PythonToolbox.py
solution file.c:ArcpyBookdata
. The RealTimeFires feature class is empty and has fields for NAME
and ACRES
.basemap
layer to provide a reference, as seen in this screenshot:The newer style ArcGIS Python Toolbox provides a Python-centric way of creating your custom script tools. The older style of creating custom script tools in ArcGIS for Desktop uses a combination of Python along with a wizard-based approach to define various aspects of the tool. The newer approach provides a more straightforward method for creating your tools. All the tools that you create are contained within a Toolbox
class that should not be renamed. By default, a single Tool
class will be created inside Toolbox
. This Tool
class should be renamed. In this recipe, we renamed it USGSDownload
. Inside the USGSDownload
class, the getParameterInfo()
and execute()
methods are present, among others. Using the getParameterInfo()
method, Parameter
objects can be defined to hold input data. In this tool, we defined a Parameter
to capture a URL for an ArcGIS Server map service containing live wildfire data and a second Parameter
object to reference a local feature class to hold the data. Finally, the execute()
method is triggered when the user clicks on the OK button in the tool. Parameter information is sent as an argument to the execute()
method in the form of the parameters
variable. Inside this method, a request to obtain the wildfire data from the remove ArcGIS Server instance is submitted using the Python requests
module. The response is returned as a json
object that is converted into a Python dictionary stored in a variable called decoded. The fire name, latitude, longitude, and acres are pulled out of the decoded variable and written to the local feature class using an InsertCursor
object from the arcpy.da
module. We'll cover the arcpy.da
module in great detail in a later chapter of the book.