Route task

The Route task allows you to easily implement routing in your ArcGIS API for Windows Phone applications. With the Route task, you can retrieve routes and directions between a set of input features. Since the Route task is built on the advanced capabilities of ArcGIS Server's network analysis services, it offers many options and can take many parameters into account when solving a route. For instance, routes can be solved such that the stops are visited in the optimal order (as opposed to the order defined); barriers can be defined that must be routed around; and impedance attributes (e.g. speed limit, traffic, pipe diameter, etc.) can be accounted for.

To enable routing, you will use the ArcGIS API for Window Phone's RouteTask class. This class requires that its Url property reference the REST endpoint of a network analysis service. To create such a service, you will need to create a network dataset using Network Analyst, then publish that dataset to ArcGIS Server. Alternatively, to calculate driving routes in the US, Canada, or Europe, you can use ArcGIS Online's routing services. These services can be used without charge up to 5,000 times per year for non-commercial purposes, or by subscription for commercial purposes or more than 5,000 annual uses.

Samples of Route tasks are available in the Network section of the Interactive SDK.

Creating a Route task

As with all the tasks, you will initialize the Route task either in a XAML resource dictionary or .NET code (i.e. code-behind), then execute and process the task's results in code. The Route task does not contain any visual components, so it cannot be interacted with via XAML. This topic works with the Route task in code, assuming that the input and output interfaces are already defined. Typically, the task's input and output interfaces would be defined primarily in XAML. For instance, you might use a Map and a button that initializes a Draw object to accept input points, and then use a GraphicsLayer and a StackPanel to display the output route and directions. For step-by-step implementation of similar input/output interface, see the Find task, Query task, and Identify task topics. For examples of routing applications, complete with input and output interfaces, see the Network section of the Interactive SDK.

This topic covers the following Route task steps:

  1. Initializing the task
  2. Specifying the Route task's input parameters
  3. Executing the task
  4. Handling and displaying results
  5. Handling execution errors

Initializing the task

To initialize a Route task, simply declare a RouteTask object, instantiate it with the new keyword, and pass the URL of a routing layer's REST endpoint to the constructor. To find the URL, you can use the ArcGIS Services Directory. See the Discovering services topic for more information. This example uses the Route layer of the ESRI_Route_NA service.

RouteTask routeTask = new RouteTask("http://tasks.arcgisonline.com/ArcGIS/" + 
    "rest/services/NetworkAnalysis/ESRI_Route_NA/NAServer/Route");

Specifying the Route task's input parameters

The Route task's execution method, SolveAsync, takes a RouteParameters object as input. At a minimum, you will need to specify the Stops parameter, as this determines the locations between which a route will be calculated. Stops may be defined as a FeatureSet, GraphicsLayer, or any other type that implements IEnumerable<Graphic>. Often times, you may also wish to define the Barriers parameter, which defines locations that must be routed around. This parameter is of the same type as Stops. Other commonly used boolean parameters include ReturnRoutes, which specifies whether route geometry is returned, ReturnDirections, which specifies whether directions are returned, and FindBestSequence, which determines whether to visit stops in the order specified (false) or that optimizes the route (true). When specifying optimized route calculation (FindBestSequence = true), you can exclude the first and last stops from being re-ordered by setting the PreserveFirstStop and PreserveLastStop properties to true.

Below is an example of initializing a RouteParameters object with stops and barriers defined by GraphicsLayers. Properties are specified such that the route will be optimized, the first and last stops will be preserved, and the SolveAsync operation will return both geometry and directions for the calculated route. Note that the ReturnRoute property is not explicitly specified because it has a default value of true. This code assumes that there is a Map control in the current scope named MyMap that contains GraphicsLayers with IDs of MyStopsGraphicsLayer and MyBarriersGraphicsLayer.

GraphicsLayer stopsGraphicsLayer = MyMap.Layers["MyStopsGraphicsLayer"] as GraphicsLayer;
GraphicsLayer barriersGraphicsLayer = MyMap.Layers["MyBarriersGraphicsLayer"] as GraphicsLayer;
RouteParameters routeParameters = new RouteParameters(){
    Stops = stopsGraphicsLayer,
    Barriers = barriersGraphicsLayer,
    ReturnDirections = true,
    FindBestSequence = true,
    PreserveFirstStop = true,
    PreserveLastStop = true
};

Executing the task

Once you have initialized a RouteParameters object with the desired input (detailed in the previous step), calculating the route simply requires a call to the SolveAsync method, as shown in the final line of code below:

RouteParameters routeParameters = new RouteParameters(){
    Stops = stopsGraphicsLayer,
    Barriers = barriersGraphicsLayer,
    ReturnDirections = true,
    FindBestSequence = true,
    PreserveFirstStop = true,
    PreserveLastStop = true
};

routeTask.SolveAsync(routeParameters);

Handling and displaying results

Of course, executing a routing operation alone isn't very useful; the whole point of the operation is to get its results. The Route task passes a route operation's results to the SolveCompleted event, which fires whenever a route operation is finished. So to get those results, you need to implement a handler for this event. The code below demonstrates declaring such a handler using a lambda expression. The handler needs to be attached before initiating the route operation with SolveAsync, shown in the previous section. This is to ensure that the handler is attached before the operation completes.

routeTask.SolveCompleted += (source, args) =>{};

routeTask.SolveAsync(routeParameters);

On completion of a route operation, the Route task passes a RouteEventArgs object to SolveCompleted. This object contains the operation's results in the RouteResults property. The route's geometry is returned as a Graphic in the Route property of RouteResults. The code below builds on the SolveCompleted handler declared above to retrieve the route, apply a symbol to it, and add it to a graphics layer. The code assumes that a LineSymbol named RouteSymbol, a Map control named MyMap, and a GraphicsLayer named MyRouteGraphicsLayer are all available in the current scope.

routeTask.SolveCompleted += (source, args) =>
{
    // Get the route and apply a symbol to it
    RouteResult routeResult = args.RouteResults[0];
    routeResult.Route.Symbol = LayoutRoot.Resources["RouteSymbol"] as Symbol;

    // Add the route to a graphics layer
    GraphicsLayer graphicsLayer = MyMap.Layers["MyRouteGraphicsLayer"] as GraphicsLayer;
    graphicsLayer.Graphics.Add(routeResult.Route);
};

Directions are returned in the Directions property as a DirectionsFeatureSet. Each graphic contained within this feature set represents one step in the directions. The graphic's geometry is the segment of the route covered by the step, while the graphic's "text," "length," and "time" attributes store the step's description, distance, and estimated travel time. The code below steps through the directions, retrieving and formatting the description, distance, and travel time of each. To keep this example simple, the formatting used is very basic and null checks are omitted.

routeTask.SolveCompleted += (source, args) =>
{
    // Get the route and apply a symbol to it
    RouteResult routeResult = args.RouteResults[0];
    routeResult.Route.Symbol = LayoutRoot.Resources["RouteSymbol"] as Symbol;

    // Add the route to a graphics layer
    GraphicsLayer graphicsLayer = MyMap.Layers["MyRouteGraphicsLayer"] as GraphicsLayer;
    graphicsLayer.Graphics.Add(routeResult.Route);

    int i = 1;
    // Loop through each step of the directions
    foreach (Graphic graphic in routeResult.Directions)
    {
        // Get the current step's description and format as <number>. <description>
        // (e.g. "1. Turn right at...")
        System.Text.StringBuilder text = new System.Text.StringBuilder();
        text.AppendFormat("{0}. {1}", i, graphic.Attributes["text"]);
        if (i > 1 && i < routeResult.Directions.Features.Count)
        {
            // Append distance and duration
            decimal distance = (decimal)graphic.Attributes["length"];
            text.AppendFormat("   {0} miles", distance.ToString("#0.00"));
            decimal time = (decimal)graphic.Attributes["time"];
            text.AppendFormat(", {0} minutes", time.ToString("#0"));
        }
        i++;
    }
};

Once you have a nicely formatted string for each step of directions, all that's left is to display this somewhere in your application. In the Windows Phone there are many ways of doing this. One simple approach, shown below, is to create a list of the directions and bind them to a templated ListBox. The code assumes that a ListBox named DirectionsListBox is available in the current scope.

routeTask.SolveCompleted += (source, args) =>
{
    // Get the route and apply a symbol to it
    RouteResult routeResult = args.RouteResults[0];
    routeResult.Route.Symbol = RouteSymbol;

    // Add the route to a graphics layer
    GraphicsLayer graphicsLayer = MyMap.Layers["MyRouteGraphicsLayer"] as GraphicsLayer;
    graphicsLayer.Graphics.Add(routeResult.Route);

    int i = 1;
    // Loop through each step of the directions
    List<string> directions = new List<string>();
    foreach (Graphic graphic in routeResult.Directions)
    {
        // Get the current step's description and format as <number>. <description>
        // (e.g. "1. Turn right at...")
        System.Text.StringBuilder text = new System.Text.StringBuilder();
        text.AppendFormat("{0}. {1}", i, graphic.Attributes["text"]);
        if (i > 1 && i < routeResult.Directions.Features.Count)
        {
            // Append distance and duration
            decimal distance = (decimal)graphic.Attributes["length"];
            text.AppendFormat("   {0} miles", distance.ToString("#0.00"));
            decimal time = (decimal)graphic.Attributes["time"];
            text.AppendFormat(", {0} minutes", time.ToString("#0"));
        }

        directions.Add(text.ToString());

        i++;
    }

    DirectionsListBox.DataContext = directions;
};

routeTask.SolveAsync(routeParameters);

Handling execution errors

Tasks do not always Execute as expected, and failures also need to be handled.

  1. Specify a handler for the task's Failed event, which fires when there is a problem executing the task. Like the SolveCompleted handler, this handler needs to be attached before initiating the route operation with SolveAsync. This is to ensure that the handler is attached before the operation completes.
    routeTask.Failed += new EventHandler<TaskFailedEventArgs>(routeTask_Failed);
    
    routeTask.SolveAsync(routeParameters);
    
  2. Declare a handler for the Route task's Failed event. This handler will be invoked if there is a problem with executing a find operation.
    private void routeTask_Failed(object sender, TaskFailedEventArgs e)
    {
    }
    
  3. Notify the user of the problem with a MessageBox.

    private void routeTask_Failed(object sender, TaskFailedEventArgs e)
    {
        MessageBox.Show("Routing failed: " + e.Error);
    }
    


12/1/2010