Mobile map (search and route)
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 offline.
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. If a locator task is available, click on the map to reverse geocode the location's address. If transportation networks are available, a route will be calculated between geocode locations.
How it works
- Create a
MobileMapPackage
usingMobileMapPackage.OpenAsync(path)
. - Get a list of maps using the
Maps
property. - If the package has a locator, access it using the
LocatorTask
property. - 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
disconnected, field mobility, geocode, network, network analysis, offline, routing, search, transportation
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(
name: "Mobile map (search and route)",
category: "Map",
description: "Display maps and use locators to enable search and routing offline using a Mobile Map Package.",
instructions: "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. If a locator task is available, click on the map to reverse geocode the location's address. If transportation networks are available, a route will be calculated between geocode locations.",
tags: new[] { "disconnected", "field mobility", "geocode", "network", "network analysis", "offline", "routing", "search", "transportation" })]
[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)
{
// Open the map package.
MobileMapPackage package = await MobileMapPackage.OpenAsync(path);
// 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();
}
}
}