Downloading map from portal for offline use

2132
2
Jump to solution
07-12-2017 02:12 PM
BikeshMaharjan1
New Contributor III

I am trying to download the map from my ArcGis portal content as a Mobile map package so that I can use it offline.

I am able to create arcgisportal with my credentials and i can connect and get basic data but when i try to download the map with:

GenerateOfflineMapResult jobResult = await job.GetResultAsync(); 

It gives me an error which says token is required. So, I tried to make the map public to everyone and still it gives me the same error.

I basically followed this tutorial Create an offline map—ArcGIS Runtime SDK for .NET (WPF) | ArcGIS for Developers .

Thanks!

0 Kudos
1 Solution

Accepted Solutions
AnttiKajanus1
Occasional Contributor III

Hmm, that sounds a bit weird. If you already have authenticated and you can access the portal with given credentials then it sounds that for some reason that isn't attached correctly or there are other privileges that doesn't match. 

You should see the error in the results object for the basemap, can you confirm that? Or is the await actually throwing an exception?

How do you provide the credential for GenerateOfflineMapJobOfflineMapTask (and job) doesn't support setting credentials directly since the task might be accessing multiple domain so you have to hook into AuthenticationManager.  I added a simple example to this response. 

I assume that you are using a basemap from the basemap gallery which requires you to authenticate the user since using the export tiles operation is shared as subscriber content. The operation doesn't use credits but requires the user be authenticated. If you don't use our basemaps and those services exposes export tiles operation and none of the feature services requires authentication, you shouldn't see that prompted. 

Here is an example how to hook authentication to the job. 

using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Portal;
using Esri.ArcGISRuntime.Security;
using Esri.ArcGISRuntime.Tasks.Offline;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

namespace WaterNetworkDemo
{
    public partial class MainWindow : Window
    {
        private const string PORTAL_ITEM_ID = "19df6a8e2c694a3c958778ac25398f26";

        private string offlineDataFolder;
        
        public MainWindow()
        {
            InitializeComponent();
            AuthenticationManager.Current.ChallengeHandler = new ChallengeHandler(CreateKnownCredentials);
            Initialize();
        }

        private async void Initialize()
        {
            try
            {
                Map offlineMap = null;
                MobileMapPackage offlineMapPackage = null;

                offlineDataFolder = Path.Combine(Directory.GetCurrentDirectory(), "OfflineMaps", "WaterNetwork.mmpk");

                try
                {
                    // Try to open the package, if there isn't a valid package at this location an error will be thrown
                    offlineMapPackage = await MobileMapPackage.OpenAsync(offlineDataFolder);
                    offlineMap = offlineMapPackage.Maps.First();
                }
                catch (FileNotFoundException)
                {
                    // If there isn't a valid offline map package in provided path, this is thrown.
                }

                // Package was not found, lets create it and download it to that location
                if (offlineMapPackage == null)
                {
                    loadingIndicator.Visibility = Visibility.Collapsed;
                    busyIndicator.Visibility = Visibility.Visible;
                    // Folder doesn't exist, lets create it and download the data
                    Directory.CreateDirectory(offlineDataFolder);
                    offlineMap = await GenerateOfflineMapAsync(offlineDataFolder, PORTAL_ITEM_ID);
                }

                // Hide loading indicators after the map has been opened / downloaded
                loadingIndicator.Visibility = Visibility.Collapsed;
                busyIndicator.Visibility = Visibility.Collapsed;

                if (offlineMap != null)
                {
                    // Zoom to last specific viewpoint when the map is added to the mapview.
                  //  offlineMap.InitialViewpoint = offlineMap.Bookmarks.Last().Viewpoint;
                    // Show the offline map
                    MyMapView.Map = offlineMap;
                }
                else
                {
                    // For some reason we don't have offline map available, show error message
                    mapNoAvailable.Visibility = Visibility.Visible;
                }
            }
            catch (Exception ex)
            {
                // Something unexpected happened, handle properly
                MessageBox.Show(ex.Message);
            }
        }

        private async Task<Map> GenerateOfflineMapAsync(string path, string webmapItemId)
        {
            Map offlineMap = null;
            try
            {
                #region 1) Create an offline map task

                // Load portal, using ArcGIS Online in this case
                ArcGISPortal portal = await ArcGISPortal.CreateAsync();

                // Create a map that is taken offline based on item id
                PortalItem webmapItem = await PortalItem.CreateAsync(portal, webmapItemId);

                // Create the area that is taken offline, use extent defined in the portal item
                var areaOfInterest = GeometryEngine.Project(webmapItem.Extent, SpatialReferences.WebMercator);

                // Create task and parameters
                OfflineMapTask task = await OfflineMapTask.CreateAsync(webmapItem);
                #endregion

                #region 2) Specify parameters 
                // Create default parameters
                GenerateOfflineMapParameters parameters =
                      await task.CreateDefaultGenerateOfflineMapParametersAsync(areaOfInterest);
                parameters.MinScale = 200000;
                parameters.MaxScale = 1250;
                #endregion

                #region 3) Generate offline map, report progress and use offline map
                // Create a job and hook progress changed reporting
                var job = task.GenerateOfflineMap(parameters, path);
                job.ProgressChanged += (s, e) =>
                {
                    // Update progress to UI, ensure that this is done in UI thread
                    Dispatcher.Invoke(() =>
                    {
                        Percentage.Text = job.Progress > 0 ? $"{job.Progress.ToString()} %" : string.Empty;
                    });
                };

                // Execute job and wait until the map has been taken offline
                GenerateOfflineMapResult results = await job.GetResultAsync();
                // Set offline map from the results
                offlineMap = results.OfflineMap;
                #endregion 
            }
            catch (TaskCanceledException)
            {
                // Thrown if the operation was cancelled 
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Taking map offline failed.");
            }

            // Return offline map or null if taking the map offline failed
            return offlineMap;
        }

        private string _username = "username";
        private string _password = "password";

        private async Task<Credential> CreateKnownCredentials(CredentialRequestInfo info)
        {
            // If this isn't the expected resource, the credential will stay null
            Credential knownCredential = null;

            try
            {

                // Create a credential for this resource
                knownCredential = await AuthenticationManager.Current.GenerateCredentialAsync
                                        (info.ServiceUri,
                                         _username,
                                         _password,
                                         info.GenerateTokenOptions);
            }
            catch (Exception ex)
            {
                // Report error accessing a secured resource
                MessageBox.Show("Access to " + info.ServiceUri.AbsoluteUri + " denied. " + ex.Message, "Credential Error");
            }

            // Return the credential
            return knownCredential;
        }
    }
}

<Window x:Class="WaterNetworkDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:esri="http://schemas.esri.com/arcgis/runtime/2013"
        xmlns:local="clr-namespace:WaterNetworkDemo"
        xmlns:converters="clr-namespace:WaterNetworkDemo.DataConverters"
        mc:Ignorable="d"
        Title="MainWindow" Height="525" Width="790">
    <Grid>
        <esri:MapView x:Name="MyMapView"/>

        <TextBlock x:Name="mapNoAvailable" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="18"
                   Text="Map isn't currently available." Visibility="Collapsed"></TextBlock>
        <Grid x:Name="busyIndicator" Background="#807f7f7f" Visibility="Collapsed">
            <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                </Grid.RowDefinitions>
                <TextBlock Foreground="White" Margin="10" FontSize="18" HorizontalAlignment="Center" VerticalAlignment="Center">
                    <Run Text="Generating offline map... "></Run>
                    <Run x:Name="Percentage" Text=""></Run>
                </TextBlock>
                <ProgressBar x:Name="progressBar" Minimum="0" Maximum="100"
                             IsEnabled="True" IsIndeterminate="True" Width="100" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1" Height="10" Margin="0,0,0,10"/>
             </Grid>
        </Grid>
        <Grid x:Name="loadingIndicator" Background="#807f7f7f" Visibility="Visible">
            <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                </Grid.RowDefinitions>
                <TextBlock Text="Loading online map..." Foreground="White" Margin="10" FontSize="18"/>
                <ProgressBar IsEnabled="True" IsIndeterminate="True" Width="100" Height="10" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1"/>
            </Grid>
        </Grid>
    </Grid>
</Window>

View solution in original post

2 Replies
AnttiKajanus1
Occasional Contributor III

Hmm, that sounds a bit weird. If you already have authenticated and you can access the portal with given credentials then it sounds that for some reason that isn't attached correctly or there are other privileges that doesn't match. 

You should see the error in the results object for the basemap, can you confirm that? Or is the await actually throwing an exception?

How do you provide the credential for GenerateOfflineMapJobOfflineMapTask (and job) doesn't support setting credentials directly since the task might be accessing multiple domain so you have to hook into AuthenticationManager.  I added a simple example to this response. 

I assume that you are using a basemap from the basemap gallery which requires you to authenticate the user since using the export tiles operation is shared as subscriber content. The operation doesn't use credits but requires the user be authenticated. If you don't use our basemaps and those services exposes export tiles operation and none of the feature services requires authentication, you shouldn't see that prompted. 

Here is an example how to hook authentication to the job. 

using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Portal;
using Esri.ArcGISRuntime.Security;
using Esri.ArcGISRuntime.Tasks.Offline;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

namespace WaterNetworkDemo
{
    public partial class MainWindow : Window
    {
        private const string PORTAL_ITEM_ID = "19df6a8e2c694a3c958778ac25398f26";

        private string offlineDataFolder;
        
        public MainWindow()
        {
            InitializeComponent();
            AuthenticationManager.Current.ChallengeHandler = new ChallengeHandler(CreateKnownCredentials);
            Initialize();
        }

        private async void Initialize()
        {
            try
            {
                Map offlineMap = null;
                MobileMapPackage offlineMapPackage = null;

                offlineDataFolder = Path.Combine(Directory.GetCurrentDirectory(), "OfflineMaps", "WaterNetwork.mmpk");

                try
                {
                    // Try to open the package, if there isn't a valid package at this location an error will be thrown
                    offlineMapPackage = await MobileMapPackage.OpenAsync(offlineDataFolder);
                    offlineMap = offlineMapPackage.Maps.First();
                }
                catch (FileNotFoundException)
                {
                    // If there isn't a valid offline map package in provided path, this is thrown.
                }

                // Package was not found, lets create it and download it to that location
                if (offlineMapPackage == null)
                {
                    loadingIndicator.Visibility = Visibility.Collapsed;
                    busyIndicator.Visibility = Visibility.Visible;
                    // Folder doesn't exist, lets create it and download the data
                    Directory.CreateDirectory(offlineDataFolder);
                    offlineMap = await GenerateOfflineMapAsync(offlineDataFolder, PORTAL_ITEM_ID);
                }

                // Hide loading indicators after the map has been opened / downloaded
                loadingIndicator.Visibility = Visibility.Collapsed;
                busyIndicator.Visibility = Visibility.Collapsed;

                if (offlineMap != null)
                {
                    // Zoom to last specific viewpoint when the map is added to the mapview.
                  //  offlineMap.InitialViewpoint = offlineMap.Bookmarks.Last().Viewpoint;
                    // Show the offline map
                    MyMapView.Map = offlineMap;
                }
                else
                {
                    // For some reason we don't have offline map available, show error message
                    mapNoAvailable.Visibility = Visibility.Visible;
                }
            }
            catch (Exception ex)
            {
                // Something unexpected happened, handle properly
                MessageBox.Show(ex.Message);
            }
        }

        private async Task<Map> GenerateOfflineMapAsync(string path, string webmapItemId)
        {
            Map offlineMap = null;
            try
            {
                #region 1) Create an offline map task

                // Load portal, using ArcGIS Online in this case
                ArcGISPortal portal = await ArcGISPortal.CreateAsync();

                // Create a map that is taken offline based on item id
                PortalItem webmapItem = await PortalItem.CreateAsync(portal, webmapItemId);

                // Create the area that is taken offline, use extent defined in the portal item
                var areaOfInterest = GeometryEngine.Project(webmapItem.Extent, SpatialReferences.WebMercator);

                // Create task and parameters
                OfflineMapTask task = await OfflineMapTask.CreateAsync(webmapItem);
                #endregion

                #region 2) Specify parameters 
                // Create default parameters
                GenerateOfflineMapParameters parameters =
                      await task.CreateDefaultGenerateOfflineMapParametersAsync(areaOfInterest);
                parameters.MinScale = 200000;
                parameters.MaxScale = 1250;
                #endregion

                #region 3) Generate offline map, report progress and use offline map
                // Create a job and hook progress changed reporting
                var job = task.GenerateOfflineMap(parameters, path);
                job.ProgressChanged += (s, e) =>
                {
                    // Update progress to UI, ensure that this is done in UI thread
                    Dispatcher.Invoke(() =>
                    {
                        Percentage.Text = job.Progress > 0 ? $"{job.Progress.ToString()} %" : string.Empty;
                    });
                };

                // Execute job and wait until the map has been taken offline
                GenerateOfflineMapResult results = await job.GetResultAsync();
                // Set offline map from the results
                offlineMap = results.OfflineMap;
                #endregion 
            }
            catch (TaskCanceledException)
            {
                // Thrown if the operation was cancelled 
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Taking map offline failed.");
            }

            // Return offline map or null if taking the map offline failed
            return offlineMap;
        }

        private string _username = "username";
        private string _password = "password";

        private async Task<Credential> CreateKnownCredentials(CredentialRequestInfo info)
        {
            // If this isn't the expected resource, the credential will stay null
            Credential knownCredential = null;

            try
            {

                // Create a credential for this resource
                knownCredential = await AuthenticationManager.Current.GenerateCredentialAsync
                                        (info.ServiceUri,
                                         _username,
                                         _password,
                                         info.GenerateTokenOptions);
            }
            catch (Exception ex)
            {
                // Report error accessing a secured resource
                MessageBox.Show("Access to " + info.ServiceUri.AbsoluteUri + " denied. " + ex.Message, "Credential Error");
            }

            // Return the credential
            return knownCredential;
        }
    }
}

<Window x:Class="WaterNetworkDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:esri="http://schemas.esri.com/arcgis/runtime/2013"
        xmlns:local="clr-namespace:WaterNetworkDemo"
        xmlns:converters="clr-namespace:WaterNetworkDemo.DataConverters"
        mc:Ignorable="d"
        Title="MainWindow" Height="525" Width="790">
    <Grid>
        <esri:MapView x:Name="MyMapView"/>

        <TextBlock x:Name="mapNoAvailable" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="18"
                   Text="Map isn't currently available." Visibility="Collapsed"></TextBlock>
        <Grid x:Name="busyIndicator" Background="#807f7f7f" Visibility="Collapsed">
            <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                </Grid.RowDefinitions>
                <TextBlock Foreground="White" Margin="10" FontSize="18" HorizontalAlignment="Center" VerticalAlignment="Center">
                    <Run Text="Generating offline map... "></Run>
                    <Run x:Name="Percentage" Text=""></Run>
                </TextBlock>
                <ProgressBar x:Name="progressBar" Minimum="0" Maximum="100"
                             IsEnabled="True" IsIndeterminate="True" Width="100" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1" Height="10" Margin="0,0,0,10"/>
             </Grid>
        </Grid>
        <Grid x:Name="loadingIndicator" Background="#807f7f7f" Visibility="Visible">
            <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                </Grid.RowDefinitions>
                <TextBlock Text="Loading online map..." Foreground="White" Margin="10" FontSize="18"/>
                <ProgressBar IsEnabled="True" IsIndeterminate="True" Width="100" Height="10" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Row="1"/>
            </Grid>
        </Grid>
    </Grid>
</Window>
BikeshMaharjan1
New Contributor III

Thanks a bunch for the example. That worked like a charm! You are right. I was actually getting the exception "Token Required" because the credentials were not connected with the job. I had tried to manually set up the credentials but as pointed out, one cannot set the credentials directly and was getting confused with creating ChallengeHandler before i saw your reply. Thanks again!

0 Kudos