Create a Python script tool using arcpy.nax

Available with Network Analyst license.

You can use the arcpy.nax Python module to automate network analysis workflows. After writing a Python script using arcpy.nax, you can turn a script into a script tool so it can be run like any other geoprocessing tool. In this tutorial, you will learn how to code a simple network analysis workflow and configure a script tool to run it.

Note:

Although in this tutorial, you will learn how to create a script tool in an .atbx toolbox, you can also create a custom geoprocessing tool with Python and the arcpy.nax module using a Python toolbox (.pyt). Learn the differences between a script tool and a Python toolbox (.pyt). Many of the lessons from this tutorial apply to both types of tools.

The script tool you will create in this tutorial will run a Service Area analysis and write the resulting Service Area polygons to a feature class.

Note:

This tutorial uses Service Area analysis as an example; however, you can create a script tool to run any type of network analysis.

The tool will include the following parameters, which can be set by the user of the tool:

  • Input Facilities—The points around which the Service Area polygons will be calculated
  • Output Polygons—The output feature class that will be created by the tool
  • Network—The network dataset or network analysis service used to calculate the Service Areas
  • Travel Mode—The travel mode used for the analysis
  • Cutoffs—The Service Area's travel time or distance limit
  • Cutoff Units—The time or distance units in which to interpret the Cutoffs parameter value
  • Time Of Day—The date and time of day to use for the analysis
Shows the dialog for the script tool you will create in this tutorial populated with valid inputs.

Gather test data

The goal of this tutorial is to create a script tool that can be used with any input data. No specific data is required, but you must have the following to test the script tool:

  • A network dataset or access to a network analysis service
  • A point feature class with some test points in the same geographic area as the network

If you don't have your own data, you can download and use the provided tutorial data.

Note:
This tutorial can be completed using as the network data source either the designated tutorial network dataset, ArcGIS Online, or an ArcGIS Enterprise routing service published using a network dataset that covers the geography of the input data of the analysis. If you use ArcGIS Online, credits will be consumed. Learn more about network analysis with a service.
Tip:

The provided tutorial data includes a completed script tool created by following the steps of this tutorial. You can find it in the Tutorial\ScriptTool folder of the extracted tutorial data.

  1. Go to the data download page.
  2. Click the Download button, and save the file locally.
  3. Unzip the downloaded file.

Create the tool

First, you will create the tool in a new toolbox and define its input and output parameters.

Create the toolbox

Create a toolbox to store your script tool.

  1. Open ArcGIS Pro and create a new project with a map.
  2. In the Catalog pane, which is on the right side of the application by default, right-click Folders and choose Add Folder Connection Add Folder Connection.

    The Add Folder Connection dialog box appears.

  3. Connect to a folder of your choice.

    You will create the toolbox and store the script tool's Python .py file in this folder.

  4. Right-click the folder connection and click New > Toolbox (.atbx).

    A toolbox file with the .atbx extension is created. The toolbox's name is put into edit mode.

  5. Update the toolbox's name to TutorialScriptTool.atbx.
  6. Right-click the toolbox and click Properties.

    The Toolbox Properties dialog box appears.

  7. In the Label box, update the toolbox's label to Tutorial Script Tool.
  8. In the Alias box, update the toolbox's alias to TutorialScriptTool.

    The toolbox's alias is used when calling a tool from a Python process.

    The
  9. Click OK to close the Toolbox Properties dialog box.

Create the script tool in the toolbox

Next, create the script tool in the toolbox and update its basic properties.

  1. Right-click the toolbox you created in the previous section and click New > Script.

    The Tool Properties dialog box appears.

  2. In the list of side tabs, click the General tab if it is not already selected.
  3. In the Name text box, type ServiceAreaTutorialScriptTool.

    The name is used when calling the tool from a Python process. Use only alphanumeric characters for the name. It cannot include spaces or special characters.

  4. In the Label text box, type Service Area Tutorial Script Tool.

    The label is the display name for the script tool (as shown in the Geoprocessing pane) and can contain spaces.

    The script tool
  5. In the Description text box, type a description for the script tool if desired.

Define the tool parameters

The next step is to define the tool's parameters. Parameters appear on the tool's dialog box and allow the user to choose input data, output locations, and other options. When the tool is run, the parameter values are sent to the tool's Python script. The script retrieves the parameter values and uses them in the analysis.

You will create seven parameters, as described at the beginning of this tutorial.

Learn more about setting script tool parameters

  1. In the list of side tabs, click the Parameters tab.
  2. To create the Input Facilities parameter complete the following substeps:

    The Input Facilities parameter will allow the user to choose a feature class or layer of points around which the Service Area polygons will be calculated.

    1. Click the first empty cell under the Label column, type Input Facilities, and press Enter.

      The Input Facilities parameter is created. The cell in the Name column is automatically updated to Input_Facilities.

    2. Click the Options button Options in the Data Type cell to open the Parameter Data Type dialog box.
    3. On the Parameter Data Type dialog box, from the drop-down menu, select Feature Layer and click OK to close the dialog box.
      The Parameter Data Type dialog box showing the Feature Layer type selected.

      A parameter of type Feature Layer allows the user of the tool to either select a layer from the map or use a catalog path to a feature class as input for this parameter.

    4. Click the cell in the Filter column. Scroll to the right to find this column if necessary.
    5. Use the drop-down menu to select the Feature Type option.
      Select the Feature Type filter for the Input Facilities parameter.

      The Feature Type Filter dialog box appears.

    6. On the Feature Type Filter dialog box, check the Point option and click OK to close the dialog box.
      The Feature Type Filter dialog box with the Point option checked

      Service Area facilities must be points. After setting this feature type filter, the Input Facilities parameter will only allow point feature classes and layers as input. If the user selects a polygon or line feature class, for example, the tool parameter will automatically display an error.

  3. To create the Output Polygons parameter, complete the following substeps:

    The Output Polygons parameter will allow the user to choose the location where the tool will save the Service Area polygon feature class created during the tool's run.

    1. Click the first empty cell under the Label column, type Output Polygons, and press Enter.

      The Output Polygons parameter is created. The cell in the Name column is automatically updated to Output_Polygons.

    2. As you did when defining data type for the Input Facilities parameter, click the Data Type cell to open the Parameter Data Type dialog box. This time, set the data type to Feature Class.

      An output parameter of type Feature Class allows the user of the tool to choose the catalog path to a new feature class or shapefile.

    3. Click the cell in the Direction column and from the drop-down menu, choose the Output option.

      Because this parameter is configured as an output parameter, the user can choose an output file path and name for a feature class that does not yet exist.

  4. To create the Network parameter, complete the following substeps:

    The Network parameter will allow the user to choose the network dataset or network analysis service for calculating the Service Areas.

    1. Click the first empty cell under the Label column, type Network, and press Enter.

      The Network parameter is created. The cell in the Name column is automatically updated to Network.

    2. In the Data Type column, set the data type to Network Data Source.

      The Network Data Source parameter type allows the user to choose a network dataset layer, a network dataset catalog path, or the URL of a network analysis service. For a parameter of type Network Dataset Layer, the user can choose a network dataset layer or network dataset catalog path only, without the option to use a service URL. The Network Dataset parameter type allows only a network dataset catalog path and is used more commonly as an output parameter type than for inputs.

  5. To create the Travel Mode parameter, complete the following substeps:

    The Travel Mode parameter will allow the user to choose the travel mode used for the analysis. You will configure this parameter with a dependency so that the choice list updates automatically to reflect the travel modes available with the selected Network parameter value.

    1. Click the first empty cell under the Label column, type Travel Mode, and press Enter.

      The Travel Mode parameter is created. The cell in the Name column is automatically updated to Travel_Mode.

    2. In the Data Type column, set the data type to Network Travel Mode.
    3. Click the cell in the Dependency column. Scroll to the right to find this column if necessary.
    4. From the drop-down menu in the Dependency column cell, select the Network option.
      Select the Network parameter in the drop-down menu in the Dependency column of the Travel Mode parameter.

      The Travel Mode parameter is now linked to the Network parameter. The tool dialog box automatically updates the choice list in the Travel Mode parameter according to the value set in the Network parameter.

  6. To create the Cutoffs parameter, complete the following substeps:

    The Cutoffs parameter will allow the user to choose one or more numerical values designating the Service Area's travel time or distance limits. The Service Area polygons will show the area reachable from the input facilities within these limits. If multiple values are entered, one Service Area polygon will be created for each facility for each cutoff value. For example, the output could include polygons representing both a 10-minute and 15-minute drive time surrounding each facility.

    1. Click the first empty cell under the Label column, type Cutoffs, and press Enter.

      The Cutoffs parameter is created. The cell in the Name column is automatically updated to Cutoffs.

    2. Click the Options button Options in the Data Type cell to open the Parameter Data Type dialog box.
    3. On the Parameter Data Type dialog box, from the drop-down menu, select Double.
    4. On the Parameter Data Type dialog box, check the Multiple values option.
      The Parameter Data Type dialog box showing the Double type selected and the Multiple values option checked.

      The Multiple values option allows the user to enter more than one value for the parameter.

    5. Click OK to close the Parameter Data Type dialog box.

    Service Area cutoffs must be greater than zero. It doesn't make sense to create a polygon representing a zero-minute or negative drive time. It is possible to configure a numerical parameter with a range filter by selecting the Range option in the Filter column. An error message will automatically be shown if the user selects a number outside this range. However, range filters support only inclusive numerical ranges, and you want to exclude the lower bound of 0 while still allowing decimal values such as 0.5. Consequently, instead of using the provided range filter, you will use tool validation to construct your own range filter using Python code. This is described later in this tutorial.

  7. To create the Cutoff Units parameter, complete the following substeps:

    The Cutoff Units parameter will allow the user to specify the unit of measurement in which to interpret the Cutoffs parameter value.

    1. Click the first empty cell under the Label column, type Cutoff Units, and press Enter.

      The Cutoff Units parameter is created. The cell in the Name column is automatically updated to Cutoff_Units.

    2. In the Data Type column, confirm that the data type is set to String, and update it if necessary.

    To configure a choice list for a parameter, you can select the Value List option in the Filter column and enter the list of values you want to present to the user. However, for the Cutoff Units parameter, you want the choice list to be dynamic depending on whether the user's selected Travel Mode parameter value has units of time or distance. In the next section, you will use tool validation to construct a dynamic choice list with Python code.

  8. To create the Time Of Day parameter, complete the following substeps:

    The Time Of Day parameter will allow the user to set the date and time of day for the analysis. This is the date and time when vehicles or pedestrians leave the facilities. Since time of day is not always relevant, you will configure this parameter to be optional. The user can set it if desired, or they can leave it blank.

    1. Click the first empty cell under the Label column, type Time Of Day, and press Enter.

      The Time Of Day parameter is created. The cell in the Name column is automatically updated to Time_Of_Day.

    2. In the Data Type column, set the data type to Date.
    3. Click the cell in the Type column and from the drop-down menu, choose the Optional option.

      Because this parameter is configured as optional, the user can leave it blank without receiving an error.

  9. Check the parameter configuration. It should look similar to the following image:
    The full parameter configuration for the script tool
  10. Click OK on the Tool Properties dialog box to commit your changes and close the dialog box.

Verify the tool parameters

Now that you have configured the tool parameters, you should verify that they are working as desired. There is still some work to do to configure custom validation for some parameters, and you cannot run the tool yet because you have not yet written the tool's run code, but take a moment to verify your parameter configuration so far.

  1. In the Catalog pane, find the new Service Area Tutorial Script Tool script tool in the toolbox.
    The toolbox and script tool in the Catalog pane
  2. Double-click the tool to open it.

    The Service Area Tutorial Script Tool opens in the Geoprocessing pane. The seven parameters you created in the previous section are visible.

    The script tool dialog box in the Geoprocessing pane showing all the parameters.
  3. Examine each parameter and confirm that the behavior is as expected.
    1. Examine the Input Facilities parameter.

      You should only be able to select point layers and feature classes with this parameter. Polygon and line feature classes do not appear in the choice list, and if you manually enter one, you will see an error message indicating that the input has an invalid geometry type.

      Tip:
      You will not see a choice list if your map has no feature layers in it. Add some layers to your map if you want to see the parameter's behavior.

    2. Examine the Output Polygons parameter.

      You should be able to choose the output location. If you choose an existing feature class, the parameter should display a warning indicating that the output already exists.

    3. Examine the Network and Travel Mode parameters.

      The Travel Mode parameter should initially be empty and have no choice list. The Network parameter should allow you to select a network dataset layer, a network dataset catalog path, or a URL to a network analysis service.

    4. Choose a value for the Network parameter.

      The Travel Mode parameter choice list updates automatically to include the list of available travel modes associated with the Network value.

    5. Examine the Cutoffs parameter.

      You can enter more than one numerical value.

      Currently, you can enter negative numbers or 0, and the parameter does not display an error. Later, you will use tool validation to update this behavior.

    6. Examine the Cutoff Units parameter.

      Currently, the parameter does not display a choice list. Later, you will use tool validation to provide a list of choices depending on the Travel Mode value.

    7. Examine the Time Of Day parameter.

      Unlike the other parameters, this parameter does not display a red asterisk when empty because it is optional. You can use the date and time selector utility or enter a date and time manually.

Write the Python script

In this section, you will write the Python script that the script tool will run. The script tool will do the following:

  • Retrieve the input parameters.
  • Check out the ArcGIS Network Analyst extension license, if required.
  • Initialize the Service Area analysis.
  • Set the Service Area analysis settings.
  • Load the input facilities.
  • Solve the Service Area analysis and handle solve errors.
  • Write the output Service Area polygons to a feature class.

  1. Create a file called ServiceAreaTutorialScriptTool.py in the same folder where you created the toolbox earlier and open it for editing in the integrated development environment (IDE) or text editor of your choice.

    You can use any Python IDE or text editor for this exercise.

  2. Read the subsections below to understand the components of the code you will use for the script tool.
  3. The complete code is included at the bottom of this section. Copy and paste this code into the ServiceAreaTutorialScriptTool.py file and save the file.

    Caution:
    Verify that the code's indentation is correct after pasting it into the ServiceAreaTutorialScriptTool.py file.

Retrieve the input parameters

The script tool's code must retrieve the user's inputs from the tool's parameters using the GetParameter or GetParameterAsText ArcPy function. The GetParameter function returns the parameter value in the parameter's defined data type, and the GetParameterAsText function always returns the parameter's value as a string.

In this code snippet, the script retrieves the tool parameters and assigns them to variables. The GetParameter function is used when you want to retain the data type of the input parameter. The GetParameterAsText function is used when retrieving the input facilities and the network data source, even though those inputs may be layers because the layers' string names can be used as valid inputs to geoprocessing tools and ArcPy functions. The index values used here with GetParameter and GetParameterAsText match the order of the parameters you defined earlier.

input_facilities = arcpy.GetParameterAsText(0)
output_polygons = arcpy.GetParameterAsText(1)
network = arcpy.GetParameterAsText(2)
travel_mode = arcpy.GetParameter(3)
cutoffs = arcpy.GetParameter(4)
cutoff_units = arcpy.GetParameterAsText(5)
time_of_day = arcpy.GetParameter(6)

Check out the ArcGIS Network Analyst extension license

The ArcGIS Network Analyst extension license is required if the tool's user chooses a network dataset for the Network parameter. If the user instead chooses to use a network analysis service by designating a portal URL, the ArcGIS Network Analyst extension license is not required.

In this code snippet, the script attempts to check out the extension license if the input network does not start with http, which is presumably a network analysis service. If the extension is unavailable, an error is raised. Error handling will be discussed later in this tutorial.

# Check out the Network Analyst extension license if the input network
# is a local network dataset and not a service.
if not network.startswith("http"):
    if arcpy.CheckExtension("network") == "Available":
        arcpy.CheckOutExtension("network")
    else:
        # Throw an error if the license cannot be checked out.
        arcpy.AddError("The Network Analyst license is unavailable.")
        raise CustomError

Initialize the Service Area analysis

The first step in a network analysis workflow using arcpy.nax is to instantiate the solver's analysis object using the designated network data source.

Learn more about the steps in a network analysis workflow with arcpy.nax

In this code snippet, the Service Area analysis object is initialized.

# Instantiate the ServiceArea solver object
sa = arcpy.nax.ServiceArea(network)

Set the Service Area analysis settings

For this analysis, you will hard-code some Service Area analysis settings that you don't want the user to change. You will set other settings based on the user's choices from the tool parameters.

Learn more about the Service Area analysis settings

In this code snippet, some analysis settings are hard-coded. The travel mode, time of day, and default impedance cutoffs are set using the values retrieved from the tool parameters.

# Hard-code some non-default Service Area settings that we don't want
# the user to change
sa.geometryAtCutoff = arcpy.nax.ServiceAreaPolygonCutoffGeometry.Disks
sa.polygonBufferDistance = 150
sa.polygonBufferDistanceUnits = arcpy.nax.DistanceUnits.Feet

# Set analysis properties chosen by the user and passed in via tool
# parameters
sa.travelMode = travel_mode
sa.timeOfDay = time_of_day
sa.defaultImpedanceCutoffs = cutoffs

You also need to set the cutoff units, but this requires a bit of extra processing. The tool parameter will be configured as a string, and you need to convert the string-based unit name to the appropriate TimeUnits or DistanceUnits enumeration value.

In this code snippet, the time units or distance units are set depending on the value passed in from the tool parameter with the help of two functions to convert from a string value to the appropriate enumeration value. In this example tool, you will support only a limited list of possible time and distance units. The tool parameter will be discussed more later in this tutorial.

# Do special handling of cutoff units to convert them to the correct
# arcpy.nax enum
if cutoff_units in ["Hours", "Minutes"]:
    sa.timeUnits = convert_time_units_to_nax(cutoff_units)
elif cutoff_units in ["Kilometers", "Meters", "Miles", "Yards", "Feet"]:
    sa.distanceUnits = convert_distance_units_to_nax(cutoff_units)

In this code snippet, two functions are defined to convert string-based unit names to the correct enumeration value. Not all possible values of the TimeUnits and DistanceUnits enumerations are included because, in this example, you will limit the choices of units presented to the users. The functions raise an error if an invalid unit is passed.

def convert_time_units_to_nax(time_units_str):
    """Convert string-based time units to the correct arcpy.nax enum."""
    if time_units_str == "Hours":
        return arcpy.nax.TimeUnits.Hours
    if time_units_str == "Minutes":
        return arcpy.nax.TimeUnits.Minutes
    arcpy.AddError(f"Invalid time units: {time_units_str}")
    raise CustomError


def convert_distance_units_to_nax(dist_units_str):
    """Convert string-based distance units to the correct arcpy.nax enum."""
    if dist_units_str == "Kilometers":
        return arcpy.nax.DistanceUnits.Kilometers
    if dist_units_str == "Meters":
        return arcpy.nax.DistanceUnits.Meters
    if dist_units_str == "Miles":
        return arcpy.nax.DistanceUnits.Miles
    if dist_units_str == "Yards":
        return arcpy.nax.DistanceUnits.Yards
    if dist_units_str == "Feet":
        return arcpy.nax.DistanceUnits.Feet
    arcpy.AddError(f"Invalid distance units: {dist_units_str}")
    raise CustomError

Load the input facilities

Next, use the load method to add the user's facilities into the Service Area analysis.

Learn about more ways to set analysis inputs

In this code snippet, the input facilities are added to the Service Area analysis using the load method. The ServiceAreaInputDataType.Facilities enumeration value is used as a parameter of the load method to indicate that the inputs should be added as facilities.

# Load the input facilities
sa.load(arcpy.nax.ServiceAreaInputDataType.Facilities, input_facilities)

Solve the Service Area analysis and handle solve errors

Now that the Service Area settings have been configured and the input data has been loaded, you are ready to solve the analysis and retrieve the result. Solving the analysis using the solve method produces an instance of a ServiceAreaResult object, which includes properties and methods for working with the outputs of the analysis.

In this code snippet, the Service Area analysis is solved using the solve method. The result variable can be used to work with the analysis outputs.

# Solve the analysis
result = sa.solve()

Sometimes, an analysis may fail, or it may succeed but return warning messages to the user about possible problems. In your tool, you want to return solver errors and warnings to the user. If the solve fails, you want to stop the tool's run.

In this code snippet, the ServiceAreaResult object's solverMessages method is used to retrieve warning messages by using the MessageSeverity.Warning enumeration value. Each warning message received is added to the tool's warning messages using the AddWarning function.

# Print warning messages if there are any
for warning in result.solverMessages(arcpy.nax.MessageSeverity.Warning):
    arcpy.AddWarning(warning[1])

In this code snippet, you check whether the Service Area analysis succeeded using the ServiceAreaResult object's solveSucceeded property. If it did not, the solverMessages method is used to retrieve the error messages, and the AddError function adds them to the tool's errors. Additionally, an error is raised to stop the tool's run. Error handling is discussed more later.

# Handle failed solves
if not result.solveSucceeded:
    arcpy.AddError("The Service Area solve failed.")
    # Print error messages and stop the tool from running further
    for error in result.solverMessages(arcpy.nax.MessageSeverity.Error):
        arcpy.AddError(error[1])
    # Stop tool run by raising an error
    raise CustomError

Write the output Service Area polygons to a feature class

Finally, you write the output of the Service Area analysis to the user's designated feature class using the export method. You also count the number of polygons in the output using the count method and write this information as a message.

Learn about other ways to access and work with the outputs of an analysis

In this code snippet, the number of polygons in the output is written as a message using the AddMessage function, and the Service Area polygons are exported to a feature class. The ServiceAreaOutputDataType.Polygons enumeration value is used to work with the output polygons instead of one of the other analysis output types.

# Add a message with the total number of polygons that were generated
# in the analysis
num_polygons = result.count(
    arcpy.nax.ServiceAreaOutputDataType.Polygons)
arcpy.AddMessage(f"Number of polygons generated: {num_polygons}.")

# Export the Service Area polygons to the output feature class
result.export(
    arcpy.nax.ServiceAreaOutputDataType.Polygons, output_polygons)

Handle errors

When a known error is encountered, you want the tool to stop running and produce a helpful error message. You do this by raising a custom exception and wrapping the tool's run code in a try/except block.

In this code snippet, a custom exception is defined. It doesn't do anything, but it serves to stop the tool's run when a known problem is encountered.

class CustomError(Exception):
    pass

The tool's run code is wrapped in a try/except block. If a CustomError is caught, the code does nothing because the error message has already been added to the tool's errors. If an unknown error occurs, the code retrieves the traceback and adds it as a tool error to assist with debugging.

try:

    [...]

except CustomError:
    # We caught a known error and already added the message. Do nothing.
    pass

except Exception:
    # An unknown error occurred. Add the traceback as an error message.
    arcpy.AddError(
        "An unknown error occurred when generating Service Areas.")
    import traceback
    arcpy.AddError(traceback.format_exc())

Put it all together

The following code snippet includes the complete Python script containing all the components discussed above. You can copy and paste this code into the ServiceAreaTutorialScriptTool.py file.

import arcpy


class CustomError(Exception):
    pass


def convert_time_units_to_nax(time_units_str):
    """Convert string-based time units to the correct arcpy.nax enum."""
    if time_units_str == "Hours":
        return arcpy.nax.TimeUnits.Hours
    if time_units_str == "Minutes":
        return arcpy.nax.TimeUnits.Minutes
    arcpy.AddError(f"Invalid time units: {time_units_str}")
    raise CustomError


def convert_distance_units_to_nax(dist_units_str):
    """Convert string-based distance units to the correct arcpy.nax enum."""
    if dist_units_str == "Kilometers":
        return arcpy.nax.DistanceUnits.Kilometers
    if dist_units_str == "Meters":
        return arcpy.nax.DistanceUnits.Meters
    if dist_units_str == "Miles":
        return arcpy.nax.DistanceUnits.Miles
    if dist_units_str == "Yards":
        return arcpy.nax.DistanceUnits.Yards
    if dist_units_str == "Feet":
        return arcpy.nax.DistanceUnits.Feet
    arcpy.AddError(f"Invalid distance units: {dist_units_str}")
    raise CustomError


def generate_service_areas():
    """Generate Service Area polygons."""
    try:
        input_facilities = arcpy.GetParameterAsText(0)
        output_polygons = arcpy.GetParameterAsText(1)
        network = arcpy.GetParameterAsText(2)
        travel_mode = arcpy.GetParameter(3)
        cutoffs = arcpy.GetParameter(4)
        cutoff_units = arcpy.GetParameterAsText(5)
        time_of_day = arcpy.GetParameter(6)

        # Check out the Network Analyst extension license if the input network
        # is a local network dataset and not a service.
        if not network.startswith("http"):
            if arcpy.CheckExtension("network") == "Available":
                arcpy.CheckOutExtension("network")
            else:
                # Throw an error if the license cannot be checked out.
                arcpy.AddError("The Network Analyst license is unavailable.")
                raise CustomError

        # Instantiate the ServiceArea solver object
        sa = arcpy.nax.ServiceArea(network)

        # Hard-code some non-default Service Area settings that we don't want
        # the user to change
        sa.geometryAtCutoff = arcpy.nax.ServiceAreaPolygonCutoffGeometry.Disks
        sa.polygonBufferDistance = 150
        sa.polygonBufferDistanceUnits = arcpy.nax.DistanceUnits.Feet

        # Set analysis properties chosen by the user and passed in via tool
        # parameters
        sa.travelMode = travel_mode
        sa.timeOfDay = time_of_day
        sa.defaultImpedanceCutoffs = cutoffs
        # Do special handling of cutoff units to convert them to the correct
        # arcpy.nax enum
        if cutoff_units in ["Hours", "Minutes"]:
            sa.timeUnits = convert_time_units_to_nax(cutoff_units)
        elif cutoff_units in ["Kilometers", "Meters", "Miles", "Yards", "Feet"]:
            sa.distanceUnits = convert_distance_units_to_nax(cutoff_units)

        # Load the input facilities
        sa.load(arcpy.nax.ServiceAreaInputDataType.Facilities, input_facilities)

        # Solve the analysis
        result = sa.solve()

        # Print warning messages if there are any
        for warning in result.solverMessages(arcpy.nax.MessageSeverity.Warning):
            arcpy.AddWarning(warning[1])

        # Handle failed solves
        if not result.solveSucceeded:
            arcpy.AddError("The Service Area solve failed.")
            # Print error messages and stop the tool from running further
            for error in result.solverMessages(arcpy.nax.MessageSeverity.Error):
                arcpy.AddError(error[1])
            # Stop tool run by raising an error
            raise CustomError

        # Add a message with the total number of polygons that were generated
        # in the analysis
        num_polygons = result.count(
            arcpy.nax.ServiceAreaOutputDataType.Polygons)
        arcpy.AddMessage(f"Number of polygons generated: {num_polygons}.")

        # Export the Service Area polygons to the output feature class
        result.export(
            arcpy.nax.ServiceAreaOutputDataType.Polygons, output_polygons)

    except CustomError:
        # We caught a known error and already added the message. Do nothing.
        pass

    except Exception:
        # An unknown error occurred. Add the traceback as an error message.
        arcpy.AddError(
            "An unknown error occurred when generating Service Areas.")
        import traceback
        arcpy.AddError(traceback.format_exc())


if __name__ == "__main__":
    generate_service_areas()

Configure the script tool to run the Python script file

Earlier, you created the script tool and defined its parameters. Then, you wrote a Python script to do the analysis. Now, you must configure the tool to run the Python script. When you run the tool, the tool will run the code in the Python script, and the Python script will retrieve the inputs passed to it from the tool's parameters and complete the analysis.

  1. Open the script tool's properties dialog box by right-clicking the script tool and clicking Properties.

    The Tool Properties dialog box appears.

  2. In the list of side tabs, click the Execution tab.
  3. Click the Folder button Folder and browse to the location of your ServiceAreaTutorialScriptTool.py script file.

    Tip:
    If the ServiceAreaTutorialScriptTool.py file does not appear on the browse dialog box, click the Refresh button next to the address bar at the top.The button to refresh the browse dialog box

    The code shown on the Execution tab updates to show the contents of the Python script file. The tool is now configured to run the Python script.

Customize tool validation

Validation refers to the process that checks and updates the values in a tool's parameters before the tool is run. Validation can ensure that values entered for parameters are of the correct type or range of values. Validation can also update parameter choice lists or even hide or expose parameters depending on the value of other parameters.

Some validation processes are built into certain parameter types and configurations. For example, earlier, you configured the Input Facilities parameter with a Feature Type filter to accept only point feature classes. When the user selects an input for this parameter, validation verifies that the input is of the correct type and displays an error message if it is not.

Sometimes, however, the author of a script tool needs to implement more sophisticated or customized validation. This can be done using the ToolValidator class. This special Python class can be updated with custom code to update parameters and perform checks that raise error and warning messages when necessary.

In this tutorial, you will program the ToolValidator class in your script tool to raise an error if a value in the Cutoffs parameter is less than or equal to 0 and to update the Cutoff Units parameter choice list with a list of time units or distance units depending on the value in the Travel Mode parameter.

The ToolValidator code is separate from the Python script you wrote earlier that the tool runs and is accessed and edited separately.

Note:

You can also create a custom geoprocessing tool in a Python toolbox (.pyt). In this case, both the validation code and the tool run code are included in the same Python script.

Learn more about what you can do with script tool validation

Find the ToolValidator class and open it for editing

When you create a script tool, the ToolValidator class is automatically created. You can access it through the script tool's properties dialog box and open it for editing.

  1. If necessary, open the script tool's properties dialog box by right-clicking the script tool and clicking Properties.

    The Tool Properties dialog box appears.

  2. In the list of side tabs, click the Validation tab.

    The script tool's ToolValidator class is visible. You can edit the code directly on the dialog box or click the Open in Script Editor button to open the ToolValidator in a separate file in an IDE or text editor.

    Tip:

    You control which IDE or text editor opens the file with the Script Editor setting in the Geoprocessing options.

Raise an error if the Cutoffs parameter value is less than or equal to 0

The updateMessages method of the ToolValidator class can be used to check parameter values and adds error or warning messages. In this section, you will modify the updateMessages method to raise an error if a value in the Cutoffs parameter is less than or equal to 0.

  1. Find the updateMessages method in the ToolValidator class.

    By default, the updateMessages method contains only a return statement. It does not do custom validation.

  2. Change the updateMessages method to use the following code:

    Caution:
    Verify the validation code's indentation after pasting it into the ToolValidator class.

    def updateMessages(self):
        # Customize messages for the parameters.
        # This gets called after standard validation.
    
        # Raise an error if any of the cutoffs are <= 0
        cutoffs_param = self.params[4]
        if cutoffs_param.valueAsText:
            for cutoff in cutoffs_param.values:
                if cutoff <= 0:
                    cutoffs_param.setErrorMessage("Cutoffs must be positive.")
                    break
    
        return

    In this code snippet, an error message is added to the Cutoffs parameter if any of its values are less than or equal to 0.

    The Cutoffs parameter is retrieved using the self.params variable, which is a list of tool parameters built into the ToolValidator class. The Cutoffs parameter is the fifth parameter, so it is accessed using index 4 of the list. The retrieved value is a Parameter object.

    If the parameter is empty, the values does not need to be checked. The Parameter object's valueAsText property is a quick way to determine whether the parameter is empty.

    Because this is a multivalue parameter, the list of values is retrieved using the values property. The code iterates over the parameter's current values.

    If the cutoff value is less than or equal to 0, the Parameter object's setErrorMessage is used to set an error message. This message will appear on the parameter in the tool's user interface.

    If an invalid value is encountered, the break statement halts the iteration. It is not necessary to check the remaining values once an invalid one has been found.

    Note:
    Even if you add custom validation code, the basic internal validation associated with each parameter still applies. For example, because you defined the Cutoffs parameter to be of type double, the internal validation will automatically check that the input is a valid numerical value and raise an error if necessary. Any custom validation you add for your parameter is in addition to the internal validation automatically provided for that parameter type.

Populate the Cutoff Units parameter with a list of either time or distance units

A travel mode includes several settings controlling how travel through a network is modeled. It determines which variable is optimized when finding the shortest path through the network. Typically, this is a measure of either time or distance, although network datasets can also be configured to optimize some other type of variable, such as the energy or monetary cost of travel.

The Cutoffs parameter allows the user to specify numerical values for the Service Area's travel cost limit. The Cutoff Units parameter allows the user to specify the unit of measurement in which to interpret those values.

For your tool, you want to give the user a list of valid units; however, if the selected travel mode optimizes time, you want to show only time units, and if the selected travel mode optimizes distance, you want to show only distance units. In the rare case that the travel mode optimizes some other type of cost, you do not want to show either time or distance units.

The updateParameters method of the ToolValidator class can be used to update parameter choice lists. In this section, you will modify the updateParameters method to set the Cutoff Units choice list with an appropriate list of units depending on the value of the Travel Mode parameter.

  1. Find the updateParameters method in the ToolValidator class.

    By default, the updateParameters method contains only a return statement. It does not update parameters.

  2. Change the updateParameters method to use the following code:

    Caution:
    Verify the validation code's indentation after pasting it into the ToolValidator class.

    def updateParameters(self):
        # Modify parameter values and properties.
        # This gets called each time a parameter is modified, before
        # standard validation.
    
        # Set filter list of units in cutoff units parameter based on what type
        # of travel mode is selected
        travel_mode_param = self.params[3]
        cutoff_units_param = self.params[5]
        if travel_mode_param.valueAsText:
            try:
                tm_object = travel_mode_param.value
                if tm_object.impedance == tm_object.timeAttributeName:
                    # The impedance has units of time, so present time units as
                    # options
                    cutoff_units_param.filter.list = ["Hours", "Minutes"]
                elif tm_object.impedance == tm_object.distanceAttributeName:
                    # The impedance has units of distance, so present distance
                    # units as options
                    cutoff_units_param.filter.list = [
                        "Kilometers", "Meters", "Miles", "Yards", "Feet"]
                else:
                    # The impedance has units that are neither time nor
                    # distance, so present only one option, "Other". The
                    # Service Area cutoffs will be interpreted in the impedance
                    # units.
                    cutoff_units_param.filter.list = ["Other"]
            except Exception:
                pass
    
        return

    In this code snippet, the list of possible values in the Cutoff Units parameter is updated based on the current value of the Travel Mode parameter.

    The parameters are retrieved using the self.params variable. The Travel Mode parameter is at index 3 (the fourth parameter), and the Cutoff Units parameter is at index 5 (the sixth parameter).

    If the Travel Mode parameter is empty, the Cutoff Units parameter need not be updated. The Parameter object's valueAsText property is a quick way to determine whether the parameter is empty.

    The Travel Mode parameter value is retrieved as a TravelMode object using the Parameter object's value property.

    The easiest way to determine whether the travel mode optimizes time, distance, or some other unit of measurement is to compare the TravelMode object's impedance property with the timeAttributeName property and the distanceAttributeName property. The impedance property returns the name of the network attribute being optimized. Travel modes also include a default time attribute (timeAttributeName) and distance attribute (distanceAttributeName). If the impedance matches the timeAttributeName, the travel mode optimizes time, and the Cutoff Units parameter is updated to display a list of time units. If the impedance matches the distanceAttributeName, the travel mode optimizes distance, and the Cutoff Units parameter is updated to display a list of distance units. If the impedance matches neither, the travel mode optimizes some other variable, and the Service Area's cutoffs will be interpreted in those units. The Cutoff Units parameter is updated to show only one choice: Other.

    In all cases, the choice list is specified by setting the Cutoff Units Parameter object's filter.list property.

    The code block is wrapped in a try/except block. If errors are encountered in reading the travel mode, the code does nothing and does not update the choice list.

Verify the tool validation

In this section, you will save and test the validation code.

  1. If you opened the ToolValidator class in an IDE or text editor, save and close the file.
  2. Click OK on the Tool Properties dialog box to commit the changes.
  3. In the Catalog pane, double-click the tool to open it.

    The Service Area Tutorial Script Tool opens in the Geoprocessing pane.

  4. Enter a number less than or equal to 0 in the Cutoffs parameter.

    An error symbol appears on the Cutoffs parameter. If you hover over it or click it, your error message appears in the ToolTip.

  5. Verify that the Cutoff Units choice list updates to show the correct list of units when you select a value in the Travel Mode parameter.
    1. Choose a value for the Network parameter.

      Tip:
      If you're using the provided tutorial data, use the network dataset, SanFrancisco.gdb/Transportation/Streets_ND, for the Network parameter. You can use the catalog path to the network dataset, or you can add it to the map first and use its layer representation as the input for the tool.

      When the Network parameter is empty, the Travel Mode parameter does not include a choice list. When a value is entered for the Network parameter, the Travel Mode parameter choice list updates automatically to include the list of available travel modes associated with the Network value.

    2. Choose a value for the Travel Mode parameter.

      When the Travel Mode parameter is empty, the Cutoff Units parameter does not include a choice list. When a value is entered for the Travel Mode parameter, the Cutoff Units parameter choice list updates automatically to show a list of time units or distance units (or the single option for Other).

Run the tool

You have created and configured the script tool, written the tool's code, and customized the tool's validation. It is now ready to run and can be used like any other geoprocessing tool. In addition to running the tool from the Geoprocessing pane, you can add it to a model, call it from a Python script, and share it as a web tool.

  1. If necessary, open the tool by double-clicking it in the Catalog pane.
  2. Choose valid options for each of the tool's parameters.

    Make sure the points you use for the Input Facilities parameter are in the same geographic area as the network you use for the Network parameter.

    Tip:
    If you're using the provided tutorial data, use the datasets in the SanFrancisco.gdb geodatabase to test the tool. The point feature class of fire stations, SanFrancisco.gdb/Analysis/FireStations, can be used for the Input Facilities parameter, and the network dataset, SanFrancisco.gdb/Transportation/Streets_ND, can be used for the Network parameter. You can use the catalog paths to the data, or you can add the data to the map first and use the layers as inputs for the tool. A good cutoff for modeling fire station response time is two to four minutes.

  3. Click the Run button at the bottom of the tool.

    The tool runs successfully and adds a polygon layer to the map.

    Note:
    The tool may produce warning messages.