Custom directions

Custom directions allow you to alter the turn-by-turn directions text after it is initially generated during a solve operation. The existing directions for a route can be updated or removed, or new direction points can be added. This allows you to change the output directions without updating the underlying network dataset.

Example applications of custom directions include the following:

  • Add a directions event when entering or exiting a toll road or when transferring from a surface street to a subway line.
  • Alter directions maneuver text to emphasize sharp turn angles.
  • Alert the driver after every 120 minutes of driving.
  • Simplify or expand directions instructions for a specific application.

To implement custom directions, create a Python class that inherits from the arcpy.nax.DirectionsCustomizer class, and associate the Python class with a particular network dataset.

When implementing a directions customizer, several objects can be used to gather information about the network and the directions, such as DirectionPoint, DirectionsName, ReferenceLandmark, and others.

Custom directions class

To create a directions customizer, define a class that inherits from the arcpy.nax.DirectionsCustomizer class and implements the customize method. You can also use the class to implement the __init__ and attach methods.

Custom directions methods

The subsections below describe the methods that can be used to customize directions at solve time.

Initializer

Implementing the initializer (__init__) method is optional. This is the standard initializer for a Python object and can be used as needed.

Attach

Implementing the attach method is optional. This method can be used to inspect and validate the network dataset to ensure that it complies with the requirements of the custom directions code using the network query object that is passed into this method. If the network dataset is valid, the attach method returns True; otherwise, it returns False. When False is returned, the directions customizer will not be associated with the network dataset and no other class methods will be invoked.

For temporary directions customizers, the attach method is invoked when the directionsCustomizer property on a network dataset object in a script is assigned.

For persisted custom directions, the attach method is invoked internally by core network dataset code when a network dataset is opened.

Note:

A network dataset can be opened multiple times depending on the number of threads the application is using to access a network dataset.

Customize

Implementing the customize method is required. This method will be invoked after the initial directions are created and where directions can be altered.

The arcpy.nax.DirectionsQuery object that is passed into this method can be used to iterate through the junctions and the edges to get their associated directions points.

The existing direction points can be updated, such as altering the display text. New direction points can be created and added to an element's list of direction points. Alternatively, existing direction points can be removed.

Note:

  • When creating a direction point, at minimum, set the displayText and directionPointType properties.
  • The azimuth, arrivalTime, and exitNumber properties are set internally and cannot be set in a directions customizer.

The elements have various properties that may be helpful when customizing a route's directions. The self.networkQuery object can be used to retrieve information from the network dataset.

A route is composed of stops, edges, and junctions. These feature classes are used to create a route's directions. The directions are composed of direction points and direction line feature classes. Directions are created by analyzing the stops, edges, and junctions to find locations where something occurs that a user should be informed about. Direction points are then created at these locations and associated with the appropriate element. Usually, direction points are associated with junctions. Currently, direction points are only associated with edges when a landmark is encountered. The direction lines are then created by appending together the edges between direction points.

Note:

More than one direction point can be associated with the same feature.

Examples

Example 1:The code below is a simple example of a custom directions class that adds a number to the beginning of each directions maneuver.

import arcpy

class DirectionsIndexer(arcpy.nax.DirectionsCustomizer):
    """Defines a directions customizer that adds and index number to directions."""

    def customize(self, directions_query: arcpy.nax.DirectionsQuery):
        """Add an index number to each directions maneuver."""
        index = 1
        for junction in directions_query.junctions:
            for point in junction.directionPoints:
                point.displayText = f"{index} -> {point.displayText}"
                index += 1

Example 2: The code below shows a custom directions class with the optional methods implemented.

import arcpy

class DirectionsIndexer(arcpy.nax.DirectionsCustomizer):
    """Defines a directions customizer that adds and index number to directions."""

    def __init__(self):
        """Example initializer."""
        super().__init__()
        # Do additional custom initialization

    def attach(self, network_query: arcpy.nax.NetworkQuery) -> bool:
        # Do additional validation checks before returning Boolean
        return True

    def customize(self, directions_query: arcpy.nax.DirectionsQuery):
        """Add an index number to each directions maneuver."""
        index = 1
        for junction in directions_query.junctions:
            for point in junction.directionPoints:
                point.displayText = f"{index} -> {point.displayText}"
                index += 1

Example 3: The code below shows a custom directions class that adds new direction points with a custom message to alert the driver when they've been driving for more than two hours.

import arcpy

class LongDriveAlert(arcpy.nax.DirectionsCustomizer):
    """Defines a directions customizer that reports an alert after a long drive."""

    def customize(self, directions_query: arcpy.nax.DirectionsQuery) -> None:
        """Customize directions.

        If a traversed junction's accumulated time is over the alert interval, add an alert as a directions point.
        Enhance the existing directions text to show the time elapsed since the beginning of the trip.
        """
        alert_interval = 120  # This assumes that the travel mode's time attribute unit is minutes.
        # Loop through the traversed junctions and get the accumulated time at each junction.
        # If the accumulated time is over the alert interval, add a directions point with an alert message.
        i = 1
        for junction in directions_query.junctions:
            elapsed_time = junction.accumulatedTime
            if elapsed_time > alert_interval * i:
                # Create a new directions point at the junction.
                point = arcpy.nax.DirectionPoint(junction)
                point.displayText = f"You have driven for over 2 hours. Consider taking a break."
                point.directionPointType = arcpy.nax.DirectionPointType.Event
                # Update the junction direction points to add the new break reminder.
                junction.directionPoints.append(point)
                i += 1
            else:
                for point in junction.directionPoints:
                    # For existing directions, report the elapsed time at each direction point.
                    point.displayText = f"{point.displayText} (Time elapsed: {elapsed_time:.2f} minutes)"

Example 4: The code below shows a custom directions class that inspects the direction's point type to identify left turns and adds additional warning text to the directions instructions.

import arcpy

class LeftTurnsHighlighter(arcpy.nax.DirectionsCustomizer):
    """Add warning text for left turn maneuvers."""

    def __init__(self) -> None:
        """Initialize customizer.

        Set left_turn_maneuver_types in initialization so it is set only once
        when the network dataset object is constructed and when the
        customizer instance is initialized.
        """
        super().__init__()
        self.left_turn_manuever_types = [
            arcpy.nax.DirectionPointType.ManeuverForkLeft,
            arcpy.nax.DirectionPointType.ManeuverRampLeft,
            arcpy.nax.DirectionPointType.ManeuverUTurnLeft,
            arcpy.nax.DirectionPointType.ManeuverBearLeft,
            arcpy.nax.DirectionPointType.ManeuverTurnLeft,
            arcpy.nax.DirectionPointType.ManeuverSharpLeft,
            arcpy.nax.DirectionPointType.ManeuverTurnLeftLeft,
            arcpy.nax.DirectionPointType.ManeuverTurnLeftRight,
            arcpy.nax.DirectionPointType.ManeuverTurnRightLeft
        ]

    def customize(self, directions_query: arcpy.nax.DirectionsQuery) -> None:
        """Alter directions text to highlight left turns with warning text."""
        for junction in directions_query.junctions:
            for point in junction.directionPoints:
                if point.directionPointType in self.left_turn_manuever_types:
                    point.displayText = f"{point.displayText} (LEFT TURN WARNING!)"

Associate a directions customizer with a network dataset

There are two ways to deploy a directions customizer so that it is associated with a network dataset and has the customization logic invoked during a solve operation: temporary and persisted.

Tip:

Create the custom directions class and test it as a temporary directions customizer. Then run the script in debug mode in an editor such as Visual Studio Code. Once the script is working as expected, and if required, you can make it a persisted directions customizer. Validate the persisted directions customizer to ensure that everything is working correctly.

Temporary directions customizer

Temporary directions customizers are only associated with a network dataset object created in a script; they are not saved permanently to the network dataset. Temporary directions customizers are configured using the directionsCustomizer property on a network dataset object in a script that performs a solve operation.

A temporary directions customizer can be used for applications in which the directions customizer must be invoked from a Python script, for example, a stand-alone Python script, a Python script tool, or a custom geoprocessing service or a web tool. They can also be useful for developing and debugging the persisted directions customizers.

Configure a temporary directions customizer

To set up a temporary directions customizer, create a directions customizer object, and use the directionsCustomizer property on a network dataset object to associate the directions customizer object with it.

The example below shows how to instantiate a custom directions object and associate it with the network dataset object. To invoke the custom directions at solve time, instantiate a route solver object using the network dataset object.

# Instantiate a directions customizer object that adds an index number
# to each directions maneuver's display text
add_numbers_customizer = DirectionsIndexer()

# Create a network dataset object
network_dataset = arcpy.nax.NetworkDataset(
    r"C:\Data\Tutorial\SanFrancisco.gdb\Transportation\Streets_ND")

# Attach the custom directions object to the network dataset
network_dataset.directionsCustomizer = add_numbers_customizer

# Instantiate a route analysis
route = arcpy.nax.Route(network_dataset)

Persisted directions customizer

Persisted directions customizers store a reference to a directions customizer class as part of the network dataset schema, which is stored in the geodatabase. These directions customizers will be invoked whenever a solve operation is performed using that network dataset. This is referred to as persisted since the reference is part of the network dataset. They are configured using the updateNetworkDatasetSchema method on a network dataset object.

When a network with a persisted directions customizer is opened, the directions customizer class is loaded and cached. This cache is retained for the lifetime of the application. This means that any changes made to a persisted class will not be read until the application that opened the network dataset is closed and restarted. This applies to both ArcGIS Pro and ArcGIS Server.

It is important to note that for persisted directions customizers, the network dataset’s schema only contains a reference to a directions customizer, it does not contain the class code. This reference allows the network dataset, when it is accessed, to implicitly find and load its referenced directions customizer. A Python module containing the class code must reside in the active ArcGIS Pro Python environment's site-packages folder so that the network dataset can find it.

Learn more about Python environments

Note:

If the customization script needs to use third-party Python packages that are not included with the default ArcGIS Pro Python environment, it is recommended that you clone the default Python environment before installing additional packages. Follow the workflow in the Package Manager topic to create a clone of the default ArcGIS Pro Python environment, add the packages, and activate the environment. The custom evaluator Python file must be stored in the site-packages directory of the active Python environment, in this case, a clone of the default ArcGIS Pro Python environment.

This also applies to ArcGIS Server. If you need to deploy persisted customization to the ArcGIS Server site and need to use additional third-party packages that are not included with the ArcGIS Server default Python environment, follow the steps in the Deploy custom Python packages for ArcGIS Server topic to clone the default Python environment and add packages, then activate the cloned environment. When you deploy persisted customization to ArcGIS Server, the customization must be copied to the site-packages directory of the active Python environment on ArcGIS Server.

Use a persisted directions customizer when you need to have a directions customizer invoked when performing a solve operation outside of a Python script, such as in ArcGIS Pro or ArcGIS Server. For example, to create a network analysis layer in ArcGIS Pro, solve the layer and generate directions using the customization. Another example is to use a persisted directions customizer when you want to use standard routing services published based on a network dataset but also want to customize the directions output.

If a network analyst layer using a network dataset that has a persisted directions customizer is published as a service, the custom directions package must be manually copied to the ArcGIS Server Python environment's site packages directory. Also, when a directions customizer is used on a service, any external resource it uses (such as files) should be accessible by the ArcGIS Server user, as it is dependent on how the server is configured.

Configure a persisted directions customizer

Use the updateNetworkDatasetSchema method on a network dataset object to permanently update the network dataset schema, passing in a dictionary that defines the path to the directions customizer class. The path uses the dot notation to define the folder name (in the site-packages directory), the file name the class is in, and the class name.

The example below shows how to update a network dataset with a persisted custom directions class. The class in this example is called DirectionsIndexer, and its code is in a Python module called direction_customization.py in a folder called na_customizers, which is in the site-packages folder of an ArcGIS Pro active Python environment.

import arcpy

# Create a network dataset object
network_dataset = arcpy.nax.NetworkDataset(
    r"C:\Data\Tutorial\SanFrancisco_Persisted.gdb\Transportation\Streets_ND")

# Create a dictionary referencing the custom directions class to use
my_custom_directions = {"class": "na_customizers.direction_customization.DirectionsIndexer"}

# Update the network dataset to use the custom directions class
network_dataset.updateNetworkDatasetSchema(
    custom_directions=my_custom_directions
)

When this network dataset is used in a network analysis, an object of the directions customizer class will be created, and the network dataset will invoke the directions customizer object after generating the initial directions to customize the directions. When creating the object of the directions customizer, the network will find and load the specified class in the package and module from the active ArcGIS Pro environment's site packages folder. If no package, module, or class is found, the directions customizer will not be used. The solve and direction generation will complete with a warning message stating that there was an issue using the directions customizer

When a network dataset has a persisted directions customizer, it will be listed in the Summary section of the General pane and on the Directions page of the Network Dataset Properties dialog box. If there was an issue loading the referenced class, an error or warning message appears.

Object life cycle

When a network dataset is initially constructed, and it has a persisted directions customizer, it instantiates a directions customizer object that is referenced throughout its lifetime. The lifetime can vary depending on the framework being used (such as ArcGIS Pro, ArcGIS Server, or Python).

Since a specific instance of a directions customizer object can be used over multiple solve operations, it is important to manage the state of this object, in particular, resetting variables at the beginning of the Customize method before iterating through the current directions as needed. For example, if the directions customizer has an instance variable that is updated while iterating over the directions, reset it at the beginning of the Customize method; otherwise, it may start with a value from the previous solve operation.

In the context of ArcGIS Server, each server object component (SOC) process (at startup time and at recycle time) will construct a new network dataset object, and create a new instance of the directions customizer object. This instance of the directions customizer object will be used throughout the life cycle of the SOC process; only the Customize method will run at each request.

Limitations

The following limitations apply to custom directions:

  • Custom directions are only available in ArcGIS Pro and ArcGIS Server.
  • Custom directions can only be invoked on a file or enterprise geodatabase.
  • For performance reasons, directions customizers do not support using keywords for arguments.

Create and use a temporary directions customizer

The following sections describe how to create and use a temporary directions customizer. Each code sample illustrates a specific component of the full workflow. The workflow components are as follows:

  1. Solve a route analysis
  2. Define a directions customizer class
  3. Associate a directions customizer with a network dataset

The final code sample shows how to put all the components together.

The code samples below are created using the network analyst tutorial that is available for download from the data download page.

Solve a route analysis

The code sample below illustrates a workflow to solve a route analysis using the arcpy.nax solver object, and print turn-by-turn directions.

Note:

The path to the geodatabase in the code below must be updated to reflect where the data resides on your system.

import arcpy

# Create a network dataset object
network_dataset = arcpy.nax.NetworkDataset(
    r"C:\Data\Tutorial\SanFrancisco.gdb\Transportation\Streets_ND")

# Instantiate a route analysis
route = arcpy.nax.Route(network_dataset)
route.returnDirections = True

# Insert stops for the route
with route.insertCursor(
    arcpy.nax.RouteInputDataType.Stops,
    ["NAME", "SHAPE@XY"]
) as cursor:
    cursor.insertRow(["Stop1", (-122.501, 37.757)])
    cursor.insertRow(["Stop2", (-122.445, 37.767)])

# Solve the route
result = route.solve()

# Print the directions
if result.solveSucceeded:
    for row in result.searchCursor(
        arcpy.nax.RouteOutputDataType.DirectionPoints, ["DisplayText"]
    ):
        print(row[0])

Define a directions customizer class

The code sample below illustrates a directions customizer class definition. This example adds a number at the start of each directions maneuver. For example, without customization, the first direction would be Start at Stop1, with customization, the first direction would be 1 -> Start at Stop1.

import arcpy

class DirectionsIndexer(arcpy.nax.DirectionsCustomizer):
    """Defines a directions customizer that adds an index number to directions."""

    def customize(self, directions_query: arcpy.nax.DirectionsQuery):
        """Add an index number to each directions maneuver."""
        index = 1
        for junction in directions_query.junctions:
            for point in junction.directionPoints:
                point.displayText = f"{index} -> {point.displayText}"
                index += 1

Associate a directions customizer with a network dataset

The code sample below illustrates creating an instance of the directions customizer class and associating it with the network dataset object. Perform this workflow component before the route solve is invoked (route.solve()).

# Instantiate a directions customizer object that adds an index number
# to each directions maneuver's display text
add_numbers_customizer = DirectionsIndexer()

# Attach the custom directions object to the network dataset
network_dataset.directionsCustomizer = add_numbers_customizer

Combine all the components

The code sample below shows how to put all the components together into a complete workflow that defines and uses a temporary directions customizer for a route analysis workflow.

import arcpy

class DirectionsIndexer(arcpy.nax.DirectionsCustomizer):
    """Defines a directions customizer that adds an index number to directions."""

    def customize(self, directions_query: arcpy.nax.DirectionsQuery):
        """Add an index number to each directions maneuver."""
        index = 1
        for junction in directions_query.junctions:
            for point in junction.directionPoints:
                point.displayText = f"{index} -> {point.displayText}"
                index += 1

# Create a network dataset object
network_dataset = arcpy.nax.NetworkDataset(
    r"C:\Data\Tutorial\SanFrancisco.gdb\Transportation\Streets_ND")

# Instantiate a directions customizer object that adds an index number
# to each directions maneuver's display text
add_numbers_customizer = DirectionsIndexer()

# Attach the custom directions object to the network dataset
network_dataset.directionsCustomizer = add_numbers_customizer

# Instantiate a route analysis
route = arcpy.nax.Route(network_dataset)
route.returnDirections = True

# Insert stops for the route
with route.insertCursor(
    arcpy.nax.RouteInputDataType.Stops,
    ["NAME", "SHAPE@XY"]
) as cursor:
    cursor.insertRow(["Stop1", (-122.501, 37.757)])
    cursor.insertRow(["Stop2", (-122.445, 37.767)])

# Solve the route
result = route.solve()

# Print the directions
if result.solveSucceeded:
    for row in result.searchCursor(
        arcpy.nax.RouteOutputDataType.DirectionPoints, ["DisplayText"]
    ):
        print(row[0])

Create and use a persisted directions customizer

The following sections describe how to create and use a persisted directions customizer. This workflow creates a Python module that contains a directions customizer class, stores the Python module in the active Python environment ’s site-packages directory, updates the network dataset to use the directions customizer, and tests it by solving a route analysis. The workflow components are as follows:

  1. Clone the default Python environment (optional)
  2. Define the directions customizer class
  3. Update the network dataset schema
  4. Solve the route

The code samples below are created using the network analyst tutorial that is available for download from the data download page.

Clone the default Python environment (optional)

This step is optional. You only need to clone the default Python environment if the customization script needs to use third-party Python libraries that are not included in the default ArcGIS Pro Python environment.

Learn more about cloning an environment

Define the directions customizer class

The code sample below illustrates a directions customizer class definition. This example adds a number at the start of each directions maneuver.

import arcpy

class DirectionsIndexer(arcpy.nax.DirectionsCustomizer):
    """Defines a directions customizer that adds an index number to directions."""

    def customize(self, directions_query: arcpy.nax.DirectionsQuery):
        """Add an index number to each directions maneuver."""
        index = 1
        for junction in directions_query.junctions:
            for point in junction.directionPoints:
                point.displayText = f"{index} -> {point.displayText}"
                index += 1

In the active ArcGIS Pro Python environment, find the site-packages folder. In that directory, create a folder called na_customizers. Save the code above defining a directions customizer class to the na_customizers folder as direction_customization.py.

Update the network dataset schema

Copy Network Analyst\Tutorial\SanFrancisco.gdb from the tutorial data to SanFrancisco_Persisted.gdb.

Use the code below in a stand-alone script to permanently update the network dataset in SanFrancisco_Persisted.gdb with a persisted custom directions class.

import arcpy

# Check out the ArcGIS Network Analyst extension
arcpy.CheckOutExtension("network")

# Create a network dataset object
network_dataset = arcpy.nax.NetworkDataset(
    r"C:\Data\Tutorial\SanFrancisco_Persisted.gdb\Transportation\Streets_ND")

# Create a dictionary referencing the custom directions class to use
my_custom_directions = {"class": "na_customizers.direction_customization.DirectionsIndexer"}

# Update the network dataset to use the custom directions class
network_dataset.updateNetworkDatasetSchema(
    custom_directions=my_custom_directions
)

Solve the route

In ArcGIS Pro, use the network dataset from the SanFrancisco_Persisted.gdb to solve a route. Solve a second route, using SanFrancisco.gdb with the same stops and compare the output route directions. The directions in the route referencing SanFrancisco_Persisted.gdb should have a number prefixed to each of the directions because of the directions customizer. For example, using SanFrancisco.gdb, the first direction would be Start at Stop1, whereas using SanFrancisco_Persisted.gdb, the first direction would be 1 -> Start at Stop1.

Learn how to solve a route analysis in ArcGIS Pro

Deploy custom directions to be used in routing services hosted on an ArcGIS GIS Server site

You can publish two types of routing services: standard routing services and custom routing services.

Standard routing services are map and geoprocessing services that provide out-of-the-box capabilities available with the ArcGIS Network Analyst extension. You can publish standard routing services to an ArcGIS GIS Server site using a network dataset, and you will get a set of routing service endpoints with predefined parameters and schema for the inputs. These services provide full capability and integration with Esri applications such as ArcGIS Pro and Map Viewer.

Learn more about publishing standard routing services

Custom routing services are geoprocessing services with custom capabilities. The custom routing services allow you to perform a workflow that might involve multiple network analysis solvers or other geoprocessing tools. It also allows you to define custom parameters and input schema to fit the needs of the application.

Learn more about publishing custom routing services

Both standard routing services and custom routing services can invoke custom directions.

Invoke custom directions with standard routing services

To invoke custom directions with standard routing services, complete the following steps:

  1. Create a persisted directions customizer and associate it with a network dataset.
  2. Test the directions customizer in ArcGIS Pro to ensure it is invoked when you solve a route.
  3. Copy the network dataset to the same directory on all the machines participating in the ArcGIS Server site that will be used to host routing services.
  4. Copy the customization folder and file to all the machines participating in the ArcGIS Server site. Place the folder and file in the ArcGIS Server active Python environment's site-packages folder. The default path on a server machine is: <install>\ArcGIS\Server\framework\runtime\ArcGIS\bin\Python\envs\arcgispro-py3\Lib\site-packages. When you copy from the ArcGIS Pro Python environment's site-packages folder to ArcGIS Server, maintain the same folder structure for the customization code. For example, if the customization code is in the direction_customization.py file in the na_customizers folder, copy the na_customizers folder to the site-packages folder on ArcGIS Server.
  5. Publish the standard routing services.

When you use the routing services, the customization is applied.

Invoke custom directions with custom routing services

To invoke custom directions with custom routing services, complete the following steps:

  1. Author a script tool following the steps in the Publish custom routing services topic.
  2. Associate a temporary customizer with a network dataset in the script tool.
  3. Publish the service following the steps in the Publish custom routing services topic.

When you use the routing services, the customization is applied.