This topic describes how to perform network analysis in Python using the Network Analyst module, arcpy.nax. The module provides access to six different analysis types. For each analysis (also known as a solver), you work with two objects specific to that analysis. An object that lets you 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 ServiceArea and ServiceAreaResult objects that allows you to perform the service area analysis. For each object, the image shows the properties and methods available with the objects. The properties allows you to modify the analysis settings where as the methods allow you to perform some action such as load the inputs or execute the analysis.
Note:
While this topic mainly uses Service Area analysis as an example, the guidelines described here can be applied to perform any type of network analysis.
When performing network analysis, you need to follow this five step workflow.
- Initialize the analysis.
- Set properties for the analysis.
- Load the inputs.
- Solve the analysis.
- Work with the results.
Initialize the analysis
In order to initialize the analysis, you need to provide a network dataset which models the transportation network on which the analysis is performed. You can specify the network dataset using it's full catalog path or using a name that points to a network dataset layer created from a network dataset using the MakeNetworkDatasetLayer function.
Tip:
To get the best performance, it is recommended to use a name that points to a network dataset layer when initializing your analysis. If you use a catalog path instead, the network dataset is opened each time the analysis is initialized. Opening a network dataset is expensive, as they contain advanced data structures and tables that are read and cached. A network dataset layer opens the dataset only a single time and hence performs better when the same layer is used.
The below 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 it's 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 have initialized the analysis, you need to set the properties for the analysis. To determine the properties supported by an analysis object and understand how a given property can influence the analysis, refer to the help topic for the particular object (available under the Classes category in the help). Many of the properties are set using Python enumerations, 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 if a given property needs to be set as an enumeration and the name of the enumeration that you should use.
The most important property that you should set for any analysis is the travel mode used for the analysis. In order to set the travel mode, you need to first get the list of travel modes supported by the network dataset. This can be achieved using the GetTravelModes function. From the supported list, you can select the travel mode for the analysis using it's name. The GetTravelModes function returns a Python dictionary where 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 snipped assumes that you have already initialized the service area analysis object.
# Get the desired travel mode for 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 need to load the input locations that are 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, in order to perform service area analysis, you need to load one or more facilities, locations around which you generate service area polygons and service area lines, into 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 a 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.
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)
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.
import csv
import arcpy
# Read the CSV file with 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 expensive depending on the number of input features. If your analysis needs to reload the same set of input features between different analysis scenarios, for example, you want to perform service area analysis with time of day as 8 AM and 10 AM but use the same set of facilities in both analysis, you can optimize the overall performance by pre-calculating 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 (that you got after running the CalculateLocations function), executing the analysis will be faster since the analysis will not need to do this processing again.
Tip:
The use of CalculateLocations is recommended only if you are reloading the same inputs for different analysis scenarios. If you are loading inputs only once, running CalculateLocations does not provide any performance improvement.
Solve the analysis
In order to execute the analysis, you need to 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 could not find any results, for example, if a route can not be found between your input stops, the solve method does not raise any Python exception. To determine if the analysis produced a valid result, you can use the solveSucceeded property of the result object.
License:
A Network Analyst extension license is required in order for solve to be successful. You should check out the license before you call the solve method.
The below 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, you can work with the results using the result object obtained from the solve method. The result object allows you to determine if the analysis succeeded or failed (with the solveSucceeded property) and also whether the analysis produced a partial solution (with isPartialSolution property). You can determine why the analysis failed by using the solverMessages method. In order to export the results to a feature class, you can use the export method.
The below 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])
If you need to display only certain information from the result such as the total time and distance from a route, you can 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 below code snippet shows how to print the total time and total 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.")