Generate a local geodatabase from an online feature service.
Use case
Generating geodatabases is the first step toward taking a feature service offline. It allows you to save features locally for offline display.
How to use the sample
Zoom to any extent. Then click the generate button to generate a geodatabase of features from a feature service filtered to the current extent. A red outline will show the extent used. The job's progress is shown while the geodatabase is generated.
How it works
- Create a
GeodatabaseSyncTaskwith the URL of the feature service and load it. - Create
GenerateGeodatabaseReplicaParametersspecifying the extent and whether to include attachments. - Create a
GenerateGeodatabaseReplicaJobwithgeodatabaseSyncTask.GenerateGeodatabaseReplicaAsync(parameters, downloadPath). Start the job withjob.Start(). - When the job is done,
job.GetResultAsync()will return the geodatabase. Inside the geodatabase are feature tables which can be used to add feature layers to the map. - Call
syncTask.UnregisterGeodatabaseAsync(geodatabase)after generation when you're not planning on syncing changes to the service.
Relevant API
- GenerateGeodatabaseReplicaJob
- GenerateGeodatabaseReplicaParameters
- Geodatabase
- GeodatabaseSyncTask
Tags
disconnected, local geodatabase, offline, replica, sync
Sample Code
// Copyright 2018 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 ArcGIS.Samples.Managers; using Esri.ArcGISRuntime.ArcGISServices; using Esri.ArcGISRuntime.Data; using Esri.ArcGISRuntime.Geometry; using Esri.ArcGISRuntime.Mapping; using Esri.ArcGISRuntime.Symbology; using Esri.ArcGISRuntime.Tasks; using Esri.ArcGISRuntime.Tasks.Offline; using Esri.ArcGISRuntime.UI; using System; using System.Drawing; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows; namespace ArcGIS.WPF.Samples.GenerateGeodatabaseReplica { [ArcGIS.Samples.Shared.Attributes.Sample( name: "Generate geodatabase replica from feature service", category: "Data", description: "Generate a local geodatabase from an online feature service.", instructions: "Zoom to any extent. Then click the generate button to generate a geodatabase of features from a feature service filtered to the current extent. A red outline will show the extent used. The job's progress is shown while the geodatabase is generated.", tags: new[] { "disconnected", "local geodatabase", "offline", "replica", "sync" })] [ArcGIS.Samples.Shared.Attributes.OfflineData("e4a398afe9a945f3b0f4dca1e4faccb5")] public partial class GenerateGeodatabaseReplica { // URI for a feature service that supports geodatabase generation. private Uri _featureServiceUri = new Uri("https://sampleserver6.arcgisonline.com/arcgis/rest/services/Sync/WildfireSync/FeatureServer"); // Path to the geodatabase file on disk. private string _gdbPath; // Task to be used for generating the geodatabase. private GeodatabaseSyncTask _gdbSyncTask; // Job used to generate the geodatabase. private GenerateGeodatabaseJob _generateGdbJob; public GenerateGeodatabaseReplica() { InitializeComponent(); // Create the UI, setup the control references and execute initialization. _ = Initialize(); } private async Task Initialize() { try { // Create a tile cache from a locally downloaded file. TileCache tileCache = new TileCache(DataManager.GetDataFolder("e4a398afe9a945f3b0f4dca1e4faccb5", "SanFrancisco.tpkx")); // Create the corresponding layer based on the tile cache. ArcGISTiledLayer tileLayer = new ArcGISTiledLayer(tileCache); // Create the basemap based on the tile cache. Basemap sfBasemap = new Basemap(tileLayer); // Create the map with the tile-based basemap. Map myMap = new Map(sfBasemap); // Assign the map to the MapView. MyMapView.Map = myMap; // Create a new symbol for the extent graphic. SimpleLineSymbol lineSymbol = new SimpleLineSymbol(SimpleLineSymbolStyle.Solid, Color.Red, 2); // Create graphics overlay for the extent graphic and apply a renderer. GraphicsOverlay extentOverlay = new GraphicsOverlay { Renderer = new SimpleRenderer(lineSymbol) }; // Add graphics overlay to the map view. MyMapView.GraphicsOverlays.Add(extentOverlay); // Set up an event handler for when the viewpoint (extent) changes. MyMapView.ViewpointChanged += UpdateMapExtent; // Create a task for generating a geodatabase (GeodatabaseSyncTask). _gdbSyncTask = await GeodatabaseSyncTask.CreateAsync(_featureServiceUri); // Add all graphics from the service to the map. foreach (IdInfo layer in _gdbSyncTask.ServiceInfo.LayerInfos) { // Create the ServiceFeatureTable for this layer. ServiceFeatureTable onlineTable = new ServiceFeatureTable(new Uri(_featureServiceUri + "/" + layer.Id)); // Wait for the table to load. await onlineTable.LoadAsync(); // Add the layer to the map's operational layers if load succeeds. if (onlineTable.LoadStatus == Esri.ArcGISRuntime.LoadStatus.Loaded) { myMap.OperationalLayers.Add(new FeatureLayer(onlineTable)); } } // Update the graphic - needed in case the user decides not to interact before pressing the button. UpdateMapExtent(null, null); // Enable the generate button. MyGenerateButton.IsEnabled = true; } catch (Exception ex) { MessageBox.Show(ex.ToString()); } } private void UpdateMapExtent(Object sender, EventArgs e) { // Return if mapview is null. if (MyMapView == null) { return; } // Get the new viewpoint. Viewpoint myViewPoint = MyMapView.GetCurrentViewpoint(ViewpointType.BoundingGeometry); // Return if viewpoint is null. if (myViewPoint == null) { return; } // Get the updated extent for the new viewpoint. Envelope extent = myViewPoint.TargetGeometry as Envelope; // Return if extent is null. if (extent == null) { return; } // Create an envelope that is a bit smaller than the extent. EnvelopeBuilder envelopeBldr = new EnvelopeBuilder(extent); envelopeBldr.Expand(0.80); // Get the (only) graphics overlay in the map view. GraphicsOverlay extentOverlay = MyMapView.GraphicsOverlays.FirstOrDefault(); // Return if the extent overlay is null. if (extentOverlay == null) { return; } // Get the extent graphic. Graphic extentGraphic = extentOverlay.Graphics.FirstOrDefault(); // Create the extent graphic and add it to the overlay if it doesn't exist. if (extentGraphic == null) { extentGraphic = new Graphic(envelopeBldr.ToGeometry()); extentOverlay.Graphics.Add(extentGraphic); } else { // Otherwise, simply update the graphic's geometry. extentGraphic.Geometry = envelopeBldr.ToGeometry(); } } private async Task StartGeodatabaseGeneration() { // Create a task for generating a geodatabase (GeodatabaseSyncTask). _gdbSyncTask = await GeodatabaseSyncTask.CreateAsync(_featureServiceUri); // Get the current extent of the red preview box. Envelope extent = MyMapView.GraphicsOverlays.First().Graphics.First().Geometry as Envelope; // Get the default parameters for the generate geodatabase task. GenerateGeodatabaseParameters generateParams = await _gdbSyncTask.CreateDefaultGenerateGeodatabaseParametersAsync(extent); // Create a generate geodatabase job. _generateGdbJob = _gdbSyncTask.GenerateGeodatabase(generateParams, _gdbPath); // Handle the progress changed event (to show progress bar). _generateGdbJob.ProgressChanged += (sender, e) => { UpdateProgressBar(); }; // Show the progress bar. MyProgressBar.Visibility = Visibility.Visible; // Start the job. _generateGdbJob.Start(); // Get the result. Geodatabase resultGdb = await _generateGdbJob.GetResultAsync(); // Hide the progress bar. MyProgressBar.Visibility = Visibility.Hidden; // Do the rest of the work. await HandleGenerationCompleted(resultGdb); } private async Task HandleGenerationCompleted(Geodatabase resultGdb) { // If the job completed successfully, add the geodatabase data to the map, // removing the version from the service. if (_generateGdbJob.Status == JobStatus.Succeeded) { MyMapView.Map.OperationalLayers.Clear(); // Loop through all feature tables in the geodatabase and add a new layer to the map. foreach (GeodatabaseFeatureTable table in resultGdb.GeodatabaseFeatureTables) { // Create a new feature layer for the table. FeatureLayer layer = new FeatureLayer(table); // Add the new layer to the map. MyMapView.Map.OperationalLayers.Add(layer); } // Best practice is to unregister the geodatabase. await _gdbSyncTask.UnregisterGeodatabaseAsync(resultGdb); // Tell the user that the geodatabase was unregistered. MessageBox.Show("Since no edits will be made, the local geodatabase has been unregistered per best practice."); } else { // Create a message to show the user. string message = "Generate geodatabase job failed"; // Show an error message (if there is one). if (_generateGdbJob.Error != null) { message += ": " + _generateGdbJob.Error.Message; } else { // If no error, show messages from the _generateGdbJob. message += ": " + string.Join("\n", _generateGdbJob.Messages.Select(m => m.Message)); } MessageBox.Show(message); } } private async void GenerateButton_Clicked(object sender, RoutedEventArgs e) { // Fix the extent of the graphic. MyMapView.ViewpointChanged -= UpdateMapExtent; // Update the geodatabase path for the new run. try { _gdbPath = Path.Combine(Environment.ExpandEnvironmentVariables("%TEMP%"), Path.GetTempFileName() + ".geodatabase"); // Prevent the user from clicking twice - errors happen. MyGenerateButton.IsEnabled = false; // Call the cross-platform geodatabase generation method. await StartGeodatabaseGeneration(); } catch (Exception ex) { MessageBox.Show(ex.ToString()); } } private void UpdateProgressBar() { // Due to the nature of the threading implementation, // the dispatcher needs to be used to interact with the UI. Dispatcher.Invoke(() => { // Update the progress bar value. MyProgressBar.Value = _generateGdbJob.Progress / 1.0; }); } } }