How to develop a point-to-point routing application

This topic walks you through the development of an application using the ArcGIS Runtime SDK for WPF. Your application will consume the geoprocessing package created in How to author and publish a geoprocessing model. The application provides users with the ability to specify a route start point and end point on a basemap and determine an optimal route between the selected points on any device to which the application is deployed.

Create a WPF map application

The ArcGIS Runtime SDK for WPF installation includes an integrated development environment (IDE) option to integrate a set of templates with Visual Studio. The following steps assume that this integration option was chosen.

See Installation and setup for additional information.

Complete the following steps to create an ArcGIS Runtime SDK for WPF mapping application using the Visual C# application project template

Steps:
  1. In a location of your choice, create a folder named RoutingExercise.
  2. Start Visual Studio and choose to create a new project.
  3. Locate the ArcGIS Runtime SDK 10.2.5 for WPF Application template in the list of installed templates (Visual C# >Windows > ArcGIS > Runtime SDK for 10.2.5 WPF).
  4. Specify the following project name to ensure consistency with relative paths in steps you will encounter later in this exercise: PointToPointRouting_Example.
  5. For the location, browse to the RoutingExercise folder you created earlier.
  6. Click OK to create the project.

Verify the project's references

The integrated ArcGIS Runtime SDK for WPF template configures the necessary project references. Complete the following steps to examine the project's references and verify that the necessary assemblies are referenced by your project:

Steps:
  1. In Visual Studio Solution Explorer, expand References.
  2. Confirm that both ESRI.ArcGIS.Client and ESRI.ArcGIS.Client.Local are listed as project references. To add these references if they're not listed, follow these steps:
    1. Right-click References in Visual Studio Solution Explorer.
    2. Select Add Reference.
    3. Browse to the ESRI.ArcGIS.Client.dll and ESRI.ArcGIS.Client.Local.dll assemblies in <install folder>\SDK\bin.
  3. NoteNote:

    The ESRI.ArcGIS.Client assembly supports run time initialization, map layers, geometry, and workflows for querying, identifying, and editing geospatial features. The ESRI.ArcGIS.Client.Local assembly supports the creation of services from maps and data stored locally on the device running your application.

Include a basemap as a local tiled layer

Complete the following steps to modify your application's Extensible Application Markup Language (XAML) within Visual Studio to include a tile package as a basemap layer. When the runtime is initialized, a service is created locally on the device running your application to consume the contents of the tile package providing users with a basemap without requiring an on-line connection to an external server.

Steps:
  1. In Visual Studio Solution Explorer, double-click MainWindow.xaml to open/display the XAML file.
  2. Locate the <esri:ArcGISLocalTiledLayer> tag in the XAML code. The entire Local Tiled Basemap Layer code block is commented-out in the template code, as shown in the following sample code:
  3. <!-- Local Tiled Basemap Layer -->
    <!--<esri:ArcGISLocalTiledLayer ID="Topographic USA" Path="C:\Program Files (x86)\ArcGIS SDKs\WPF10.2.5\sdk\Samples\Data\TPKs\Topographic.tpk"/>-->
    
  4. Change the system path in the code block to address your local copy of the SanFrancisco.tpk tile package, found with the sample data installed when you installed the software development kit (SDK).
  5. Change the ID in the code block to reflect the data layer.
  6. Uncomment the code block to include the basemap layer in your application as shown in the following sample code:
  7. <!-- Local Tiled Basemap Layer -->
                <esri:ArcGISLocalTiledLayer ID="San Francisco Basemap" Path="C:\Program Files (x86)\ArcGIS SDKs\WPF10.2.5\sdk\Samples\Data\TPKs\SanFrancisco.tpk"/>
    
    NoteNote:

    The previous code sample configures your application to use local data for its basemap. Using the fully-qualified path to the tile package in the ArcGIS Runtime SDK for WPF sample data will work for the purposes of this exercise; however, to license and deploy this application, you need to change this to a path relative to your application's executable and deploy the tile package along with your compiled application's executable.

Add a graphics layer

A graphics layer provides your application's users with a set of simple vector graphics with which they can interact. The route start point, end point, and the route itself will all be rendered as simple vector graphics. Complete the following steps to modify your application's XAML within Visual Studio to include a graphics layer:

Steps:
  1. In Visual Studio Solution Explorer, double-click MainWindow.xaml to open/display the XAML file.
  2. Locate the Graphics Layer code block in the XAML code.
  3. NoteNote:

    This code block is part of the commented-out code in the coding template.

  4. Uncomment the entire code block to include the graphics layer in your application as shown in the following code sample:
  5. <esri:GraphicsLayer ID="Example Graphic">
                    <esri:Graphic>
                        <esri:Graphic.Symbol>
                            <esri:SimpleMarkerSymbol Style="Circle" Color="Red"/>
                        </esri:Graphic.Symbol>
                        <esri:Graphic.Geometry>
                            <esri:MapPoint  X="0" Y="0" >
                                <esri:MapPoint.SpatialReference>
                                    <esri:SpatialReference WKID="102100"/>
                                </esri:MapPoint.SpatialReference>
                            </esri:MapPoint>                    
                            </esri:Graphic.Geometry>                  
                    </esri:Graphic>
                </esri:GraphicsLayer>
    
  6. Using the following syntax, add a name attribute to the esri:GraphicsLayer object and change the identifier of the object to remove "Example" from the identifier"
  7. <!--Graphics Layer-->
                <esri:GraphicsLayer ID="graphicsLayer" x:Name="_graphicsLayer">
    

Configure SimpleMarkerSymbol and SimpleLineSymbol elements

Configuring symbol elements, such as the SimpleMarkerSymbol and SimpleLineSymbol in the Window Resources collection, allows you to control how your application symbolizes user input locations and the identified route between those locations. Complete the following steps to configure these elements:

Steps:
  1. In Visual Studio Solution Explorer, double-click MainWindow.xaml to open/display the XAML file.
  2. Locate the <Window> tag and <Grid> tag toward the top of the XAML code.
  3. After the <Window> tag's properties, before the opening <Grid> tag, insert the following highlighted code block:
  4. <Window.Resources>
            <esri:SimpleMarkerSymbol x:Key="PointSymbol" Color="DarkViolet" Size="15" Style="Diamond"></esri:SimpleMarkerSymbol>
            <esri:SimpleLineSymbol x:Key="LineSymbol" Color="DarkViolet" Width="5" Style="Solid"></esri:SimpleLineSymbol>
        </Window.Resources>
    

Building the application's user interface

The user interface (UI) for your point-to-point routing application will have three buttons. One will allow the user to specify the route's starting point; the second will be used to establish the route's end point, and the third button will execute the geoprocessing task to solve the problem and identify an optimal route between the designated points.

First you'll define the button objects. Each button will require an event handler that will perform an action when the button is clicked. After you define the buttons, you'll modify the XAML to define a click event binding and create a stub for each click event handler. You'll also set an initial enabled state for the Solve button within the button object's XAML declaration.

Complete the following steps to define the UI for your application:

Steps:
  1. In Visual Studio Solution Explorer, double-click MainWindow.xaml to open/display the XAML file.
  2. Locate the closing tag </esri:Map> toward the bottom of the XAML code.
  3. Insert the following XAML code between the closing <Map> tag and closing <Grid> tag:
  4. <StackPanel Orientation="Vertical" HorizontalAlignment="Left">
                <Button Content="Start Point" VerticalAlignment="Top" Name="StartButton" Margin="5,5,5,0"/>
                <Button Content="End Point" VerticalAlignment="Top" Name="EndButton" Margin="5,5,5,0"/>
                <Button Content="Solve" VerticalAlignment="Top" Name="SolveButton" Margin="5,5,5,0"/>
            </StackPanel>
    

    As you insert the XAML code, the IDE presents you with the opportunity to auto-complete the code you're adding. As you type the assignment operator following a property, a pair of quotation marks automatically display, and you are able to quickly enter the name for the property.

    As you insert the following code, you'll notice IntelliSense displaying a <New Event Handler> option. You might also see help text "Bind an event to a newly created method" for the coding assistance option. Ignore the coding assistance and focus on making the changes in the next step.

  5. Add the click event bindings to the button configurations as shown in the following code sample. As you insert the following code, you'll notice intellisense displaying a <New Event Handler> option. Click <New Event Handler> for Visual Studio to automatically create the event based on the name of the button.
  6. <StackPanel Orientation="Vertical" HorizontalAlignment="Left">
                <Button Content="Start Point" VerticalAlignment="Top" Name="StartButton" Margin="5,5,5,0" Click="StartButton_Click"/>
                <Button Content="End Point" VerticalAlignment="Top" Name="EndButton" Margin="5,5,5,0" Click="EndButton_Click"/>
                <Button Content="Solve" VerticalAlignment="Top" Name="SolveButton" Margin="5,5,5,0" Click="SolveButton_Click" IsEnabled="False"/>
            </StackPanel>
    

  7. Hover the mouse pointer over the Click event binding for the StartButton_Click and right-click to display a context menu.
  8. From the displayed context menu, select Navigate to Event Handler .

    The MainWindow.xaml.cs code file opens. This is the code behind the higher level XAML you use to design and configure your application. Note that Visual Studio has stubbed out the code for the button click event handler.

  9. NoteNote:

    Both the MainWindow.xaml design file and the MainWindow.xaml.cs code behind files are listed in Visual Studio Solution Explorer. Double-click either file to open it for editing. You can also switch between the two by right-clicking in an open space in the editor window. When editing the code behind file, right-click and select View Designer from the context menu. When editing the XAML, right-click and select View Code.

    Each time you right-click and select Navigate to Event Handler, a new method is created in the code behind file.

  10. Use the code assist to create stubs for each button (StartButton, EndButton, and SolveButton) defined in the XAML.
  11. For now, these three stubs are sufficient to allow your application to compile.

Define class member variables and PointType enumeration

There are several different ways you can handle the button click events your application will generate. For this exercise, you'll create an enumeration that you'll use to flag whether a particular button click is a user specifying a route's starting point or the route's ending point. The enumeration declaration is made in the code behind file. First you'll confirm that needed namespace declarations have been made.

Steps:
  1. In the MainWindow.xaml.cs code behind file, confirm that the using directives shown in the following code sample have been added at the top of the file:
  2. using System.Windows;
    using ESRI.ArcGIS.Client;
    using ESRI.ArcGIS.Client.Local;
    using ESRI.ArcGIS.Client.Tasks;
    using ESRI.ArcGIS.Client.Symbols;
    using System.Collections.Generic;
    
    NoteNote:

    If the ESRI.ArcGIS.Client assembly is not currently referenced by your project, the namespace declarations shown in the previous code sample will generate errors when you attempt to compile your project. See the Verify the project's references section for information on adding required assembly references to your project.

  3. In the MainWindow.xaml.cs code behind file, after the class declaration's opening curl brace, just above the default constructor, enter the member variable declarations shown in the following code sample:
  4. public partial class MainWindow : Window
        {
            private Graphic _startPoint = null;
            private Graphic _endPoint = null;
            private Draw _draw = null;
            private PointType _pointType;
    
  5. At the bottom of the MainWindow.xaml.cs code behind file, just before the final curl brace, insert the PointType enumeration declaration shown in the following code sample:
  6. public enum PointType
        {
            Start,
            End
        }
    

Create a LocalGeoprocessingService

Applications you develop using the ArcGIS Runtime SDK for WPF use services to retrieve data from online sources or packages you prepare using ArcGIS for Desktop. Packages spin up services locally. These services are instantiated at run time on the device running your application.

Complete the following steps to add code to the project's MainWindow.xaml.cs code file. The code you're adding declares a local geoprocessing service, provides a path to a local geoprocessing package containing an implemented point-to-point routing model, and starts the service asynchronously.

Steps:
  1. Open the MainWindow.xaml.cs code behind file.
  2. Add a declaration for a LocalGeoprocessingService immediately following the MainWindow class's other private member variables.
  3. private LocalGeoprocessingService _localGPService = null;
    
  4. Copy the following code into the class's default constructor immediately following the InitializeComponent() invocation:
  5. _localGPService = new LocalGeoprocessingService(@"C:\Program Files (x86)\ArcGIS SDKs\WPF10.2.5\sdk\Samples\Data\GPKs\Routing\Route.gpk", GPServiceType.Execute);
                _localGPService.StartAsync((callback) =>
                    {
                        if (callback.Error == null)
                        {
                            SolveButton.IsEnabled = true;
                        }
                        else
                        {
                            MessageBox.Show("Error starting routing service");
                        }
                    });
            }
    

    Notice that the LocalGeoprocessingService was declared with a GPServiceType of Execute . This is the recommended service type for services that are known to execute quickly (for example, less than 10 seconds). This configuration will handle service requests synchronously blocking the application from executing further tasks.

    Also, notice the lambda expression that's employed when invoking the service's StartAsync method. Using the lambda expression allows an inline response to completion of the service startup. In the previous code sample, the application's Solve button is enabled once the service starts successfully. For more information on lambda expressions, see the Anonymous Functions topic in the C# Programming Guide in the MSDN Library.

Create the Draw object and implement the DrawComplete event

An ESRI.ArcGIS.Client.Draw object is used to capture user clicks that designate both the route's start and end points. The Draw object's DrawComplete event will disable the Draw object, determine whether a start or end point is being specified, retrieve the point symbol from the Window Resources, and set the geometry for the designated point before adding the graphic to the graphics layer.

Complete the following steps to instantiate and configure the Draw object declared previously as a private member variable of the MainWindow class:

Steps:
  1. Open the MainWindow.xaml.cs code behind file.
  2. Copy the following code into the class's default constructor beneath the code that creates the LocalGeoprocessingService:
  3. _draw = new Draw(_map);
                _draw.DrawMode = DrawMode.Point;
                _draw.DrawComplete += (senderObject, drawEventArgs) => { };
    
  4. Copy the following code into the empty brackets of the DrawComplete event's lambda expression:
  5. _draw = new Draw(_map);
                _draw.DrawMode = DrawMode.Point;
                _draw.DrawComplete += (senderObject, drawEventArgs) => 
                {
                    _draw.IsEnabled = false;
                    switch (_pointType)
                    {
                        case PointType.Start:
                        _startPoint = new Graphic();
                        _startPoint.Symbol = this.Resources["PointSymbol"] as SimpleMarkerSymbol;
                        _startPoint.Geometry = drawEventArgs.Geometry;
                        _graphicsLayer.Graphics.Add(_startPoint);
                        break;
                        case PointType.End:
                        _endPoint = new Graphic();
                        _endPoint.Symbol = this.Resources["PointSymbol"] as SimpleMarkerSymbol;
                        _endPoint.Geometry = drawEventArgs.Geometry;
                        _graphicsLayer.Graphics.Add(_endPoint);
                        break;
                    }
                };
    

Implement event handlers for button click events

Earlier in this exercise, you created stubs for event handlers associated with the click events for buttons declared in the XAML code. The event handler stubs are in the MainWindow.xaml.cs code behind file. See the Building your application's user interface section for more information.

When a user clicks the application's Start Point button, the application enables its internal Draw object and uses the PointType enumeration to set a PointType member to PointType.Start. Clicking the End Point button also enables the Draw object, but the PointType member variable's value is set to PointType.End.

When a user clicks the application's Solve button, the application submits the route's designated start and end points to the LocalGeoprocessingService and prepares handlers for both a successful route determination and a service's failure to determine a route between the provided points. See the Create a LocalGeoprocessingService section for more information on instantiating and starting the local geoprocessing service.

Complete the following steps to implement the StartButton_Click, EndButton_Click, and SolveButton_Click event handlers:

Steps:
  1. Open the MainWindow.xaml.cs code behind file.
  2. Locate the StartButton_Click method and insert the following code between the curl braces:
  3. private void StartButton_Click(object sender, RoutedEventArgs e)
            {
                _draw.IsEnabled = true;
                _pointType = PointType.Start;
            }
    
  4. Locate the EndButton_Click method and insert the following code between the curl braces:
  5. private void EndButton_Click(object sender, RoutedEventArgs e)
            {
                _draw.IsEnabled = true;
                _pointType = PointType.End;
            }
    
  6. Locate the SolveButton_Click method and insert the following code between the curl braces:
  7. private void SolveButton_Click(object sender, RoutedEventArgs e)
            {
                if (null == _startPoint || null == _endPoint)
                    return;
                           
                //Create a new Geoprocessor object
                Geoprocessor gp = new Geoprocessor(_localGPService.UrlGeoprocessingService + "/Route");
                
                //Create a new List of type GPParameter to hold the parameters for the service
                List<GPParameter> parameters = new List<GPParameter>();
               
                //Create a new GP FeaturerecordSetLayer with parameter name
                GPFeatureRecordSetLayer gpFeatureRecordSetLayer = new GPFeatureRecordSetLayer("Input_Locations");
    
                //Add the start and end points to the GPFeatureRecordSetLayer
                gpFeatureRecordSetLayer.FeatureSet.Features.Add(_startPoint);
                gpFeatureRecordSetLayer.FeatureSet.Features.Add(_endPoint);
    
                //Add the GpFeatureRecordSetLayer to the parameter list
                parameters.Add(gpFeatureRecordSetLayer);
            }
    

    The implementation for the SolveButton_Click event handler is almost complete. The code you added creates a Geoprocessor object with a uniform resource locator (URL) of a LocalGeoprocessingService. The code also prepares a GPFeatureRecordSetLayer, adds the user's specified route start point and end point features, and adds the record set to a list of geoprocessing parameters.

    In the following steps, you'll complete the SolveButton_Click event handler's implementation by passing the prepared geoprocessing parameters to the geoprocessor and signal it to begin asynchronous execution. But first, you'll wire event handling for events reported by the geoprocessor.

  8. Enter the following code after the addition of the gpFeatureRecordSetLayer to the parameters list. This code wires event handling for events reported by the geoprocessor.
  9. //Add the GpFeatureRecordSetLayer to the parameter list
                parameters.Add(gpFeatureRecordSetLayer);
                gp.ExecuteCompleted += (senderObject, gpExecuteCompleteEventArgs) =>
                    {
                    };
                gp.Failed += (senderObject, taskFailedEventArgs) =>
                    {
                    };
    
    Now enter code to loop through the output parameters and identify the GPFeatureRecordSetLayer containing the computed route line features. The following code will loop and add each polyline graphic to the map's graphics layer symbolizing the line segment according to the Windows Resources configured in the XAML code.
  10. Insert the following code between the curl braces beneath gp.ExecuteCompleted to implement an event handler for a successful process:
  11. gp.ExecuteCompleted += (senderObject, gpExecuteCompleteEventArgs) =>
                    {
                        foreach (GPParameter outputParameter in gpExecuteCompleteEventArgs.Results.OutParameters)
                        {
                            if (outputParameter is GPFeatureRecordSetLayer)
                            {
                                GPFeatureRecordSetLayer gpResults = outputParameter as GPFeatureRecordSetLayer;
                                foreach (Graphic graphic in gpResults.FeatureSet.Features)
                                {
                                    if (graphic.Geometry is ESRI.ArcGIS.Client.Geometry.Polyline)
                                    {
                                        graphic.Symbol = this.Resources["LineSymbol"] as SimpleLineSymbol;
                                        _graphicsLayer.Graphics.Add(graphic);
                                    }
                                }
                            }
                        }
    
                    };
                gp.Faile
    
  12. Insert the following code between the curl braces beneath gp.Failed to implement an event handler for a processing failure. In this case, you handle the error by alerting the user of the error.
  13. gp.Failed += (senderObject, taskFailedEventArgs) =>
                    {
                        MessageBox.Show("Execute Error \n" + taskFailedEventArgs.Error.Message, "Geoprocessing Error", MessageBoxButton.OK, MessageBoxImage.Error);
                    };
    
  14. Following the event handling for processing failure, immediately before the closing curl brace for the SolveButton_Click method, insert a line of code to submit the geoprocessing task to the geoprocessor executing the task asynchronously.
  15. MessageBox.Show("Execute Error \n" + taskFailedEventArgs.Error.Message, "Geoprocessing Error", MessageBoxButton.OK, MessageBoxImage.Error);
                    };
    
                gp.ExecuteAsync(parameters);
            }
    

Run your application and examine the task parameters

If you've completed each step in this exercise, you'll have a functional point-to-point application that can be licensed and deployed to any Microsoft Windows device supporting applications developed for WPF.

Steps:
  1. Run your application from within Visual Studio.
  2. Zoom to the center of San Francisco and click the Start Point button.
  3. Click a point on the map display to designate a route start point.
  4. Click the End Point button and select a route end point on the map.
  5. Click the Solve button and wait a few seconds for the geoprocessing task to complete.

The geoprocessing task executes and the resulting route is added to the map. Depending on the points you chose, your route will look something like the following screen shot:

1/27/2015