MapSaveAsync Method (Boolean) |
Namespace: Esri.ArcGISRuntime.Mapping
public Task SaveAsync( bool forceSaveToSupportedVersion = false )
Exception | Condition |
---|---|
InvalidOperationException | Map must be loaded before saving to a portal. |
Asynchronous method to save any changes that have been made to the map. The map must have been constructed using the Map(PortalItem) constructor to associate it with a PortalItem object, and must be loaded.
Updates the map content data on the portal and also updates the portal item properties stored on the portal to match those stored within the map's PortalItem object.
Android
Example Name: AuthorMap
Create and save a map as an ArcGIS `PortalItem` (i.e. web map).
// Copyright 2016 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 Android.App; using Android.OS; using Android.Views; using Android.Widget; using Esri.ArcGISRuntime.Mapping; using Esri.ArcGISRuntime.Portal; using Esri.ArcGISRuntime.Security; using Esri.ArcGISRuntime.UI; using Esri.ArcGISRuntime.UI.Controls; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Android.Content; using Xamarin.Auth; using ContextThemeWrapper = Android.Support.V7.View.ContextThemeWrapper; namespace ArcGISRuntime.Samples.AuthorMap { [Activity (ConfigurationChanges=Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize)] [ArcGISRuntime.Samples.Shared.Attributes.Sample( "Author and save a map", "Map", "This sample demonstrates how to author and save a map as an ArcGIS portal item (web map). Saving a map to arcgis.com requires an ArcGIS Online login.", "1. Pan and zoom to the extent you would like for your map. \n2. Choose a basemap from the list of available basemaps. \n3. Choose one or more operational layers to include. \n4. Click 'Save ...' to apply your changes. \n5. Provide info for the new portal item, such as a Title, Description, and Tags. \n6. Click 'Save Map'. \n7. After successfully logging in to your ArcGIS Online account, the map will be saved to your default folder. \n8. You can make additional changes, update the map, and then re-save to store changes in the portal item.")] public class AuthorMap : Activity, IOAuthAuthorizeHandler { // Create and hold reference to the used MapView private MapView _myMapView = new MapView(); // Progress bar to show when the app is working private ProgressBar _progressBar; // Use a TaskCompletionSource to track the completion of the authorization private TaskCompletionSource<IDictionary<string, string>> _taskCompletionSource; // Store the OAuth dialog and controls for updating OAuth configuration private AlertDialog _configOAuthDialog = null; private EditText _clientIdText; private EditText _redirectUrlText; // String array to store basemap constructor types private string[] _basemapTypes = { "Topographic", "Streets", "Imagery", "Oceans" }; // Dictionary of operational layer names and URLs private Dictionary<string, string> _operationalLayerUrls = new Dictionary<string, string> { {"World Elevations", "https://sampleserver6.arcgisonline.com/arcgis/rest/services/Elevation/WorldElevations/MapServer"}, {"World Cities", "https://sampleserver6.arcgisonline.com/arcgis/rest/services/SampleWorldCities/MapServer/" }, {"US Census Data", "https://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer"} }; // Variables for OAuth-related values ... // URL of the server to authenticate with private string _serverUrl = "https://www.arcgis.com/sharing/rest"; // TODO: Add Client ID for an app registered with the server private string _appClientId = "lgAdHkYZYlwwfAhC"; // TODO: Add URL for redirecting after a successful authorization // Note - this must be a URL configured as a valid Redirect URI with your app private string _oAuthRedirectUrl = "my-ags-app://auth"; protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); Title = "Author and save a map"; // Create the UI, setup the control references and execute initialization CreateLayout(); // Set up AuthenticationManager (prompt user for OAuth config first) ShowOAuthConfigDialog(); // Note: the code above calls UpdateAuthenticationManager() Initialize(); } private void Initialize() { // Create new Map with basemap Map myMap = new Map(Basemap.CreateLightGrayCanvas()); // Provide used Map to the MapView _myMapView.Map = myMap; } private void CreateLayout() { // Create a horizontal layout for the buttons at the top LinearLayout buttonLayout = new LinearLayout(this) { Orientation = Orientation.Horizontal }; // Create a progress bar (circle) to show when the app is working _progressBar = new ProgressBar(this) { Indeterminate = true, Visibility = ViewStates.Invisible }; // Create button to clear the map from the map view (start over) Button newMapButton = new Button(this) { Text = "New" }; newMapButton.Click += OnNewMapClicked; // Create button to show available basemap Button basemapButton = new Button(this) { Text = "Basemap" }; basemapButton.Click += OnBasemapsClicked; // Create a button to show operational layers Button layersButton = new Button(this) { Text = "Layers" }; layersButton.Click += OnLayersClicked; // Create a button to save the map Button saveMapButton = new Button(this) { Text = "Save ..." }; saveMapButton.Click += OnSaveMapClicked; // Add progress bar, new map, basemap, layers, and save buttons to the layout buttonLayout.AddView(newMapButton); buttonLayout.AddView(basemapButton); buttonLayout.AddView(layersButton); buttonLayout.AddView(saveMapButton); buttonLayout.AddView(_progressBar); // Create a new vertical layout for the app (buttons followed by map view) LinearLayout mainLayout = new LinearLayout(this) { Orientation = Orientation.Vertical }; // Add the button layout mainLayout.AddView(buttonLayout); // Add the map view to the layout mainLayout.AddView(_myMapView); // Show the layout in the app SetContentView(mainLayout); } private void OnNewMapClicked(object sender, EventArgs e) { // Create a new map for the map view (can make changes and save as a new portal item) _myMapView.Map = new Map(Basemap.CreateLightGrayCanvas()); } private void OnSaveMapClicked(object sender, EventArgs e) { // Create a dialog to show save options (title, description, and tags) SaveDialogFragment saveMapDialog = new SaveDialogFragment(_myMapView.Map.Item as PortalItem); saveMapDialog.OnSaveClicked += SaveMapAsync; // Begin a transaction to show a UI fragment (the save dialog) FragmentTransaction trans = FragmentManager.BeginTransaction(); saveMapDialog.Show(trans, "save map"); } private async void SaveMapAsync(object sender, OnSaveMapEventArgs e) { AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this); // Get the current map Map myMap = _myMapView.Map; try { // Show the progress bar so the user knows work is happening _progressBar.Visibility = ViewStates.Visible; // Get information entered by the user for the new portal item properties string title = e.MapTitle; string description = e.MapDescription; string[] tags = e.Tags; // Apply the current extent as the map's initial extent myMap.InitialViewpoint = _myMapView.GetCurrentViewpoint(ViewpointType.BoundingGeometry); // Export the current map view for the item's thumbnail RuntimeImage thumbnailImg = await _myMapView.ExportImageAsync(); // See if the map has already been saved (has an associated portal item) if (myMap.Item == null) { // Call a function to save the map as a new portal item await SaveNewMapAsync(myMap, title, description, tags, thumbnailImg); // Report a successful save alertBuilder.SetTitle("Map saved"); alertBuilder.SetMessage("Saved '" + title + "' to ArcGIS Online!"); alertBuilder.Show(); } else { // This is not the initial save, call SaveAsync to save changes to the existing portal item await myMap.SaveAsync(); // Get the file stream from the new thumbnail image Stream imageStream = await thumbnailImg.GetEncodedBufferAsync(); // Update the item thumbnail ((PortalItem)myMap.Item).SetThumbnail(imageStream); await myMap.SaveAsync(); // Report update was successful alertBuilder.SetTitle("Updates saved"); alertBuilder.SetMessage("Saved changes to '" + myMap.Item.Title + "'"); alertBuilder.Show(); } } catch (Exception ex) { // Show the exception message alertBuilder.SetTitle("Unable to save map"); alertBuilder.SetMessage(ex.Message); alertBuilder.Show(); } finally { // Hide the progress bar _progressBar.Visibility = ViewStates.Invisible; } } private async Task SaveNewMapAsync(Map myMap, string title, string description, string[] tags, RuntimeImage img) { // Challenge the user for portal credentials (OAuth credential request for arcgis.com) CredentialRequestInfo loginInfo = new CredentialRequestInfo { // Use the OAuth implicit grant flow GenerateTokenOptions = new GenerateTokenOptions { TokenAuthenticationType = TokenAuthenticationType.OAuthImplicit }, // Indicate the url (portal) to authenticate with (ArcGIS Online) ServiceUri = new Uri("https://www.arcgis.com/sharing/rest") }; try { // Get a reference to the (singleton) AuthenticationManager for the app AuthenticationManager thisAuthenticationManager = AuthenticationManager.Current; // Call GetCredentialAsync on the AuthenticationManager to invoke the challenge handler await thisAuthenticationManager.GetCredentialAsync(loginInfo, false); } catch (System.OperationCanceledException) { // user canceled the login throw new Exception("Portal log in was canceled."); } // Get the ArcGIS Online portal (will use credential from login above) ArcGISPortal agsOnline = await ArcGISPortal.CreateAsync(); // Save the current state of the map as a portal item in the user's default folder await myMap.SaveAsAsync(agsOnline, null, title, description, tags, img); } #region Basemap Button private void OnBasemapsClicked(object sender, EventArgs e) { Button mapsButton = (Button)sender; // Create a menu to show basemaps PopupMenu mapsMenu = new PopupMenu(mapsButton.Context, mapsButton); mapsMenu.MenuItemClick += OnBasemapsMenuItemClicked; // Create a menu option for each basemap type foreach (string basemapType in _basemapTypes) { mapsMenu.Menu.Add(basemapType); } // Show menu in the view mapsMenu.Show(); } private void OnBasemapsMenuItemClicked(object sender, PopupMenu.MenuItemClickEventArgs e) { // Get the title of the selected item string selectedBasemapType = e.Item.TitleCondensedFormatted.ToString(); // Apply the chosen basemap switch (selectedBasemapType) { case "Topographic": // Set the basemap to Topographic _myMapView.Map.Basemap = Basemap.CreateTopographic(); break; case "Streets": // Set the basemap to Streets _myMapView.Map.Basemap = Basemap.CreateStreets(); break; case "Imagery": // Set the basemap to Imagery _myMapView.Map.Basemap = Basemap.CreateImagery(); break; case "Oceans": // Set the basemap to Oceans _myMapView.Map.Basemap = Basemap.CreateOceans(); break; } } #endregion #region Layers Button private void OnLayersClicked(object sender, EventArgs e) { Button layerButton = (Button)sender; // Create menu to show layers PopupMenu layerMenu = new PopupMenu(layerButton.Context, layerButton); layerMenu.MenuItemClick += OnLayerMenuItemClicked; // Create menu options foreach (string layerInfo in _operationalLayerUrls.Keys) { layerMenu.Menu.Add(layerInfo); } // Show menu in the view layerMenu.Show(); } private void OnLayerMenuItemClicked(object sender, PopupMenu.MenuItemClickEventArgs e) { // Get the title of the selected item string selectedLayerName = e.Item.TitleCondensedFormatted.ToString(); // See if the layer already exists ArcGISMapImageLayer layer = _myMapView.Map.OperationalLayers.FirstOrDefault(l => l.Name == selectedLayerName) as ArcGISMapImageLayer; // If the layer is in the map, remove it if (layer != null) { _myMapView.Map.OperationalLayers.Remove(layer); } else { // Get the URL for this layer string layerUrl = _operationalLayerUrls[selectedLayerName]; Uri layerUri = new Uri(layerUrl); // Create a new map image layer layer = new ArcGISMapImageLayer(layerUri) { Name = selectedLayerName, // Set it 50% opaque, and add it to the map Opacity = 0.5 }; _myMapView.Map.OperationalLayers.Add(layer); } } #endregion #region OAuth helpers // Prompt for portal item information private void ShowOAuthConfigDialog() { // Create a dialog to get OAuth information (client id, redirect url, etc.) AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); // Create the layout LinearLayout dialogLayout = new LinearLayout(this) { Orientation = Orientation.Vertical }; // Create a text box for entering the client id LinearLayout clientIdLayout = new LinearLayout(this) { Orientation = Orientation.Horizontal }; TextView clientIdLabel = new TextView(this) { Text = "Client ID:" }; _clientIdText = new EditText(this); if (!String.IsNullOrEmpty(_appClientId)) { _clientIdText.Text = _appClientId; } clientIdLayout.AddView(clientIdLabel); clientIdLayout.AddView(_clientIdText); // Create a text box for entering the redirect url LinearLayout redirectUrlLayout = new LinearLayout(this) { Orientation = Orientation.Horizontal }; TextView redirectUrlLabel = new TextView(this) { Text = "Redirect:" }; _redirectUrlText = new EditText(this) { Hint = "https://my.redirect/url" }; if (!String.IsNullOrEmpty(_oAuthRedirectUrl)) { _redirectUrlText.Text = _oAuthRedirectUrl; } redirectUrlLayout.AddView(redirectUrlLabel); redirectUrlLayout.AddView(_redirectUrlText); // Create a button to dismiss the dialog (and proceed with updating the values) Button okButton = new Button(this) { Text = "Save" }; // Handle the click event for the OK button okButton.Click += OnCloseOAuthDialog; // Add the controls to the dialog dialogLayout.AddView(clientIdLayout); dialogLayout.AddView(redirectUrlLayout); dialogLayout.AddView(okButton); dialogBuilder.SetView(dialogLayout); dialogBuilder.SetTitle("Configure OAuth"); // Show the dialog _configOAuthDialog = dialogBuilder.Show(); } // Click event for the OK button on the save map dialog private void OnCloseOAuthDialog(object sender, EventArgs e) { if (_configOAuthDialog != null) { // Get title and description text _appClientId = _clientIdText.Text; _oAuthRedirectUrl = _redirectUrlText.Text; // Dismiss the dialog _configOAuthDialog.Dismiss(); // Update the OAuth settings UpdateAuthenticationManager(); } } private void UpdateAuthenticationManager() { // Register the server information with the AuthenticationManager ServerInfo portalServerInfo = new ServerInfo { ServerUri = new Uri(_serverUrl), OAuthClientInfo = new OAuthClientInfo { ClientId = _appClientId, RedirectUri = new Uri(_oAuthRedirectUrl) }, // Specify OAuthAuthorizationCode if you need a refresh token (and have specified a valid client secret) // Otherwise, use OAuthImplicit TokenAuthenticationType = TokenAuthenticationType.OAuthImplicit }; // Get a reference to the (singleton) AuthenticationManager for the app AuthenticationManager thisAuthenticationManager = AuthenticationManager.Current; // Register the server information thisAuthenticationManager.RegisterServer(portalServerInfo); // Assign the method that AuthenticationManager will call to challenge for secured resources thisAuthenticationManager.ChallengeHandler = new ChallengeHandler(CreateCredentialAsync); // Set the OAuth authorization handler to this class (Implements IOAuthAuthorize interface) thisAuthenticationManager.OAuthAuthorizeHandler = this; } // ChallengeHandler function for AuthenticationManager, called whenever access to a secured resource is attempted private async Task<Credential> CreateCredentialAsync(CredentialRequestInfo info) { OAuthTokenCredential credential = null; try { // Create generate token options if necessary if (info.GenerateTokenOptions == null) { info.GenerateTokenOptions = new GenerateTokenOptions(); } // AuthenticationManager will handle challenging the user for credentials credential = await AuthenticationManager.Current.GenerateCredentialAsync ( info.ServiceUri, info.GenerateTokenOptions ) as OAuthTokenCredential; } catch (TaskCanceledException) { return credential; } catch (Exception) { // Exception will be reported in calling function throw; } return credential; } // IOAuthAuthorizeHandler.AuthorizeAsync implementation public Task<IDictionary<string, string>> AuthorizeAsync(Uri serviceUri, Uri authorizeUri, Uri callbackUri) { // If the TaskCompletionSource is not null, authorization may already be in progress and should be cancelled if (_taskCompletionSource != null) { // Try to cancel any existing authentication task _taskCompletionSource.TrySetCanceled(); } // Create a task completion source _taskCompletionSource = new TaskCompletionSource<IDictionary<string, string>>(); // Create a new Xamarin.Auth.OAuth2Authenticator using the information passed in Xamarin.Auth.OAuth2Authenticator authenticator = new OAuth2Authenticator( clientId: _appClientId, scope: "", authorizeUrl: authorizeUri, redirectUrl: callbackUri) { ShowErrors = false }; // Allow the user to cancel the OAuth attempt authenticator.AllowCancel = true; // Define a handler for the OAuth2Authenticator.Completed event authenticator.Completed += (sender, authArgs) => { try { // Check if the user is authenticated if (authArgs.IsAuthenticated) { // If authorization was successful, get the user's account Xamarin.Auth.Account authenticatedAccount = authArgs.Account; // Set the result (Credential) for the TaskCompletionSource _taskCompletionSource.SetResult(authenticatedAccount.Properties); } } catch (Exception ex) { // If authentication failed, set the exception on the TaskCompletionSource _taskCompletionSource.TrySetException(ex); // Cancel authentication authenticator.OnCancelled(); } finally { // End the OAuth login activity FinishActivity(99); } }; // If an error was encountered when authenticating, set the exception on the TaskCompletionSource authenticator.Error += (sndr, errArgs) => { // If the user cancels, the Error event is raised but there is no exception ... best to check first if (errArgs.Exception != null) { _taskCompletionSource.TrySetException(errArgs.Exception); } else { // Login canceled: end the OAuth login activity if (_taskCompletionSource != null) { _taskCompletionSource.TrySetCanceled(); FinishActivity(99); } } // Cancel authentication authenticator.OnCancelled(); }; // Present the OAuth UI (Activity) so the user can enter user name and password Intent intent = authenticator.GetUI(this); StartActivityForResult(intent, 99); // Return completion source task so the caller can await completion return _taskCompletionSource.Task; } private static IDictionary<string, string> DecodeParameters(Uri uri) { // Create a dictionary of key value pairs returned in an OAuth authorization response URI query string string answer = ""; // Get the values from the URI fragment or query string if (!String.IsNullOrEmpty(uri.Fragment)) { answer = uri.Fragment.Substring(1); } else { if (!String.IsNullOrEmpty(uri.Query)) { answer = uri.Query.Substring(1); } } // Parse parameters into key / value pairs Dictionary<string,string> keyValueDictionary = new Dictionary<string, string>(); string[] keysAndValues = answer.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries); foreach (string kvString in keysAndValues) { string[] pair = kvString.Split('='); string key = pair[0]; string value = string.Empty; if (key.Length > 1) { value = Uri.UnescapeDataString(pair[1]); } keyValueDictionary.Add(key, value); } // Return the dictionary of string keys/values return keyValueDictionary; } #endregion } // A custom DialogFragment class to show input controls for saving a web map public class SaveDialogFragment : DialogFragment { // Inputs for portal item title, description, and tags private EditText _mapTitleTextbox; private EditText _mapDescriptionTextbox; private EditText _tagsTextbox; // Store any existing portal item (for "update" versus "save", e.g.) private PortalItem _portalItem = null; // Raise an event so the listener can access input values when the form has been completed public event EventHandler<OnSaveMapEventArgs> OnSaveClicked; public SaveDialogFragment(PortalItem mapItem) { _portalItem = mapItem; } public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Dialog to display LinearLayout dialogView = null; // Get the context for creating the dialog controls Android.Content.Context ctx = Activity.ApplicationContext; Android.Support.V7.View.ContextThemeWrapper ctxWrapper = new ContextThemeWrapper(ctx, Android.Resource.Style.ThemeMaterialLight); // Set a dialog title Dialog.SetTitle("Save map to Portal"); try { base.OnCreateView(inflater, container, savedInstanceState); // The container for the dialog is a vertical linear layout dialogView = new LinearLayout(ctxWrapper) { Orientation = Orientation.Vertical }; dialogView.SetPadding(10,0,10,10); // Add a text box for entering a title for the new web map _mapTitleTextbox = new EditText(ctxWrapper) { Hint = "Title" }; dialogView.AddView(_mapTitleTextbox); // Add a text box for entering a description _mapDescriptionTextbox = new EditText(ctxWrapper) { Hint = "Description" }; dialogView.AddView(_mapDescriptionTextbox); // Add a text box for entering tags (populate with some values so the user doesn't have to fill this in) _tagsTextbox = new EditText(ctxWrapper) { Text = "ArcGIS Runtime, Web Map" }; dialogView.AddView(_tagsTextbox); // Add a button to save the map Button saveMapButton = new Button(ctxWrapper) { Text = "Save" }; saveMapButton.Click += SaveMapButtonClick; dialogView.AddView(saveMapButton); // If there's an existing portal item, configure the dialog for "update" (read-only entries) if (_portalItem != null) { _mapTitleTextbox.Text = _portalItem.Title; _mapTitleTextbox.Enabled = false; _mapDescriptionTextbox.Text = _portalItem.Description; _mapDescriptionTextbox.Enabled = false; _tagsTextbox.Text = string.Join(",", _portalItem.Tags); _tagsTextbox.Enabled = false; // Change some of the control text Dialog.SetTitle("Save changes to map"); saveMapButton.Text = "Update"; } } catch (Exception ex) { // Show the exception message AlertDialog.Builder alertBuilder = new AlertDialog.Builder(Activity); alertBuilder.SetTitle("Error"); alertBuilder.SetMessage(ex.Message); alertBuilder.Show(); } // Return the new view for display return dialogView; } // A click handler for the save map button private void SaveMapButtonClick(object sender, EventArgs e) { try { // Get information for the new portal item string title = _mapTitleTextbox.Text; string description = _mapDescriptionTextbox.Text; string[] tags = _tagsTextbox.Text.Split(','); // Make sure all required info was entered if (String.IsNullOrEmpty(title) || String.IsNullOrEmpty(description) || tags.Length == 0) { throw new Exception("Please enter a title, description, and some tags to describe the map."); } // Create a new OnSaveMapEventArgs object to store the information entered by the user OnSaveMapEventArgs mapSavedArgs = new OnSaveMapEventArgs(title, description, tags); // Raise the OnSaveClicked event so the main activity can handle the event and save the map OnSaveClicked?.Invoke(this, mapSavedArgs); // Close the dialog Dismiss(); } catch (Exception ex) { // Show the exception message (dialog will stay open so user can try again) AlertDialog.Builder alertBuilder = new AlertDialog.Builder(Activity); alertBuilder.SetTitle("Error"); alertBuilder.SetMessage(ex.Message); alertBuilder.Show(); } } } // Custom EventArgs class for containing portal item properties when saving a map public class OnSaveMapEventArgs : EventArgs { // Portal item title public string MapTitle { get; set; } // Portal item description public string MapDescription { get; set; } // Portal item tags public string[] Tags { get; set; } public OnSaveMapEventArgs(string title, string description, string[] tags) : base() { MapTitle = title; MapDescription = description; Tags = tags; } } }
Xamarin Forms Android
Example Name: AuthorMap
Create and save a map as an ArcGIS `PortalItem` (i.e. web map).
// Copyright 2016 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 Esri.ArcGISRuntime.Mapping; using Esri.ArcGISRuntime.Portal; using Esri.ArcGISRuntime.Security; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Xamarin.Forms; using Esri.ArcGISRuntime.UI; using System.IO; #if __IOS__ using Xamarin.Forms.Platform.iOS; using Xamarin.Auth; using UIKit; #endif #if __ANDROID__ using Android.App; using Application = Xamarin.Forms.Application; using Xamarin.Auth; #endif namespace ArcGISRuntime.Samples.AuthorMap { [ArcGISRuntime.Samples.Shared.Attributes.Sample( "Author and save a map", "Map", "This sample demonstrates how to author and save a map as an ArcGIS portal item (web map). Saving a map to arcgis.com requires an ArcGIS Online login.", "1. Pan and zoom to the extent you would like for your map. \n2. Choose a basemap from the list of available basemaps. \n3. Choose one or more operational layers to include. \n4. Click 'Save ...' to apply your changes. \n5. Provide info for the new portal item, such as a Title, Description, and Tags. \n6. Click 'Save Map'. \n7. After successfully logging in to your ArcGIS Online account, the map will be saved to your default folder. \n8. You can make additional changes, update the map, and then re-save to store changes in the portal item.")] [ArcGISRuntime.Samples.Shared.Attributes.ClassFile("SaveMapPage.xaml.cs")] [ArcGISRuntime.Samples.Shared.Attributes.XamlFiles("SaveMapPage.xaml")] public partial class AuthorMap : ContentPage, IOAuthAuthorizeHandler { // OAuth-related values ... // URL of the server to authenticate with (ArcGIS Online) private const string ArcGISOnlineUrl = "https://www.arcgis.com/sharing/rest"; // Client ID for the app registered with the server (Portal Maps) public static string AppClientId = "lgAdHkYZYlwwfAhC"; // Redirect URL after a successful authorization (configured for the Portal Maps application) private string _oAuthRedirectUrl = "my-ags-app://auth"; // String array to store basemap constructor types private string[] _basemapTypes = { "Topographic", "Streets", "Imagery", "Oceans" }; // Dictionary of operational layer names and URLs private Dictionary<string, string> _operationalLayerUrls = new Dictionary<string, string> { {"World Elevations", "https://sampleserver6.arcgisonline.com/arcgis/rest/services/Elevation/WorldElevations/MapServer"}, {"World Cities", "https://sampleserver6.arcgisonline.com/arcgis/rest/services/SampleWorldCities/MapServer/" }, {"US Census Data", "https://sampleserver6.arcgisonline.com/arcgis/rest/services/Census/MapServer"} }; public AuthorMap() { InitializeComponent(); // call a function to initialize the app (display a map, etc.) Initialize(); } private void Initialize() { // Call a function to create a new map with a light gray canvas basemap CreateNewMap(); // Show the default OAuth settings in the entry controls ClientIDEntry.Text = AppClientId; RedirectUrlEntry.Text = _oAuthRedirectUrl; // Change the style of the layer list view for Android and UWP switch (Device.RuntimePlatform) { case Device.Android: LayersList.BackgroundColor = Color.Black; OAuthSettingsGrid.BackgroundColor = Color.Gray; break; case Device.UWP: LayersList.BackgroundColor = Color.FromRgba(255, 255, 255, 0.3); LayersList.Margin = new Thickness(50); break; } } private void OAuthSettingsCancel(object sender, EventArgs e) { OAuthSettingsGrid.IsVisible = false; } private void SaveOAuthSettings(object sender, EventArgs e) { var appClientId = ClientIDEntry.Text.Trim(); var oAuthRedirectUrl = RedirectUrlEntry.Text.Trim(); if (!String.IsNullOrWhiteSpace(appClientId) && !String.IsNullOrWhiteSpace(oAuthRedirectUrl)) { AppClientId = appClientId; _oAuthRedirectUrl = oAuthRedirectUrl; } OAuthSettingsGrid.IsVisible = false; // Call a function to set up the AuthenticationManager UpdateAuthenticationManager(); } private void LayerSelected(object sender, ItemTappedEventArgs e) { // return if null if (e.Item == null) { return; } // Handle the event when a layer item is selected (tapped) in the layer list string selectedItem = e.Item.ToString(); // See if this is one of the layers in the operational layers list if (_operationalLayerUrls.ContainsKey(selectedItem)) { // Get the service URL from the operational layers dictionary string value = _operationalLayerUrls[selectedItem]; // Call a function to add the chosen operational layer AddLayer(selectedItem, value); } else { // Add the chosen basemap (replace the current one) AddBasemap(selectedItem); } // Hide the layer list LayersList.IsVisible = false; } private void ShowLayerList(object sender, EventArgs e) { // See which button was used to show the list and fill it accordingly Button button = (Button)sender; if (button.Text == "Basemap") { // Show the basemap list LayersList.ItemsSource = _basemapTypes.ToList(); } else if (button.Text == "Layers") { // Show the operational layers list (names) LayersList.ItemsSource = _operationalLayerUrls.Keys; } // Show the layer list view control LayersList.IsVisible = true; } private async void ShowSaveMapUI(object sender, EventArgs e) { // Create a SaveMapPage page for getting user input for the new web map item SaveMapPage mapInputForm = new SaveMapPage(); // If an existing map, show the UI for updating the item Item mapItem = MyMapView.Map.Item; if (mapItem != null) { mapInputForm.ShowForUpdate(mapItem.Title,mapItem.Description, mapItem.Tags.ToArray()); } // Handle the save button click event on the page mapInputForm.OnSaveClicked += SaveMapAsync; // Navigate to the SaveMapPage UI await Navigation.PushAsync(mapInputForm); } // Event handler to get information entered by the user and save the map private async void SaveMapAsync(object sender, SaveMapEventArgs e) { // Get the current map Map myMap = MyMapView.Map; try { // Show the progress bar so the user knows work is happening SaveMapProgressBar.IsVisible = true; // Make sure the user is logged in to ArcGIS Online Credential cred = await EnsureLoggedInAsync(); AuthenticationManager.Current.AddCredential(cred); // Get information entered by the user for the new portal item properties string title = e.MapTitle; string description = e.MapDescription; string[] tags = e.Tags; // Apply the current extent as the map's initial extent myMap.InitialViewpoint = MyMapView.GetCurrentViewpoint(ViewpointType.BoundingGeometry); // Export the current map view for the item's thumbnail RuntimeImage thumbnailImage = await MyMapView.ExportImageAsync(); // See if the map has already been saved (has an associated portal item) if (myMap.Item == null) { // Get the ArcGIS Online portal (will use credential from login above) ArcGISPortal agsOnline = await ArcGISPortal.CreateAsync(new Uri(ArcGISOnlineUrl)); // Save the current state of the map as a portal item in the user's default folder await myMap.SaveAsAsync(agsOnline, null, title, description, tags, thumbnailImage); // Report a successful save await Application.Current.MainPage.DisplayAlert("Map Saved", "Saved '" + title + "' to ArcGIS Online!", "OK"); } else { // This is not the initial save, call SaveAsync to save changes to the existing portal item await myMap.SaveAsync(); // Get the file stream from the new thumbnail image Stream imageStream = await thumbnailImage.GetEncodedBufferAsync(); // Update the item thumbnail ((PortalItem)myMap.Item).SetThumbnail(imageStream); await myMap.SaveAsync(); // Report update was successful await Application.Current.MainPage.DisplayAlert("Updates Saved", "Saved changes to '" + myMap.Item.Title + "'", "OK"); } } catch (Exception ex) { // Show the exception message await Application.Current.MainPage.DisplayAlert("Unable to save map", ex.Message, "OK"); } finally { // Hide the progress bar SaveMapProgressBar.IsVisible = false; } } private async Task<Credential> EnsureLoggedInAsync() { // Challenge the user for portal credentials (OAuth credential request for arcgis.com) Credential cred = null; CredentialRequestInfo loginInfo = new CredentialRequestInfo { // Use the OAuth implicit grant flow GenerateTokenOptions = new GenerateTokenOptions { TokenAuthenticationType = TokenAuthenticationType.OAuthImplicit }, // Indicate the url (portal) to authenticate with (ArcGIS Online) ServiceUri = new Uri(ArcGISOnlineUrl) }; try { // Get the users credentials for ArcGIS Online (should have logged in when launching the page) cred = await AuthenticationManager.Current.GetCredentialAsync(loginInfo, false); } catch (System.OperationCanceledException) { // user canceled the login throw new Exception("Portal log in was canceled."); } return cred; } private void NewMapButtonClick(object sender, EventArgs e) { // Call a function to create a new map CreateNewMap(); } private void CreateNewMap() { // Create new Map with a light gray canvas basemap Map myMap = new Map(Basemap.CreateLightGrayCanvas()); // Add the Map to the MapView MyMapView.Map = myMap; } private void AddBasemap(string basemapName) { // Apply the chosen basemap switch (basemapName) { case "Topographic": // Set the basemap to Topographic MyMapView.Map.Basemap = Basemap.CreateTopographic(); break; case "Streets": // Set the basemap to Streets MyMapView.Map.Basemap = Basemap.CreateStreets(); break; case "Imagery": // Set the basemap to Imagery MyMapView.Map.Basemap = Basemap.CreateImagery(); break; case "Oceans": // Set the basemap to Oceans MyMapView.Map.Basemap = Basemap.CreateOceans(); break; } } private void AddLayer(string layerName, string url) { // See if the layer already exists, and remove it if it does if (MyMapView.Map.OperationalLayers.FirstOrDefault(l => l.Name == layerName) is ArcGISMapImageLayer layer) { MyMapView.Map.OperationalLayers.Remove(layer); } else { // Otherwise, add the layer Uri layerUri = new Uri(url); // Create and add a new map image layer layer = new ArcGISMapImageLayer(layerUri); layer.Name = layerName; layer.Opacity = 0.5; MyMapView.Map.OperationalLayers.Add(layer); } } #region OAuth private void UpdateAuthenticationManager() { // Define the server information for ArcGIS Online ServerInfo portalServerInfo = new ServerInfo { // ArcGIS Online URI ServerUri = new Uri(ArcGISOnlineUrl), // Type of token authentication to use TokenAuthenticationType = TokenAuthenticationType.OAuthImplicit }; // Define the OAuth information OAuthClientInfo oAuthInfo = new OAuthClientInfo { ClientId = AppClientId, RedirectUri = new Uri(_oAuthRedirectUrl) }; portalServerInfo.OAuthClientInfo = oAuthInfo; // Get a reference to the (singleton) AuthenticationManager for the app AuthenticationManager thisAuthenticationManager = AuthenticationManager.Current; // Register the ArcGIS Online server information with the AuthenticationManager thisAuthenticationManager.RegisterServer(portalServerInfo); // Create a new ChallengeHandler that uses a method in this class to challenge for credentials thisAuthenticationManager.ChallengeHandler = new ChallengeHandler(CreateCredentialAsync); // Set the OAuthAuthorizeHandler component (this class) for Android or iOS platforms #if __ANDROID__ || __IOS__ thisAuthenticationManager.OAuthAuthorizeHandler = this; #endif } // ChallengeHandler function that will be called whenever access to a secured resource is attempted public async Task<Credential> CreateCredentialAsync(CredentialRequestInfo info) { Credential credential = null; try { // IOAuthAuthorizeHandler will challenge the user for OAuth credentials credential = await AuthenticationManager.Current.GenerateCredentialAsync(info.ServiceUri); } catch (TaskCanceledException) { return credential; } catch (Exception) { // Exception will be reported in calling function throw; } return credential; } #region IOAuthAuthorizationHandler implementation // Use a TaskCompletionSource to track the completion of the authorization private TaskCompletionSource<IDictionary<string, string>> _taskCompletionSource; // IOAuthAuthorizeHandler.AuthorizeAsync implementation public Task<IDictionary<string, string>> AuthorizeAsync(Uri serviceUri, Uri authorizeUri, Uri callbackUri) { // If the TaskCompletionSource is not null, authorization may already be in progress and should be cancelled if (_taskCompletionSource != null) { // Try to cancel any existing authentication task _taskCompletionSource.TrySetCanceled(); } // Create a task completion source _taskCompletionSource = new TaskCompletionSource<IDictionary<string, string>>(); #if __ANDROID__ || __IOS__ #if __ANDROID__ // Get the current Android Activity Activity activity = (Activity)ArcGISRuntime.Droid.MainActivity.Instance; #endif // Create a new Xamarin.Auth.OAuth2Authenticator using the information passed in Xamarin.Auth.OAuth2Authenticator authenticator = new Xamarin.Auth.OAuth2Authenticator( clientId: AppClientId, scope: "", authorizeUrl: authorizeUri, redirectUrl: callbackUri) { ShowErrors = false }; // Allow the user to cancel the OAuth attempt authenticator.AllowCancel = true; // Define a handler for the OAuth2Authenticator.Completed event authenticator.Completed += (sender, authArgs) => { try { #if __IOS__ // Dismiss the OAuth UI when complete Device.BeginInvokeOnMainThread(() => { var viewController = UIApplication.SharedApplication.KeyWindow.RootViewController; viewController.DismissViewController(true, null); }); #endif // Check if the user is authenticated if (authArgs.IsAuthenticated) { // If authorization was successful, get the user's account Xamarin.Auth.Account authenticatedAccount = authArgs.Account; // Set the result (Credential) for the TaskCompletionSource _taskCompletionSource.SetResult(authenticatedAccount.Properties); } else { throw new Exception("Unable to authenticate user."); } } catch (Exception ex) { // If authentication failed, set the exception on the TaskCompletionSource _taskCompletionSource.TrySetException(ex); // Cancel authentication authenticator.OnCancelled(); } #if __ANDROID__ finally { // Dismiss the OAuth login activity.FinishActivity(99); } #endif }; // If an error was encountered when authenticating, set the exception on the TaskCompletionSource authenticator.Error += (sndr, errArgs) => { // If the user cancels, the Error event is raised but there is no exception ... best to check first if (errArgs.Exception != null) { _taskCompletionSource.TrySetException(errArgs.Exception); } else { // Login canceled: dismiss the OAuth login if (_taskCompletionSource != null) { _taskCompletionSource.TrySetCanceled(); #if __ANDROID__ activity.FinishActivity(99); #endif } } // Cancel authentication authenticator.OnCancelled(); }; // Present the OAuth UI so the user can enter user name and password #if __ANDROID__ var intent = authenticator.GetUI(activity); activity.StartActivityForResult(intent, 99); #endif #if __IOS__ // Present the OAuth UI (on the app's UI thread) so the user can enter user name and password Device.BeginInvokeOnMainThread(() => { var viewController = UIApplication.SharedApplication.KeyWindow.RootViewController; viewController.PresentViewController(authenticator.GetUI(), true, null); }); #endif #endif // Return completion source task so the caller can await completion return _taskCompletionSource.Task; } #endregion #endregion } }
using System; using Xamarin.Forms; namespace ArcGISRuntime.Samples.AuthorMap { public partial class SaveMapPage : ContentPage { // Raise an event so the listener can access input values when the form has been completed public event EventHandler<SaveMapEventArgs> OnSaveClicked; public SaveMapPage () { InitializeComponent (); } // If updating an existing map item, show the existing item info and disable changing info public void ShowForUpdate(string title, string description, string[] tags) { // Item title MapTitleEntry.Text = title; MapTitleEntry.IsEnabled = false; // Item description MapDescriptionEntry.Text = description; MapDescriptionEntry.IsEnabled = false; // Item tags MapTagsEntry.Text = string.Join(",", tags); MapTagsEntry.IsEnabled = false; // Show 'Update' rather than 'Save' for button text SaveMapButton.Text = "Update"; } // A click handler for the save map button private void SaveButtonClicked(object sender, EventArgs e) { try { // Get information for the new portal item string title = MapTitleEntry.Text; string description = MapDescriptionEntry.Text; string[] tags = MapTagsEntry.Text.Split(','); // Make sure all required info was entered if (String.IsNullOrEmpty(title) || String.IsNullOrEmpty(description) || tags.Length == 0) { throw new Exception("Please enter a title, description, and some tags to describe the map."); } // Create a new OnSaveMapEventArgs object to store the information entered by the user SaveMapEventArgs mapSavedArgs = new SaveMapEventArgs(title, description, tags); // Raise the OnSaveClicked event so the main activity can handle the event and save the map OnSaveClicked?.Invoke(this, mapSavedArgs); // Close the dialog Navigation.PopAsync(); } catch (Exception ex) { // Show the exception message (dialog will stay open so user can try again) Application.Current.MainPage.DisplayAlert("Error", ex.Message, "OK"); } } private void CancelButtonClicked(object sender, EventArgs e) { // If the user cancels, just navigate back to the previous page Navigation.PopAsync(); } } // Custom EventArgs class for containing portal item properties when saving a map public class SaveMapEventArgs : EventArgs { // Portal item title public string MapTitle { get; set; } // Portal item description public string MapDescription { get; set; } // Portal item tags public string[] Tags { get; set; } public SaveMapEventArgs(string title, string description, string[] tags) : base() { MapTitle = title; MapDescription = description; Tags = tags; } } }
<?xml version="1.0" encoding="utf-8" ?> <ContentPage x:Class="ArcGISRuntime.Samples.AuthorMap.AuthorMap" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:esriUI="clr-namespace:Esri.ArcGISRuntime.Xamarin.Forms;assembly=Esri.ArcGISRuntime.Xamarin.Forms" xmlns:mapping="clr-namespace:Esri.ArcGISRuntime.Mapping;assembly=Esri.ArcGISRuntime"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <esriUI:MapView x:Name="MyMapView" Grid.Row="0"/> <ActivityIndicator x:Name="SaveMapProgressBar" IsRunning="True" Grid.Row="1" IsVisible="False"/> <ListView x:Name="LayersList" Grid.Row="0" Grid.RowSpan="2" IsVisible="False" ItemTapped="LayerSelected"> </ListView> <Grid x:Name="OAuthSettingsGrid" Grid.Row="0" HorizontalOptions="Center" VerticalOptions="Center" WidthRequest="300"> <Grid.RowDefinitions> <RowDefinition Height="60"/> <RowDefinition Height="40"/> <RowDefinition Height="40"/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Label Grid.Row="0" Grid.ColumnSpan="2" HorizontalOptions="Center" VerticalOptions="Start" Text="OAuth Settings"/> <Label Grid.Row="1" HorizontalOptions="End" VerticalOptions="Center" Text="Client ID:"/> <Entry x:Name="ClientIDEntry" Grid.Row="1" Grid.Column="1" Placeholder="mYCl1enTiD" HorizontalOptions="Start" VerticalOptions="Center" WidthRequest="240"/> <Label Grid.Row="2" HorizontalOptions="End" VerticalOptions="Center" Text="Redirect URL:"/> <Entry x:Name="RedirectUrlEntry" Grid.Row="2" Grid.Column="1" Placeholder="http://my.redirect/url" HorizontalOptions="Start" VerticalOptions="Center" WidthRequest="240"/> <Button Grid.Row="3" Grid.Column="0" HorizontalOptions="End" VerticalOptions="End" Text="Cancel" Clicked="OAuthSettingsCancel"/> <Button Grid.Row="3" Grid.Column="1" HorizontalOptions="CenterAndExpand" VerticalOptions="End" Text="Use Settings" Clicked="SaveOAuthSettings"/> </Grid> <Grid Grid.Row="2" Margin="5,2"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <Button Grid.Column="0" Text="Basemap" Clicked="ShowLayerList"/> <Button Grid.Column="1" Text="Layers" Clicked="ShowLayerList"/> <Button Grid.Column="2" Text="New" Clicked="NewMapButtonClick"/> <Button Grid.Column="3" Text="Save" Clicked="ShowSaveMapUI"/> </Grid> </Grid> </ContentPage>
<?xml version="1.0" encoding="utf-8" ?> <ContentPage x:Class="ArcGISRuntime.Samples.AuthorMap.SaveMapPage" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"> <ContentPage.Content> <TableView x:Name="SaveMapUI" Grid.Row="0" Intent="Form"> <TableRoot> <TableSection Title="Map Information"> <EntryCell x:Name="MapTitleEntry" Label="Title:" Placeholder="Required"/> <EntryCell x:Name="MapDescriptionEntry" Label="Description:" Placeholder="Required"/> <EntryCell x:Name="MapTagsEntry" Label="Tags:" Text="ArcGIS Runtime, Webmap"/> <ViewCell> <StackLayout Orientation="Horizontal" HorizontalOptions="CenterAndExpand"> <Button Text="Cancel" Clicked="CancelButtonClicked" Margin="20,0"/> <Button x:Name="SaveMapButton" Text="Save" Clicked="SaveButtonClicked"/> </StackLayout> </ViewCell> </TableSection> </TableRoot> </TableView> </ContentPage.Content> </ContentPage>
Hyperlink to Example | Description |
---|---|
AuthorMap | Create and save a map as an ArcGIS `PortalItem` (i.e. web map). |