Skip To Content ArcGIS for Developers Sign In Dashboard

ArcGIS Runtime SDK for .NET

Mobile map (search and route)

This code sample is available for these platforms:
View Sample on GitHub

Display maps and use locators to enable search and routing offline using a Mobile Map Package.

Use case

Mobile map packages make it easy to transmit and store the necessary components for an offline map experience, including transportation networks (for routing/navigation), locators (address search, forward and reverse geocoding), and maps. A field worker might download a mobile map package to support their operations while working outside the range of cell service.

How to use the sample

A list of maps from a mobile map package will be displayed. If the map contains transportation networks, the list item will have a navigation icon. Click on a map in the list to open it. Tap on the map to reverse geocode the tapped locations's address if a locator task is available. If transportation networks are available, a route will be calculated between tapped locations. Tap again to clear the route result.

How it works

  1. Create a MobileMapPackage using MobileMapPackage.OpenAsync(path).
  2. Get a list of maps using the Maps property.
  3. If the package has a locator, access it using the LocatorTask property.
  4. To see if a map contains transportation networks, check each map's TransportationNetworks property.

Relevant API

  • GeocodeResult
  • MobileMapPackage
  • ReverseGeocodeParameters
  • Route
  • RouteParameters
  • RouteResult
  • RouteTask
  • TransportationNetworkDataset

Tags

Offline, Routing, Search, geocode, network, transportation, network analysis, disconnected, field mobility

Sample Code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml.Controls.Maps;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media.Imaging;
using Esri.ArcGISRuntime;
using Esri.ArcGISRuntime.Portal;
using Esri.ArcGISRuntime.UI;

namespace ArcGISRuntime.UWP.Samples.MobileMapSearchAndRoute
{
    class ItemToImageSourceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            Item mapItem = value as Item;
            if (mapItem != null)
            {
                if (mapItem.ThumbnailUri != null)
                {
                    // Sometimes image URIs have a . appended to them... BitmapImage doesn't like that.
                    return new BitmapImage(new Uri(mapItem.ThumbnailUri.OriginalString.TrimEnd('.')));
                }

                if (mapItem.Thumbnail != null &&
                    mapItem.Thumbnail.LoadStatus == LoadStatus.Loaded &&
                    mapItem.Thumbnail.Width > 0)
                {
                    return mapItem.Thumbnail.ToImageSourceAsync().Result;
                }
            }

            return new BitmapImage();
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }
}
<UserControl
    x:Class="ArcGISRuntime.UWP.Samples.MobileMapSearchAndRoute.MobileMapSearchAndRoute"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:esriUI="using:Esri.ArcGISRuntime.UI.Controls"
    xmlns:mapping="using:Esri.ArcGISRuntime.Mapping"
    xmlns:local="using:ArcGISRuntime.UWP.Samples.MobileMapSearchAndRoute">
    <UserControl.Resources>
        <local:ItemToImageSourceConverter x:Key="ItemToImageSourceConverter" />
        <local:NullOrEmptyToVisibilityConverter x:Key="NullToVisibilityConverter" />
    </UserControl.Resources>
    <Grid>
        <esriUI:MapView x:Name="MyMapView" />
        <Border Style="{StaticResource BorderStyle}">
            <StackPanel>
                <TextBlock
                    Text="Select a map from the package. If a network is available, you can route between tapped points. If a locator is available, the address for each tapped point will be displayed in a callout."
                    TextWrapping="Wrap" FontWeight="SemiBold" />
                <ListBox ItemsSource="{Binding Maps}"
                         SelectionChanged="Map_Selected"
                         Margin="0,5,0,0">
                    <ListBox.ItemContainerStyle>
                        <Style TargetType="ListBoxItem">
                            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                        </Style>
                    </ListBox.ItemContainerStyle>
                    <ListBox.ItemTemplate>
                        <DataTemplate x:DataType="mapping:Map">
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="100" />
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
                                <Image Grid.Column="0"
                                       Source="{Binding Item, Converter={StaticResource ItemToImageSourceConverter}}" />
                                <Image Grid.Column="1" Source="ms-appx:///Assets/routingSymbol.png"
                                       Height="50" Width="50"
                                       Margin="5"
                                       Visibility="{Binding TransportationNetworks,Converter={StaticResource NullToVisibilityConverter}}" />
                                <TextBlock Grid.Column="2"
                                           Text="{Binding Item.Title}"
                                           VerticalAlignment="Center"
                                           TextAlignment="Right" />
                            </Grid>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>
            </StackPanel>
        </Border>
    </Grid>
</UserControl>
// Copyright 2019 Esri.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific 
// language governing permissions and limitations under the License.

using ArcGISRuntime.Samples.Managers;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Symbology;
using Esri.ArcGISRuntime.Tasks.Geocoding;
using Esri.ArcGISRuntime.Tasks.NetworkAnalysis;
using Esri.ArcGISRuntime.UI;
using Esri.ArcGISRuntime.UI.Controls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Windows.UI.Popups;
using Windows.UI.Xaml.Controls;

namespace ArcGISRuntime.UWP.Samples.MobileMapSearchAndRoute
{
    [ArcGISRuntime.Samples.Shared.Attributes.Sample(
        "Mobile map (search and route)",
        "Map",
        "Display maps and use locators to enable search and routing offline using a Mobile Map Package.",
        "")]
    [ArcGISRuntime.Samples.Shared.Attributes.OfflineData("260eb6535c824209964cf281766ebe43")]
    [ArcGISRuntime.Samples.Shared.Attributes.ClassFile("Converters\\ItemToImageSourceConverter.cs", "Converters\\NullToVisibilityConverter.cs")]
    public partial class MobileMapSearchAndRoute
    {
        // Hold references to map resources for easy access.
        public ObservableCollection<Map> Maps { get; } = new ObservableCollection<Map>();
        private LocatorTask _packageLocator;
        private TransportationNetworkDataset _networkDataset;

        // Overlays for use in visualizing routes.
        private GraphicsOverlay _routeOverlay;
        private GraphicsOverlay _waypointOverlay;

        // Track the start and end point for route calculation.
        private MapPoint _startPoint;
        private MapPoint _endPoint;

        public MobileMapSearchAndRoute()
        {
            InitializeComponent();
            DataContext = this;
            Initialize();
        }

        private async void Initialize()
        {
            // Get the path to the package on disk.
            string filePath = DataManager.GetDataFolder("260eb6535c824209964cf281766ebe43", "SanFrancisco.mmpk");

            // Open the map package.
            MobileMapPackage package = await OpenMobileMapPackage(filePath);

            // Populate the list of maps.
            foreach (Map map in package.Maps)
            {
                await map.LoadAsync();
                Maps.Add(map);
            }

            // Get the locator task from the package.
            _packageLocator = package.LocatorTask;

            // Create and add an overlay for showing a route.
            _routeOverlay = new GraphicsOverlay();
            _routeOverlay.Renderer = new SimpleRenderer
            {
                Symbol = new SimpleLineSymbol(SimpleLineSymbolStyle.Solid, System.Drawing.Color.Blue, 3)
            };
            MyMapView.GraphicsOverlays.Add(_routeOverlay);

            // Create and add an overlay for showing waypoints/stops.
            _waypointOverlay = new GraphicsOverlay();
            MyMapView.GraphicsOverlays.Add(_waypointOverlay);

            // Enable tap-to-reverse geocode and tap-to-route.
            MyMapView.GeoViewTapped += MapView_Tapped;
        }

        private async Task<MobileMapPackage> OpenMobileMapPackage(string path)
        {
            // Load directly or unpack then load as needed by the map package.
            if (await MobileMapPackage.IsDirectReadSupportedAsync(path))
            {
                // Open the map package.
                MobileMapPackage package = await MobileMapPackage.OpenAsync(path);

                // Load the package.
                await package.LoadAsync();

                // Return the opened package.
                return package;
            }
            else
            {
                // Create a path for the unpacked package.
                string unpackedPath = path + "unpacked";

                // Unpack the package.
                await MobileMapPackage.UnpackAsync(path, unpackedPath);

                // Open the package.
                MobileMapPackage package = await MobileMapPackage.OpenAsync(unpackedPath);

                // Load the package.
                await package.LoadAsync();

                // Return the opened package.
                return package;
            }
        }

        private async void MapView_Tapped(object sender, GeoViewInputEventArgs e)
        {
            // Handle routing.
            try
            {
                await ProcessRouteRequest(e.Location);
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception);
                await new MessageDialog("Couldn't geocode or route.", "Error").ShowAsync();
            }
        }

        private async Task ShowGeocodeResult(MapPoint tappedPoint)
        {
            // Reverse geocode to get an address.
            IReadOnlyList<GeocodeResult> results = await _packageLocator.ReverseGeocodeAsync(tappedPoint);

            // Process the address into usable strings.
            string address = results.First().Label;

            // Show the address in a callout.
            MyMapView.ShowCalloutAt(tappedPoint, new CalloutDefinition(address));
        }

        private async Task ProcessRouteRequest(MapPoint tappedPoint)
        {
            // Clear any existing overlays.
            _routeOverlay.Graphics.Clear();
            MyMapView.DismissCallout();

            // Return if there is no network available for routing.
            if (_networkDataset == null)
            {
                await ShowGeocodeResult(tappedPoint);
                return;
            }

            // Set the start point if it hasn't been set.
            if (_startPoint == null)
            {
                _startPoint = tappedPoint;

                await ShowGeocodeResult(tappedPoint);

                // Show the start point.
                _waypointOverlay.Graphics.Add(await GraphicForPoint(_startPoint));

                return;
            }

            if (_endPoint == null)
            {
                await ShowGeocodeResult(tappedPoint);

                // Show the end point.
                _endPoint = tappedPoint;
                _waypointOverlay.Graphics.Add(await GraphicForPoint(_endPoint));

                // Create the route task from the local network dataset.
                RouteTask routingTask = await RouteTask.CreateAsync(_networkDataset);

                // Configure route parameters for the route between the two tapped points.
                RouteParameters routingParameters = await routingTask.CreateDefaultParametersAsync();
                List<Stop> stops = new List<Stop> {new Stop(_startPoint), new Stop(_endPoint)};
                routingParameters.SetStops(stops);

                // Get the first route result.
                RouteResult result = await routingTask.SolveRouteAsync(routingParameters);
                Route firstRoute = result.Routes.First();

                // Show the route on the map. Note that symbology for the graphics overlay is defined in Initialize().
                Polyline routeLine = firstRoute.RouteGeometry;
                _routeOverlay.Graphics.Add(new Graphic(routeLine));

                return;
            }

            // Reset graphics and route.
            _routeOverlay.Graphics.Clear();
            _waypointOverlay.Graphics.Clear();
            _startPoint = null;
            _endPoint = null;
        }

        private async void Map_Selected(object sender, SelectionChangedEventArgs e)
        {
            // Clear existing overlays.
            MyMapView.DismissCallout();
            _waypointOverlay.Graphics.Clear();
            _routeOverlay.Graphics.Clear();

            try
            {
                // Get the selected map.
                Map selectedMap = e.AddedItems[0] as Map;

                // Show the map in the view.
                MyMapView.Map = selectedMap;

                // Get the transportation network if there is one. Will be set to null otherwise.
                _networkDataset = selectedMap.TransportationNetworks.FirstOrDefault();
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception);
                await new MessageDialog(exception.ToString(), "Couldn't select map").ShowAsync();
            }
        }

        private async Task<Graphic> GraphicForPoint(MapPoint point)
        {
            // Get current assembly that contains the image.
            Assembly currentAssembly = GetType().GetTypeInfo().Assembly;

            // Get image as a stream from the resources.
            // Picture is defined as EmbeddedResource and DoNotCopy.
            Stream resourceStream = currentAssembly.GetManifestResourceStream(
                "ArcGISRuntime.Resources.PictureMarkerSymbols.pin_star_blue.png");

            // Create new symbol using asynchronous factory method from stream.
            PictureMarkerSymbol pinSymbol = await PictureMarkerSymbol.CreateAsync(resourceStream);
            pinSymbol.Width = 60;
            pinSymbol.Height = 60;
            // The image is a pin; offset the image so that the pinpoint
            //     is on the point rather than the image's true center.
            pinSymbol.LeaderOffsetX = 30;
            pinSymbol.OffsetY = 14;
            return new Graphic(point, pinSymbol);
        }
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;

namespace ArcGISRuntime.UWP.Samples.MobileMapSearchAndRoute
{
    public class NullOrEmptyToVisibilityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string culture)
        {
            if (value == null || String.IsNullOrEmpty(value.ToString()))
                return Visibility.Collapsed;

            IList listValue = value as IList;
            if (listValue != null && listValue.Count > 0)
            {
                return Visibility.Visible;
            }
            return Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string culture)
        {
            throw new NotImplementedException();
        }
    }
}