
The Route task allows you to easily implement routing in your Silverlight GIS 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 Silverlight API'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.
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 rest of this document will focus on manipulating the task in code. Note, however, that you will typically define an interface for the task's input and output primarily in XAML. For instance, you might use a Map and a Button that initializes a Draw object to accept input points and use a GraphicsLayer and StackPanel to diplay the output route and directions. For examples of implementing a Route task's input and output interfaces, refer to the routing samples in the Interactive SDK. For step-by-step walkthroughs of implementing similar functionality, refer to the Query task, Find task, and Identify task topics.
All the code shown below is written in C#.
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");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 optmized 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. The snippet 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
};
Once you have initialized a RouteParameters object with the desired input, calculating the route simply requires a call to the SolveAsync method, as shown below:
routeTask.SolveAsync(routeParameters);
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. Note that we attach the handler before initiating the route operation. 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 and a Map control named MyMap are 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);
};
routeTask.SolveAsync(routeParameters);
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. Note that, to keep the example simple, the formatting used is very basic and null checks are omitted. Refer to the Driving Directions sample in the Interactive SDK for a more robust implementation.
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
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++;
}
};
routeTask.SolveAsync(routeParameters);
Once you have a nicely formatted string for each step of directions, all that's left is to display this somewhere in your application. Silverlight provides many ways of doing this. One simple approach, shown below, is to create a TextBlock containing the text and add it to a StackPanel. Note the code assumes that a StackPanel named DirectionsStackPanel 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
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"));
}
// Add the current step to the directions stack panel
DirectionsStackPanel.Children.Add(new TextBlock() {
Text = text.ToString(),
Margin = new Thickness(4)
});
i++;
}
};
routeTask.SolveAsync(routeParameters);