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 tutorial 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.
Complete or review tutorials 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 tutorials here.
Start Visual Studio.
Choose File > New > Project and select the ArcGIS Runtime Application (WPF) template for Visual C#.
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;
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 tutorial.
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;
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;
Your app will need to either include the StreetMap Premium .mmpk files, or be able to download them on demand. For this tutorial, the data will be included in the Visual Studio project.
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.
Add a method to the MapViewModel
class for preparing the offline transportation network for use:
public async Task ConfigureStreetMapPremium()
{
// load transportation network
}
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;
}
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();
}
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;
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>
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;
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;
// **
}
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();
};
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);
}
}
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();
}
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);
}
}
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);
}
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.
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);
}
}
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();
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);
Solve the route and select the first returned result:
RouteResult solveRouteResult = await solveRouteTask.SolveRouteAsync(routeParameters);
Route firstRoute = solveRouteResult.Routes.FirstOrDefault();
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);
Uncomment the call to FindRoute()
in the SetEndMarker
method.
Your map displays a route between two tapped locations. Compare with the completed solution project.
The RouteParameters
class offers a lot of options. Explore some of these options and enhance your app. Can you show turn-by-turn directions?
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");
Combine what you learned in Search for an address to allow your user to search for the start and end locations.
In this tutorial you handled the first result but ignored any additional results. Can you create a UI to allow the user to review multiple results?