Overview

You will learn: how to find the optimal route and directions offline for multiple stops with the ArcGIS Online Network Analytics Route service and StreetMap Premium.

StreetMap Premium provides high-quality data optimized for cartographic map display, geocoding, and routing. StreetMap Premium data is delivered via mobile map packages (.mmpk). These can be loaded on a device and used for routing and geocoding while offline. You can use a route task and the SolveRouteAsync method to find routes. RouteTask accepts "stop" locations and returns a route with directions. Once you have the results you can add the route to a map, display the turn-by-turn directions, or otherwise integrate them into your application.

In this lab you will learn how to use a transportation network from StreetMap Premium to calculate an optimal route between two stops. Stop locations will be selected with taps on the map.

Before you begin

Complete or review labs Add layers to map offline, Display point, line, and polygon graphics, and Get a route and directions as you will repeat some parts of those labs here.

Steps

Create a new ArcGIS Runtime App Visual Studio Project

  1. Start Visual Studio.

  2. Choose File > New > Project and select the ArcGIS Runtime Application (WPF) template for Visual C#.

Declare variables and using statements

  1. Add the following using statements at the top of your MapViewModel.cs code module:

    using System.Windows.Media;
    using Esri.ArcGISRuntime.Geometry;
    using Esri.ArcGISRuntime.Mapping;
    using Esri.ArcGISRuntime.Symbology;
    using Esri.ArcGISRuntime.Tasks.NetworkAnalysis;
    using Esri.ArcGISRuntime.UI;
    using Esri.ArcGISRuntime.UI.Controls;
    
  2. Your app will track the start and end points (stops) for the route and display the route geometry using a GraphicsOverlay. A GraphicsOverlay manages the display of in-memory graphics on a MapView. Graphics overlays may contain several graphics of any geometry type and are rendered on top of everything else in the view. A map view can also contain more than one graphics overlay, managed by the GraphicsOverlays collection property of the MapView. To learn more about graphics overlay visit the guide topic or review the Display point, line, and polygon graphics lab.

    private Map _map;
    private MapView _mapView;
    private double _latitude = 34.05293;
    private double _longitude = -118.24368;
    private double _scale = 180000;
    private GraphicsOverlay _graphicsOverlay;
    private MapPoint _startPoint;
    private MapPoint _endPoint;
    
  3. Your app will use a TransportationNetworkDataset for routing. Create a field in your view model to refer to the transportation network from the StreetMap Premium map package:

    private TransportationNetworkDataset _transportationNetwork;
    

Load the network from StreetMap Premium

Your app will need to either include the StreetMap Premium .mmpk files, or be able to download them on demand. For this lab, the data will be included in the Visual Studio project.

  1. Download the California package from Downloads. Add the downloaded .mmpk to your project by right-clicking the project and selecting Add > Existing Item. Right-click the item and select Properties. Set the build action to Content and "Copy to Output Directory" to Copy if Newer. This will ensure that the file is in a predictable location when you run the app.

  2. Add a method to the MapViewModel class for preparing the offline transportation network for use:

    public async Task ConfigureStreetMapPremium()
    {
        // load transportation network
    }
    
  3. In the ConfigureStreetMapPremium method, load the mobile map package:

    // Get the path to the map package
    string path = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "California.mmpk");
    
    try
    {
        // Load the package
        MobileMapPackage streetMapPremiumPackage = await MobileMapPackage.OpenAsync(path);
    }
    catch (Exception ex)
    {
        // Handle any exception
        MessageBox.Show(ex.Message, "Error opening package");
        return;
    }
    
  4. In the ConfigureStreetMapPremium method, within the 'try' block, load the map and then the transportation network:

    try
    {
        // Load the package
        MobileMapPackage streetMapPremiumPackage = await MobileMapPackage.OpenAsync(path);
        // Get the first map
        Map caliMap = streetMapPremiumPackage.Maps.First();
    
        // Load the map; transportation networks aren't available until the map is loaded
        await caliMap.LoadAsync();
    
        // Get the first network
        _transportationNetwork = caliMap.TransportationNetworks.First();
    }
    
  5. Set the viewpoint for the map and load it:

    // Set the initial viewpoint
    caliMap.InitialViewpoint = new Viewpoint(_latitude, _longitude, _scale);
    
    // Update the map in use
    this.Map = caliMap;
    

Connect the map view

  1. You need a reference to the MapView in order to add a GraphicsOverlay to it. Open the source file MainWindow.xaml and update the MapView to give it a name. This allows you to get a reference to it in code.

    <esri:MapView x:Name="EsriMapView" Map="{Binding Map, Source={StaticResource MapViewModel}}">
    </esri:MapView>
    
  2. In the code-behind source file MainWindow.xaml.cs, create a variable in the MainWindow class to hold a reference to the MapViewModel associated with the page:

    private MapViewModel _mapViewModel;
    
  3. Add code to the MainWindow constructor to set a reference to MapView on MapViewModel:

    public MainWindow()
    {
        InitializeComponent();
    
        // **Add this code**
        _mapViewModel = this.FindResource("MapViewModel") as MapViewModel;
        _mapViewModel.MapView = EsriMapView;
        // **
    }
    
  4. Finally, add code to MainWindow's constructor to call the ConfigureStreetMapPremium method once the window has loaded:

    // Set up streetmap data once the WPF window has loaded
    this.Loaded += async (o, e) => {
        await _mapViewModel.ConfigureStreetMapPremium();
    };
    

Add helper functions

  1. Create a set function to set a reference to the map view and another function to create a graphics overlay only if it has not been created.

    public MapView MapView
    {
        set { _mapView = value; }
    }
    
    private void SetGraphicsOverlay()
    {
        if (_mapView != null && _graphicsOverlay == null)
        {
            _graphicsOverlay = new GraphicsOverlay();
            _mapView.GraphicsOverlays.Add(_graphicsOverlay);
        }
    }
    

Track user route request

  1. Add new methods to MapViewModel to create marker symbols representing the start and end locations of the route request from user mouse clicks (or screen taps).

    private void SetMapMarker(MapPoint location, SimpleMarkerSymbolStyle pointStyle, Color markerColor, Color markerOutlineColor)
    {
        float markerSize = 8.0f;
        float markerOutlineThickness = 2.0f;
        SimpleMarkerSymbol pointSymbol = new SimpleMarkerSymbol(pointStyle, markerColor, markerSize);
        pointSymbol.Outline = new SimpleLineSymbol(SimpleLineSymbolStyle.Solid, markerOutlineColor, markerOutlineThickness);
        Graphic pointGraphic = new Graphic(location, pointSymbol);
        _graphicsOverlay.Graphics.Add(pointGraphic);
    }
    
    private void SetStartMarker(MapPoint location)
    {
        SetGraphicsOverlay();
        _graphicsOverlay.Graphics.Clear();
        SetMapMarker(location, SimpleMarkerSymbolStyle.Diamond, Color.FromRgb(226, 119, 40), Color.FromRgb(0, 226, 0));
        _startPoint = location;
        _endPoint = null;
    }
    
    private void SetEndMarker(MapPoint location)
    {
        SetMapMarker(location, SimpleMarkerSymbolStyle.Square, Color.FromRgb(40, 119, 226), Color.FromRgb(226, 0, 0));
        _endPoint = location;
        // FindRoute();
    }
    
  2. Add a new method to MapViewModel to interpret user input when the map view is clicked or tapped and call the symbol display functions you coded in the prior step:

    public void MapClicked(MapPoint location)
    {
        if (_startPoint == null)
        {
            SetStartMarker(location);
        }
        else if (_endPoint == null)
        {
            SetEndMarker(location);
        }
        else
        {
            SetStartMarker(location);
        }
    }
    

Handle user input

  1. Return to the code-behind source file MainWindow.xaml.cs to add a handler to respond to taps and/or clicks on the MapView and connect it to the MapClicked method of the MapViewModel:

    private void EsriMapView_GeoViewTapped(object sender, Esri.ArcGISRuntime.UI.Controls.GeoViewInputEventArgs geoViewInputEvent)
    {
        _mapViewModel.MapClicked(geoViewInputEvent.Location);
    }
    
  2. In the MainWindow constructor, wire up the event handler:

    EsriMapView.GeoViewTapped += EsriMapView_GeoViewTapped;
    

At this point you can run and test your app. The app loads the offline map and shows an initial viewpoint centered on Los Angeles. You can click the map once to show a diamond marker. Click a second time and a square marker appears at that location on the map.

Solve the route

  1. Implement code that finds a route between the two stops defined by the user. Return to the MapViewModel class defined in MapViewModel.cs. Add the following FindRoute method:

    private async void FindRoute()
    {
        try
        {
            // Code for the following steps goes here ...
        }
        catch (Exception exception)
        {
            throw (exception);
        }
    }
    
  2. Routing is handled with the RouteTask class. Await the creation of an instance of RouteTask with the TransportationNetwork and get its default route parameters:

    RouteTask solveRouteTask = await RouteTask.CreateAsync(_transportationNetwork);
    RouteParameters routeParameters = await solveRouteTask.CreateDefaultParametersAsync();
    
  3. Add the _startPoint and _endPoint points as stops to the RouteParameters:

    List<Stop> stops = new List<Stop> { new Stop(_startPoint), new Stop(_endPoint) };
    routeParameters.SetStops(stops);
    
  4. Solve the route and select the first returned result:

    RouteResult solveRouteResult = await solveRouteTask.SolveRouteAsync(routeParameters);
    Route firstRoute = solveRouteResult.Routes.FirstOrDefault();
    

Add code to draw the route on the map view

  1. Create a new Graphic to display the route geometry (Polyline) with a SimpleLineSymbol. Add the graphic to the graphics overlay:

    Polyline routePolyline = firstRoute.RouteGeometry;
    SimpleLineSymbol routeSymbol = new SimpleLineSymbol(SimpleLineSymbolStyle.Solid, Colors.BlueViolet, 4.0f);
    Graphic routeGraphic = new Graphic(routePolyline, routeSymbol);
    _graphicsOverlay.Graphics.Add(routeGraphic);
    
  2. Uncomment the call to FindRoute() in the SetEndMarker method.

Congratulations, you're done!

Your map displays a route between two tapped locations. Compare with the completed solution project.

Challenge

Explore route parameters

The RouteParameters class offers a lot of options. Explore some of these options and enhance your app. Can you show turn-by-turn directions?

Get additional information about the route

The solution route, called firstRoute in this example, has additional information accessible through a series of properties. Enhance your app by displaying additional information about the route solution, such as travel time and route length.

int lengthInKm = (int)Math.Round(firstRoute.TotalLength / 1000);
int timeInMinutes = (int)Math.Round(firstRoute.TravelTime.TotalMinutes);
MessageBox.Show("Total length (km): " + lengthInKm + " Travel time (min): " + timeInMinutes, "Route Info");

Search and directions

Combine what you learned in Search for an address to allow your user to search for the start and end locations.

Handle multiple results

In this lab you handled the first result but ignored any additional results. Can you create a UI to allow the user to review multiple results?