# Guide to Network Analysis (Part 2 - Find Routes)

## Introduction

Now we have learned the concepts of Network Datasets and Network Analysis services, let's move on the second topic - how to find routes from one point to another, and/or among multiple points. This would solve questions like,

• What is the quickest route from point A to point B?
• To travel among points [A, B, C, D, ...], what's the shorted route?

• Network Dataset and Network Analysis services (Part 1)
• Find Routes (You are here!)
• Generate Service Area (Part 3)
• Find Closest Facility (Part 4)
• Generate Origin Destination Cost Matrix (Part 5)
• Solve Location Allocation (Part 6)
• Vehicle Routing Problem Service (Part 7)

Routes represent the quickest or shortest path along roads to visit stops or point locations. They can be basic point-to-point routes visited in the order you specify, or in the order that minimizes overall travel time or distance. A route is associated with a local network dataset or a network service hosted in ArcGIS Online or ArcGIS Enterprise [1]. Here, we will show how to use the online service to solve routing problems.

## Problem statement

Centering at how to find routes among multiple points, we can assume the user story is like this:

Jim is planning his Summer trip along the west of the States, and he has decided to start from Miami, end at San Francisco, and also make stops in eight other cities: San Jose, Los Angeles, San Diego, Phoenix, El Paso, Houston, New Orleans, Orlando. What will be the best route for Jim to travel between these ten cities without making extra stops? And what will be the estimated travel distance and time? Lastly, can Jim be provided with directions and maps for routing?

Now that Jim's objectives are defined, we can go onto breaking down the problem:

• Data: where to access the input dataset
• Methods: what tools can be used to build the network model and perform routing analysis
• Tables and maps: deliverables in which directions and routes are visualized.

Let's first access and/or explore the input dataset (in this case, the stops feature class).

## Data

### Access the Stops Feature Class

The `find_routes` tool requires a `stops feature class` as the input parameter, which should be of a FeatureSet type specifying two or more stops to route between. The upper limit is for the `stops` feature class to have 10,000 stops or up to 150 stops assigned to a single route.

In this example, we will extract the `stops` feature class from an existing Feature Service. First of all, we need to import all required modules, and establish a GIS connection object to the target organization.

``````from copy import deepcopy
import datetime as dt
from IPython.display import HTML
import json
import pandas as pd
from arcgis.gis import GIS
import arcgis.network as network
import arcgis.geocoding as geocoding
from arcgis.features import FeatureLayer, FeatureSet, FeatureCollection
import arcgis.features.use_proximity as use_proximity``````

If you have already set up a profile to connect to your ArcGIS Online organization, execute the cell below to load the profile and create the GIS class object. If not, use a traditional username/password log-in e.g. `my_gis = GIS('https://www.arcgis.com', 'username', 'password', verify_cert=False, set_active=True)`

``my_gis = GIS('home')``

The existing Feature Layer to be used is titled "USA Major Cities", and can be accessed through a `search` call.

``````sample_cities = my_gis.content.search('title:"USA Major Cities" type:Feature Service owner:esri*',
outside_org=True)[0]
sample_cities``````
USA Major Cities
This layer presents the locations of cities within the United States with populations of approximately 10,000 or greater, all state capitals, and the national capital.Feature Layer Collection by esri_dm

Place the cities to be visited in a list, and provide a formatter that will later be used to format the list into a string.

``````stops_cities = ['San Francisco', 'San Jose', 'Los Angeles', 'San Diego',
'Phoenix', 'El Paso',
'Houston', 'New Orleans', 'Orlando', 'Miami']
values = "'" + "', '".join(stops_cities) + "'"``````
``````stops_cities_fl = FeatureLayer(sample_cities.url + "/0")
type(stops_cities_fl)``````
`arcgis.features.layer.FeatureLayer`

With the `FeatureLayer` class object defined, make `query` calls to get back the cities as FeatureSet.

``````stops_cities_fset = stops_cities_fl.query(where="ST in ('CA', 'NV', 'TX', 'AZ', 'LA', 'FL')  AND NAME IN ({0})".format(values), as_df=False)
stops_cities_fset``````
`<FeatureSet> 10 features`
``````start_cities_fset = stops_cities_fl.query(where="ST='FL' AND NAME = 'Miami'", as_df=False)
start_cities_fset``````
`<FeatureSet> 1 features`

### Re-arrange the order of stops

Sometimes features in the returned resulting FeatureSet from `FeatureLayer.query()` are not in the same sequential order of the search string. For example, the starting city in `stops_cities` is "San Francisico", but in the returned FeatureSet `stops_cities_fset`, "Los Angeles" appears to be the start.

``print(list(map(lambda x: x.attributes['NAME'], stops_cities_fset)))``
```['Los Angeles', 'San Diego', 'San Francisco', 'San Jose', 'Phoenix', 'New Orleans', 'Miami', 'Orlando', 'El Paso', 'Houston']
```

The method `re_order_stop_cities` can be used to re-order the stops in the FeatureSet to match the original starting/ending sequence.

``````""" Used to re-order the stops to the desired order
"""
def re_order_stop_cities(fset=stops_cities_fset, start_city = "Miami", end_city = "San Francisco"):

stops_cities_flist = []
last_city = None

for ea in fset:
if ea.attributes['NAME'] == start_city:
stops_cities_flist.insert(0, ea)
elif ea.attributes['NAME'] == end_city:
last_city = ea
else:
stops_cities_flist.append(ea)
stops_cities_flist.append(last_city)

return FeatureSet(stops_cities_flist)``````
``````re_ordered_stops_cities_fset = re_order_stop_cities()
re_ordered_stops_cities_fset``````
`<FeatureSet> 10 features`

Also we need to assign `spatialReference` to the newly sorted FeatureSet, or else, an error would be thrown at the solver as `Invalid value for parameter Stops - Details : Invalid JSON for GPFeatureRecordSetLayer or GPRecordSet datatype Failed.`

``re_ordered_stops_cities_fset.spatial_reference = stops_cities_fset.spatial_reference``
``````re_ordered_stops_cities = list(map(lambda x: x.attributes['NAME'], re_ordered_stops_cities_fset))
print(re_ordered_stops_cities)``````
```['Miami', 'Los Angeles', 'San Diego', 'San Jose', 'Phoenix', 'New Orleans', 'Orlando', 'El Paso', 'Houston', 'San Francisco']
```

## Methods

The ArcGIS API for Python provides three ways to solve a routing problem, which are namely, RouteLayer.solve, find_routes, and plan_routes.

Operationnetwork.analysisfeatures.use_proximity
Routefind_routesplan_routes
ServiceAreagenerate_service_areascreate_drive_time_areas
ClosestFacilityfind_closest_facilitiesfind_nearest

These three methods are defined in different modules of the arcgis package, and will make distinct REST calls in the back end. A key distinction between RouteLayer.solve and network.analysis.find_routes, features.use_proximity.plan_routes is, the former is meant for custom or advanced routing workflows where you need to publish your own Network Analysis layers. The latter tools work against routing services hosted on ArcGIS Online or available on your Enterprise via proxy services and will cost you credits.

In this part of guide, we will walk through the workflows of using `network.analysis.find_routes()` and `features.use_proximity.plan_routes()` in solving the same problem - designing the quickest route to travel amongst the ten cities defined in `stops_cities`, and further explore the differences in the process.

### Method 1 - Using arcgis.network.analysis.find_routes

Finding a route analysis can mean determining the quickest or shortest way to travel between locations. You might want to use the `find_routes` tool in `arcgis.network.analysis` module to generate driving directions to visit multiple stops or to measure the distance or travel time between locations. The tool is capable of finding routes for one or more vehicles each time it runs, so you can determine the best routes for several drivers to visit pre-assigned stops. Parameters for `find_routes` include:

• `stops`: Required parameter. Specify two or more stops to route between;
• `preserve_terminal_stops`: Preserve Terminal Stops (string). Optional parameter. When Reorder Stops to Find Optimal Routes is checked (or True), you have options to preserve the starting or ending stops and the tool can reorder the rest.
• `time_of_day`: Time of Day (datetime). Optional parameter. Specifies the time and date at which the routes should begin;
• `time_zone_for_time_of_day`: Time Zone for Time of Day (str). Optional parameter. Specifies the time zone of the Time of Day parameter.
• set `save_output_na_layer` to True, if you want to save the output route into a new layer file.
``````%%time

start_time = int(dt.datetime.now().timestamp() * 1000)

result = network.analysis.find_routes(re_ordered_stops_cities_fset, time_of_day=start_time,
time_zone_for_time_of_day="UTC",
preserve_terminal_stops="Preserve None",
reorder_stops_to_find_optimal_routes=True,
save_output_na_layer=True)``````
```WARNING 030194: Data values longer than 500 characters for field [Stops:Name] are truncated.
Network elements with avoid-restrictions are traversed in the output (restriction attribute names: "Through Traffic Prohibited").
```
```Wall time: 1min 26s
```

Then, let's check if the tool has been successfully run, what are the types of each output element, and the network analysis layer URL of the output route.

``print("Is the tool executed successfully?", result.solve_succeeded)``
```Is the tool executed successfully? True
```

The result we get from the previous operation is a tool output that contains three components - output_routes, output_stops, and output_directions.

``type(result)``
`arcgis.geoprocessing._support.ToolOutput`
``result.output_routes, result.output_stops, result.output_directions``
`(<FeatureSet> 1 features, <FeatureSet> 10 features, <FeatureSet> 267 features)`
``result.output_network_analysis_layer.url``
`'https://logistics.arcgis.com/arcgis/rest/directories/arcgisjobs/world/route_gpserver/je18da4dfbc64451badfb629433d3ef47/scratch/_ags_gpnac76d06923d674a76ad5524c4dcc726d5.lyr'`

#### Tabularizing the response from find_routes

The `output_directions` component of the result can be read as a pandas data frame (which lists all the directions on the route).

``````""" Access the output directions
"""
df = result.output_directions.sdf
df = df[["RouteName", "ArriveTime", "DriveDistance", "ElapsedTime", "Text"]].loc[df["RouteName"] == "Miami - San Francisco"]
RouteNameArriveTimeDriveDistanceElapsedTimeText
0Miami - San Francisco2019-10-29 23:30:30.3650000100.0000000.000000Start at Miami
1Miami - San Francisco2019-10-29 23:30:30.3650000100.0895870.320852Go west on SW 13th St toward SW 17th Ave
2Miami - San Francisco2019-10-29 23:30:49.6159999371.3228432.644720Turn right on SW 17th Ave
3Miami - San Francisco2019-10-29 23:33:28.2990000250.4758411.502659Turn right on NW 7th St (Luis Sabines Way)
4Miami - San Francisco2019-10-29 23:34:58.4590001110.2789660.669519Turn left on NW 12th Ave (SR-933)
``df.tail()``
RouteNameArriveTimeDriveDistanceElapsedTimeText
262Miami - San Francisco2019-11-01 00:15:26.4939999580.0406090.166500Continue forward on Masonic Ave
263Miami - San Francisco2019-11-01 00:15:36.4839999680.0597250.228120Make a sharp right on Upper Ter
264Miami - San Francisco2019-11-01 00:15:50.1719999310.0671300.585009Bear left
265Miami - San Francisco2019-11-01 00:15:50.1719999310.0000000.000000Restriction: Through Traffic Prohibited
266Miami - San Francisco2019-11-01 00:16:25.2720000740.0000000.000000Finish at San Francisco, on the left

Also, from the `output_routes` component of the returned object, we can then form a summary table of the entire trip.

``````""" Access output routes
"""
df = result.output_routes.sdf
start_times = pd.to_datetime(df["StartTime"], unit="ms")
end_times = pd.to_datetime(df["EndTime"], unit="ms")
df["StartTime"] = start_times.apply(lambda x: x.strftime("%H:%M:%S"))
df["EndTime"] = end_times.apply(lambda x: x.strftime("%H:%M:%S"))
df[["Name", "StartTime", "EndTime", "StopCount", "Total_Kilometers", "Total_Minutes"]]``````
NameStartTimeEndTimeStopCountTotal_KilometersTotal_Minutes
0Miami - San Francisco23:30:3000:16:25105279.7281263105.915118

Based on the results shown above, the estimated travel distance from Miami to San Francisco is 5279.728 KM, and the estimated travel time is 3105.915 minutes (around 50.825 hours), which means that, if you start from Miami at 23:30:30 PM (EST) on Monday, for instance, you can expect to arrive at San Francisco at 00:16:25 PM (PST) of the same Wednesday.

#### Visualizing the response from find_routes

Now that we have fully mastered the itineraries of the trip, it is time to visualize the route on the map widget so target users can have a better grasp of the geographical relations between stops.

The following cell defines the styles and symbologies for the stops, and routes. In order to emphasize on the starting and ending stops, we will assign balloon icons as their symbols.

``````""" draw the route and stops with customized styles, symbologies, and popups
"""
styles = [

route_symbol = {
"type": "esriSLS",
"style": "esriSLSSolid",
"color": [128,0,128,90],
"width": 4
}

stops_symbol = {"angle":0,"xoffset":2,"yoffset":8,"type":"esriPMS",
"url":"http://static.arcgis.com/images/Symbols/Basic/ShinyPin.png",
"contentType":"image/png","width":24,"height":24}

start_symbol = {"angle":0,"xoffset":0,"yoffset":8.15625,"type":"esriPMS",
"url":"http://static.arcgis.com/images/Symbols/AtoZ/redA.png",
"contentType":"image/png","width":15.75,"height":21.75}

end_symbol = {"angle":0,"xoffset":0,"yoffset":8.15625,"type":"esriPMS",
"url":"http://static.arcgis.com/images/Symbols/AtoZ/greenB.png",
"contentType":"image/png","width":15.75,"height":21.75}

popup_route = {"title": "Route",
"content": df.style.set_table_styles(styles).render()}
popup_stop = {"title": "Stop {}",
"content": df.style.set_table_styles(styles).render()}``````

Here, we will use the function `check_curb_approach2` to determine if the stop is the start (the departing point), a by-passing point, or the end (the arriving point) of the route.

``````def check_curb_approach2(result):
attributes = result.attributes
return (attributes['ArriveCurbApproach'], attributes['DepartCurbApproach'])``````
``````map1 = my_gis.map('Texas, USA', zoomlevel=4)
map1``````
``````""" draw the route and stops with customized styles, symbologies, and popups
"""
for route in result.output_routes:
map1.draw(route.geometry, popup_route, route_symbol)

sequence = 1
for stop in result.output_stops:

stop_bool_tuple = check_curb_approach2(stop)
if stop_bool_tuple[0] is None:
# starting point
symbol = start_symbol
elif stop_bool_tuple[1] is None:
# ending point
symbol = end_symbol
else:
# by-passing point
symbol = stops_symbol

map1.draw(stop.geometry,
{"title": "Stop {}".format(sequence),
symbol)
sequence+=1``````
``````#save into a web map
item_properties = {
"title": "Miami - San Francisco (2)",
"tags" : "Routing",
"snippet": " Route from Miami to San Francisco",
"description": "a web map of Route from Miami to San Francisco using network.RouteLayer.solve"
}

item = map1.save(item_properties)``````
``item``
Miami - San Francisco (2)
Route from Miami to San FranciscoWeb Map by portaladmin

We have been using the find_routes tool in the `network.analysis` module up to this point. From now on, let's use a different method - plan_routes - defined in the `features.use_proximity` module, to achieve a workflow driven, Feature Service to Feature Service user experience.

### Method 2 - Using arcgis.features.use_proximity.plan_routes

The plan_routes method determines how to efficiently divide tasks among a mobile workforce.

You provide the input, which includes a set of stops and the number of vehicles available to visit the stops, and the tool assigns the stops to vehicles and returns routes showing how each vehicle can reach their assigned stops in the least amount of time.

With plan_routes, mobile workforces reach more job sites in less time, which increases productivity and improves customer service. Organizations often use plan_routes to:

• Inspect homes, restaurants, and construction sites
• Provide repair, installation, and technical services
• Deliver items and small packages
• Make sales calls
• Provide van transportation from spectators’ homes to events

#### Prepare the input parameters

Because the `stops_layer` is required to be of a Feature Layer, we need to prepare the input data in a different format.

``````re_ordered_values = "'" + "', '".join(re_ordered_stops_cities) + "'"
re_ordered_values``````
`"'Miami', 'Los Angeles', 'San Diego', 'San Jose', 'Phoenix', 'New Orleans', 'Orlando', 'El Paso', 'Houston', 'San Francisco'"`
``````stops_layer = {'url': sample_cities.layers[0].url,
'filter': "ST in ('CA', 'NV', 'TX', 'AZ', 'LA', 'FL')  AND NAME IN ({0})".format(re_ordered_values)}
start_layer = {'url': sample_cities.layers[0].url,
'filter': "ST = 'FL' AND NAME = 'Miami'"}
end_layer = {'url': sample_cities.layers[0].url,
'filter': "NAME = 'San Francisco'"}``````

#### With output_name specified

A reference to the output Feature Layer Collection is returned when `output_name` is specified. And the returned Feature Layer includes a layer of routes showing the shortest paths to visit the stops; a layer of the stops assigned to routes, as well as any stops that couldn’t be reached due to the given parameter settings; and a layer of directions containing the travel itinerary for each route.

``````%%time

"""
result1 = use_proximity.plan_routes(stops_layer=stops_layer, route_count=1,
max_stops_per_route=10, route_start_time=start_time,
start_layer_route_id_field = "FID",
start_layer=start_layer,
end_layer=end_layer,
return_to_start=False,
context={'outSR': {"wkid": 4326}},
output_name="Plan Route from Miami to San Francisco 2a",
gis=my_gis)``````
```Network elements with avoid-restrictions are traversed in the output (restriction attribute names: "Through Traffic Prohibited").
```
```Wall time: 1min 59s
```
``result1``
Plan Route from Miami to San Francisco 2a
Feature Layer Collection by arcgis_python
``````route_sublayer = FeatureLayer.fromitem(result1, layer_id=1)
route_sublayer.url``````
`'https://services7.arcgis.com/JEwYeAy2cc8qOe3o/arcgis/rest/services/Plan Route from Miami to San Francisco 2a/FeatureServer/1'`
``route_sublayer.query(where='1=1', as_df=True)``
EndTimeEndTimeUTCOBJECTIDRouteLayerItemIDRouteLayerItemURLRouteNameSHAPEStartTimeStartTimeUTCStopCountTotalStopServiceTimeTotalTimeTotalTravelTimeTotal_KilometersTotal_Miles
02019-11-01 03:50:242019-11-01 10:50:241NoneNoneMiami - San Francisco - Route1{'paths': [[[-8930153.5818, 2969499.7754], [-8...2019-10-30 03:30:302019-10-30 07:30:301003079.9013153079.9013155282.7393282.549989

From this summary table, we can tell the estimated travel distance from Miami to San Francisco is 5282.739 KM, and the estimated travel time is 3079.901 minutes (around 51.332 hours).

``````map2a = my_gis.map('Texas, USA', zoomlevel=4)
map2a``````
``map2a.add_layer(result1)``

We have seen how a Feature Service (input) to Feature Service (output) workflow is like from previous operation, now let's look at a different approach - when the output name is not specified in the arguments.

#### Without output_name specified

A dict object is returned without `output_name` being specified:

``````%%time

"""
result1 = use_proximity.plan_routes(stops_layer=stops_layer, route_count=1,
max_stops_per_route=10, route_start_time=start_time,
start_layer_route_id_field = "FID",
start_layer=start_layer,
end_layer=end_layer,
return_to_start=False,
context={'outSR': {"wkid": 4326}},
gis=my_gis)``````
```Network elements with avoid-restrictions are traversed in the output (restriction attribute names: "Through Traffic Prohibited").
```
```Wall time: 1min 39s
```

The returned dictionary contains two components - `routes_layer` and `assigned_stops_layer`.

``result1.keys()``
`dict_keys(['routes_layer', 'assigned_stops_layer'])`
``type(result1['routes_layer']), type(result1['assigned_stops_layer'])``
```(arcgis.features.feature.FeatureCollection,
arcgis.features.feature.FeatureCollection)```

We can create a pandas data frame out of the `routes_layer` component to summarize the records. In the cell below, we can see for the route "Miami to San Francisco", 10 stops are made, with the total time and distance specified.

``````df = result1['routes_layer'].query().sdf
df[['RouteName', 'StopCount', 'TotalTime', 'Total_Kilometers']]``````
RouteNameStopCountTotalTimeTotal_Kilometers
0Miami - San Francisco - Route1103079.9013155282.739

For the stops to be made on the route, we take advantage of the `assigned_stops_layer`, and convert it to a pandas dataframe object.

``````df2 = result1['assigned_stops_layer'].query().sdf
df2[['OID', 'NAME', 'ST', 'PLACEFIPS', 'RouteName', 'StopType', 'Sequence', \
'ArriveTime', 'DepartTime', 'FromPrevDistanceKilometers']]``````
OIDNAMESTPLACEFIPSRouteNameStopTypeSequenceArriveTimeDepartTimeFromPrevDistanceKilometers
01NoneNoneNoneMiami - San Francisco - Route1Route start12019-10-30 03:30:30.0000000002019-10-30 03:30:30.0000000000.000000
12MiamiFL1245000Miami - San Francisco - Route1Stop22019-10-30 03:30:30.0000000002019-10-30 03:30:30.0000000000.000000
23OrlandoFL1253000Miami - San Francisco - Route1Stop32019-10-30 07:27:58.5409998892019-10-30 07:27:58.540999889386.309025
34New OrleansLA2255000Miami - San Francisco - Route1Stop42019-10-30 15:49:44.3159999852019-10-30 15:49:44.3159999851028.402013
45HoustonTX4835000Miami - San Francisco - Route1Stop52019-10-30 21:23:15.8190000062019-10-30 21:23:15.819000006560.581917
56El PasoTX4824000Miami - San Francisco - Route1Stop62019-10-31 07:12:39.4189999102019-10-31 07:12:39.4189999101203.096420
67PhoenixAZ0455000Miami - San Francisco - Route1Stop72019-10-31 12:37:54.0009999282019-10-31 12:37:54.000999928691.807799
78San DiegoCA0666000Miami - San Francisco - Route1Stop82019-10-31 18:16:21.6979999542019-10-31 18:16:21.697999954570.860072
89Los AngelesCA0644000Miami - San Francisco - Route1Stop92019-10-31 20:55:40.7170000082019-10-31 20:55:40.717000008206.458328
910San JoseCA0668000Miami - San Francisco - Route1Stop102019-11-01 02:38:47.9600000382019-11-01 02:38:47.960000038547.931169
1011San FranciscoCA0667000Miami - San Francisco - Route1Stop112019-11-01 03:50:24.0789999962019-11-01 03:50:24.07899999687.292255
1112NoneNoneNoneMiami - San Francisco - Route1Route end122019-11-01 03:50:24.0789999962019-11-01 03:50:24.0789999960.000000

In order to show the arrival time and better organize the columns, try the cell below to do a little parsing:

``````my_df = df2[['OID', 'NAME', 'ST', 'PLACEFIPS', 'RouteName', 'StopType', 'Sequence', \
'ArriveTimeUTC', 'DepartTimeUTC', 'FromPrevDistanceKilometers']]
end_times = pd.to_datetime(df2['ArriveTimeUTC'])
my_df["ArriveTimeUTC"] = end_times.apply(lambda x: x.strftime("%m-%d %H:%M:%S"))
start_times = pd.to_datetime(df2['DepartTimeUTC'])
my_df["DepartTimeUTC"] = start_times.apply(lambda x: x.strftime("%m-%d %H:%M:%S"))
my_df['Sequence'] = df2['Sequence'].apply(lambda x: int(x))
my_df.sort_values('Sequence', inplace=True, ascending=True)``````
``my_df``
OIDNAMESTPLACEFIPSRouteNameStopTypeSequenceArriveTimeUTCDepartTimeUTCFromPrevDistanceKilometers
01NoneNoneNoneMiami - San Francisco - Route1Route start110-30 07:30:3010-30 07:30:300.000000
12MiamiFL1245000Miami - San Francisco - Route1Stop210-30 07:30:3010-30 07:30:300.000000
23OrlandoFL1253000Miami - San Francisco - Route1Stop310-30 11:27:5810-30 11:27:58386.309025
34New OrleansLA2255000Miami - San Francisco - Route1Stop410-30 20:49:4410-30 20:49:441028.402013
45HoustonTX4835000Miami - San Francisco - Route1Stop510-31 02:23:1510-31 02:23:15560.581917
56El PasoTX4824000Miami - San Francisco - Route1Stop610-31 13:12:3910-31 13:12:391203.096420
67PhoenixAZ0455000Miami - San Francisco - Route1Stop710-31 19:37:5410-31 19:37:54691.807799
78San DiegoCA0666000Miami - San Francisco - Route1Stop811-01 01:16:2111-01 01:16:21570.860072
89Los AngelesCA0644000Miami - San Francisco - Route1Stop911-01 03:55:4011-01 03:55:40206.458328
910San JoseCA0668000Miami - San Francisco - Route1Stop1011-01 09:38:4711-01 09:38:47547.931169
1011San FranciscoCA0667000Miami - San Francisco - Route1Stop1111-01 10:50:2411-01 10:50:2487.292255
1112NoneNoneNoneMiami - San Francisco - Route1Route end1211-01 10:50:2411-01 10:50:240.000000

From the tables above, we can tell if leaving Miami at `03:30:30 AM` (EST), which is `07:30:30 AM` (UTC) of Monday, the expected arrival time in San Francisco would be `03:50:24 AM` (PST), a.k.a. `10:50:24 AM` (UTC).

``````map2 = gis.map('Texas, USA', zoomlevel=4)
map2``````
``````""" draw the route and stops with customized styles, symbologies, and popups
"""
for route in result1['routes_layer'].query():
map2.draw(route.geometry, popup_route, route_symbol)

sequence = 1
for stop in result1['assigned_stops_layer'].query():
if stop.attributes['StopType']=="Route start":
# starting point
symbol = start_symbol
elif stop.attributes['StopType']=="Route end":
# ending point
symbol = end_symbol
else:
# by-passing point
symbol = stops_symbol

map2.draw(stop.geometry,
{"title": "Stop {}".format(sequence),
symbol)
sequence+=1``````
```1620 SW 13th St, Miami, Florida, 33145
1620 SW 13th St, Miami, Florida, 33145
East-West Expy, Orlando, Florida, 32801
302-330 Loyola Ave, New Orleans, Louisiana, 70112
Starbucks
Anny Fashions
124 S Central Ave, Phoenix, Arizona, 85004
Pershing Dr, San Diego, California, 92101
1523 Cambria St, Los Angeles, California, 90017
61 N 6th St, San Jose, California, 95112
550-560 Buena Vista Ave W, San Francisco, California, 94117
550-560 Buena Vista Ave W, San Francisco, California, 94117
```

From the cell output above, we can see that though we are routing from cities to cities, the real logic under the solver is to pick a specific address in the city before the route is computed.

``````item_properties["title"] = "Miami - San Francisco (3)"

item = map2.save(item_properties)``````
``item``
Miami - San Francisco (3)
Route from Miami to San FranciscoWeb Map by portaladmin

In the last section of this guide, we have adopted a different method - `arcgis.features.use_proximity.plan_routes` - in solving the routing problem between ten cities. In doing so, we also explored the two scenarios with output_name specified (which forms a Feature Service to Feature Service user experience), and a more traditional compute/parse/draw approach when output_name is not present.

## What's next?

Part 2 has introduced `arcgis.network.analysis.find_routes` and `arcgis.features.use_proximity.plan_routes` as solvers to the routing problem, how to prepare for data required as input arguments by these solvers, and ways to tabularize, map, and the save the output from solvers.

Now that we have mastered techniques in solving route finding problems, let's proceed to the next topic - generate service areas with `network.analysis` and `features.use_proximity` modules in Part 3.

## References

[1] "Tutorial: Create routes", https://pro.arcgis.com/en/pro-app/help/analysis/networks/route-tutorial.htm, accessed August 10, 2019

[2] "Route analysis layer", https://pro.arcgis.com/en/pro-app/help/analysis/networks/route-analysis-layer.htm, accessed on September 20, 2019

[3] "Find Routes", http://desktop.arcgis.com/en/arcmap/10.3/tools/network-analyst-toolbox/find-routes.htm, accessed on September 5, 2019