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 so 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 (for example, speed limit, traffic, pipe diameter, and so on) can be taken into account.

To enable routing, use the RouteTask class in the ArcGIS API for Windows Phone. This class requires that its Url property reference the REST endpoint of a network analysis service. To create such a service, you need to create a network dataset using Network Analyst, then publish that dataset to ArcGIS Server. Alternatively, to calculate driving routes in the U.S., 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 for 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 tasks, initialize the Route task either in a XAML resource dictionary or .NET code (that is, code-behind), then execute and process the task's results in code. The Route task does not contain 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.

TipTip:

You'll typically define an interface for the task's input and output primarily in XAML. For instance, you may use a Map and a Button that initializes a Draw object to accept input points, and use a GraphicsLayer and a StackPanel to display the output route and directions. For examples of implementing a Route task's input and output interfaces, see the samples in the Network section of the Interactive SDK. For step-by-step walkthroughs of implementing similar functionality, see Find task, Query task, and Identify task.

The following sections walk you through an example of a Route task:

Initializing the task

To initialize a Route task, 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 Discovering services 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 need to specify the Stops parameter, as this determines the locations between which a route is calculated. Stops can be defined as a FeatureSet, GraphicsLayer, or any other type that implements IEnumerable<Graphic>.

You may also want 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 the following:

  • ReturnRoutes—Specifies whether route geometry is returned.
  • ReturnDirections—Specifies whether directions are returned.
  • FindBestSequence—Determines whether to visit stops in the order specified (false) or to optimize 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.

The following code example shows the initialization of a RouteParameters object with stops and barriers defined by GraphicsLayers. Properties are specified so 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. The ReturnRoute property is not explicitly specified because it has a default value of true.

The 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've initialized a RouteParameters object with the desired input (detailed in the previous section), calculating the route simply requires a call to the SolveAsync method, as shown in the final line of the following code:

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. To get those results, you need to implement a handler for this event.

The following code demonstrates declaring such a handler using a lambda expression.

NoteNote:

The handler needs to be attached before initiating the route operation with SolveAsync. This ensures 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 following code builds on the SolveCompleted handler previously declared to retrieve the route, apply a symbol to it, and add it to a graphics layer. This 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 in 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 following code 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 formatted string for each step of the directions, you need to display this information in your application. In the Windows Phone, there are many ways of doing this. The approach shown in the following code is to create a list of the directions and bind them to a templated ListBox. This 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. As with the SolveCompleted handler, this handler needs to be attached before initiating the route operation with SolveAsync. This, again, ensures 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);
    }
    

1/23/2012