Perform network analysis

You can perform network analysis in Python using the Network Analyst module, arcpy.nax. This module provides access to six analysis types. For each analysis (also known as a solver), you work with two objects specific to that analysis: an object that allows you to initialize the analysis, set analysis settings, load the inputs, and execute the analysis, and another object that allows you to work with the analysis results after you execute the analysis. The image below shows the ServiceArea and ServiceAreaResult objects that are used to perform a service area analysis. The image shows the properties and methods available to each object. The properties allow you to modify the analysis settings, and the methods allow you to perform an action, such as load the inputs or execute the analysis.

Properties and methods available with the ServiceArea and ServiceAreaResult objects

Note:

This topic uses Service Area analysis as an example; however, the information provided here can be applied to any type of network analysis.

To perform network analysis, follow this five-step workflow:

  1. Initialize the analysis
  2. Set properties for the analysis
  3. Load the inputs
  4. Solve the analysis
  5. Work with the results

Initialize the analysis

To initialize the analysis, provide a network dataset that models the transportation network on which the analysis will be performed. You can specify the network dataset using its full catalog path or a name of a network dataset layer created from a network dataset using the MakeNetworkDatasetLayer function.

Tip:

For best performance, use the name of a network dataset layer when initializing your analysis. If you use a catalog path, the network dataset is opened each time the analysis is initialized. Opening a network dataset is time consuming, as datasets contain advanced data structures and tables that are read and cached. A network dataset layer opens the dataset one time and performs better when the same layer is used.

The following code snippet shows how to initialize a service area analysis:

import arcpy
nd_path = "C:/data/NorthAmerica.gdb/Routing/Routing_ND"
nd_layer_name = "NorthAmerica"

# Create a network dataset layer. The layer will be referenced using its name.
arcpy.nax.MakeNetworkDatasetLayer(nd_path, nd_layer_name)

# Instantiate a ServiceArea analysis object.
service_area = arcpy.nax.ServiceArea(nd_layer_name)

Set properties for the analysis

After you initialize the analysis, you need to set the properties for the analysis. To determine the properties that are supported by an analysis object and understand how a given property can influence the analysis, see the help topic for the particular object (available under the Classes category in the help). Many of the properties are set using Python enumeration objects, a set of symbolic names representing some constant values. For example, the time units for the analysis can be set to minutes using the Minutes member from the arcpy.nax.TimeUnits enumeration. The help topic for an object describes whether a property needs to be set as an enumeration. It also specifies the name of the enumeration you should use.

The most important property that you should set for any analysis is the travel mode used for the analysis. To set the travel mode, you first must get the list of travel modes supported by the network dataset. Use the GetTravelModes function to do this. From the supported list, select the travel mode for the analysis using its name. The GetTravelModes function returns a dictionary in which key is the travel mode name and value is the travel mode object

The below code snippet shows how to set the properties for the service area analysis. The snippet assumes that you have already initialized the service area analysis object.

# Get the desired travel mode for the analysis.
nd_travel_modes = arcpy.nax.GetTravelModes(nd_layer_name)
travel_mode = nd_travel_modes["Driving Time"]

# Set properties.
service_area.timeUnits = arcpy.nax.TimeUnits.Minutes
service_area.defaultImpedanceCutoffs = [5, 10, 15]
service_area.travelMode = travel_mode
service_area.outputType = arcpy.nax.ServiceAreaOutputType.Polygons
service_area.geometryAtOverlap = arcpy.nax.ServiceAreaOverlapGeometry.Split

Load the inputs

Before you execute the analysis, you must load the input locations to be used for the analysis. Depending on your analysis type, you need to load one or more locations into one or more input data types supported by that analysis. For example, to perform service area analysis, you must load one or more facilities—locations around which you generate service area polygons and service area lines—into the ServiceAreaInputDataType.Facilities data type.

You can load the inputs in two ways. If your input locations are already available as feature classes or tables, you can load them using the load method on the analysis object. If you are reading input locations from other data sources, such as a CSV file containing latitude and longitude values, or if you have input locations as ArcPy point objects, you can load the inputs using the insertCursor method on the analysis object.

The following code snippet shows how to load facilities from a feature class using the load method when performing a service area analysis:

input_facilities = "C:/data/io.gdb/Facilities"
service_area.load(arcpy.nax.ServiceAreaInputDataType.Facilities, input_facilities)

The following code snippet shows how to load facilities from a CSV file using the insertCursor method when performing a service area analysis. The snippet assumes that service_area is already initialized.

# Read the CSV file with the following content.
csv_file = "facilities.csv"

"""
ID,FacilityName,X,Y
1,Store 3,-117.101911,32.634351
2,Store 5,-116.979706,32.562102
3,Store 6,-116.971414,32.654230
"""

cursor_fields = ["Name", "SHAPE@XY"]
with service_area.insertCursor(arcpy.nax.ServiceAreaInputDataType.Facilities, cursor_fields) as cur:
    with open(csv_file, "r", encoding="utf-8", newline="") as facilities:
        csv_reader = csv.reader(facilities)
        # Assume the first line is the header and skip it.
        next(csv_reader)
        for row in csv_reader:
            location = (float(row[2]), float(row[3]))
            cur.insertRow([row[1], location])

When the analysis is executed, it first needs to find the location of all the inputs on the transportation network. This process can be lengthy if there are a large number of input features. If your analysis needs to reload the same set of input features for different analyses, for example, you want to perform service area analysis with time of day as 8 a.m. and 10 a.m. but use the same set of facilities in both analyses, you can optimize the overall performance by precalculating the information used by the analysis to locate the input facilities. This can be done using the CalculateLocations function. Once your input has the location fields (from the CalculateLocations function), use the fieldMappings method with the use_location_fields parameter set to True, and use these field mappings when loading the input for your analysis. Executing the analysis will be faster because the analysis will not need to do this processing again.

Learn more about network location fields and how inputs are located on the network

Tip:

Using CalculateLocations is recommended only if you are reloading the same inputs for different analyses. If you are loading inputs only once, running CalculateLocations does not provide performance improvement.

The following code snippet shows how to use network location fields when loading inputs:

# Run the Calculate Locations tool to calculate network location fields
arcpy.nax.CalculateLocations(
    input_facilities, nd_layer_name, "5000 Meters",
    [["Streets", "SHAPE"], ["Streets_ND_Junctions", "NONE"]], 
    travel_mode=travel_mode
)

# Initialize a Service Area analysis
service_area = arcpy.nax.ServiceArea(nd_layer_name)

# Create a fieldMappings object for my Service Area with the use_location_fields 
# parameter set to True
field_mappings = service_area.fieldMappings(
    arcpy.nax.ServiceAreaInputDataType.Facilities, True)

# Map the names of my facilities to the Service Area's Name field
field_mappings["Name"].mappedFieldName = "FacilityName"

# Map the network location fields. These lines are not strictly necessary 
# because the location fields will be mapped from the input table by default if 
# the fields are present.
field_mappings["SourceID"].mappedFieldName = "SourceID"
field_mappings["SourceOID"].mappedFieldName = "SourceOID"
field_mappings["PosAlong"].mappedFieldName = "PosAlong"
field_mappings["SideOfEdge"].mappedFieldName = "SideOfEdge"

# Load the facilities using the field mappings
service_area.load(arcpy.nax.ServiceAreaInputDataType.Facilities, 
                  input_facilities, field_mappings)

Solve the analysis

To execute the analysis, call the solve method. This method returns a result object that can be used to work with results from the analysis, such as exporting the results to a feature class.

If the analysis did not find any results, for example, if a route cannot be found between your input stops, the solve method does not raise a Python exception. To determine whether the analysis produced a valid result, use the solveSucceeded property of the result object.

License:

A Network Analyst extension license is required for solve to be successful. Check out the license before you call the solve method.

The following code snippet shows to how to execute an analysis:

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

# Solve the analysis.
result = service_area.solve()

Work with the results

After executing the analysis, use the result object obtained from the solve method to work with the results. The result object allows you to determine whether the analysis succeeded or failed (using the solveSucceeded property) and whether the analysis produced a partial solution (using the isPartialSolution property). If the analysis failed, you can determine why using the solverMessages method. To export the results to a feature class, use the export method.

The following code snippet shows how to execute the analysis and export the results to a feature class:

# Solve the analysis.
result = service_area.solve()

# Export the results to a feature class. If the analysis failed print all the 
# messages.
if result.solveSucceeded:
    result.export(arcpy.nax.ServiceAreaOutputDataType.Polygons, output_polygons)
else:
    arcpy.AddError("Analysis failed")
    # Print all the warning messages.
    for message in result.solverMessages(arcpy.nax.MessageSeverity.Warning):
        arcpy.AddWarning(message[-1])
    # Print all the error messages.
    for message in result.solverMessages(arcpy.nax.MessageSeverity.Error):
        arcpy.AddError(message[-1])

To display only certain information from the result, such as the total time and distance from a route, use the searchCursor method to iterate over the result features. This can be faster than exporting the results first to a feature class and then deriving such information from the feature class.

The following code snippet shows how to print the total time and distance from a route analysis using the searchCursor method:

# Solve the analysis.
result = route.solve()

with result.searchCursor(arcpy.nax.RouteOutputDataType.Routes, ["Name", "Total_Minutes", "Total_Miles"]) as cur:
    for row in cur:
        print(f"Summary for route: '{row[0]}'")
        print(f"Total time: {row[1]} minutes.")
        print(f"Total distance: {row[2]} miles.")