Skip to content

Generate offline map (overrides)

View on GitHubSample viewer app

Take a web map offline with additional options for each layer.

Image of generate offline map overrides

Use case

When taking a web map offline, you may adjust the data (such as layers or tiles) that are downloaded by using custom parameter overrides. This can be used to reduce the extent of the map or the download size of the offline map, or to highlight specific data by removing irrelevant data. Additionally, this workflow allows you to take features offline that don't have a geometry; for example, features whose attributes have been populated in the office, but still need a site survey for their geometry.

How to use the sample

Modify the overrides parameters:

  • Use the min/max scale input fields to adjust the level IDs to be taken offline for the streets basemap.
  • Use the "Extent Buffer Distance" input field to set the buffer radius for the streets basemap.
  • Check the checkboxes for the feature operational layers you want to include in the offline map.
  • Use the "Min Hydrant Flow Rate" input field to only download features with a flow rate higher than this value.
  • Select the "Water Pipes" checkbox if you want to crop the water pipe features to the extent of the map.

Set up the overrides to your liking, click the "Generate offline map" button to start the download. A progress bar will display. Click the "Cancel" button if you want to stop the download. When the download is complete, the view will display the offline map. Pan around to see that the map is cropped to the download area's extent.

How it works

  1. Load a web map from a PortalItem. Authenticate with the portal if required.
  2. Create an OfflineMapTask with the map.
  3. Generate default task parameters using the extent area you want to download with offlineMapTask.createDefaultGenerateOfflineMapParametersAsync(extent).
  4. Generate additional "override" parameters using the default parameters with offlineMapTask.createGenerateOfflineMapParameterOverridesAsync(parameters).
  5. For the basemap:
    • Get the parameters OfflineMapParametersKey for the basemap layer.
    • Get the ExportTileCacheParameters for the basemap layer with overrides.getExportTileCacheParameters().get(basemapParamKey).
    • Set the level IDs you want to download with exportTileCacheParameters.getLevelIDs().add(levelID).
    • To buffer the extent, use exportTileCacheParameters.setAreaOfInterest(bufferedGeometry) where bufferedGeometry can be calculated with the GeometryEngine.
  6. To remove operational layers from the download:
    • Create a OfflineParametersKey with the operational layer.
    • Get the generate geodatabase layer options using the key with List<GenerateLayerOption> layerOptions = overrides.getGenerateGeodatabaseParameters().get(key).getLayerOptions();
    • Loop through each GenerateLayerOption in the the list, and remove it if the layer option's ID matches the layer's ID.
  7. To filter the features downloaded in an operational layer:
    • Get the layer options for the operational layer using the directions in step 6.
    • Loop through the layer options. If the option layerID matches the layer's ID, set the filter clause with layerOption.setWhereClause(sqlQueryString) and set the query option with layerOption.setQueryOption(GenerateLayerOption.QueryOption.USE_FILTER).
  8. To not crop a layer's features to the extent of the offline map (default is true):
    • Set layerOption.setUseGeometry(false).
  9. Create a GenerateOfflineMapJob with offlineMapTask.generateOfflineMap(parameters, downloadPath, overrides). Start the job with job.start().
  10. When the job is done, get a reference to the offline map with job.getResult.getOfflineMap().

Relevant API

  • ExportTileCacheParameters
  • GenerateGeodatabaseParameters
  • GenerateLayerOption
  • GenerateOfflineMapJob
  • GenerateOfflineMapParameterOverrides
  • GenerateOfflineMapParameters
  • GenerateOfflineMapResult
  • OfflineMapParametersKey
  • OfflineMapTask

Additional information

For applications where you just need to take all layers offline, use the standard workflow (using only GenerateOfflineMapParameters). For a simple example of how you take a map offline, please consult the "Generate offline map" sample.

Tags

adjust, download, extent, filter, LOD, offline, override, parameters, reduce, scale range, setting

Sample Code

GenerateOfflineMapOverridesController.javaGenerateOfflineMapOverridesController.javaGenerateOfflineMapOverridesSample.java
Use dark colors for code blocksCopy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 /*  * 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.  */  package com.esri.samples.generate_offline_map_overrides;  import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Iterator; import java.util.List;  import com.esri.arcgisruntime.ArcGISRuntimeEnvironment; import com.esri.arcgisruntime.data.ServiceFeatureTable; import com.esri.arcgisruntime.geometry.Envelope; import com.esri.arcgisruntime.geometry.GeometryEngine; import com.esri.arcgisruntime.geometry.Point; import com.esri.arcgisruntime.layers.FeatureLayer; import com.esri.arcgisruntime.loadable.LoadStatus; import com.esri.arcgisruntime.mapping.ArcGISMap; import com.esri.arcgisruntime.mapping.view.Graphic; import com.esri.arcgisruntime.mapping.view.GraphicsOverlay; import com.esri.arcgisruntime.mapping.view.MapView; import com.esri.arcgisruntime.portal.Portal; import com.esri.arcgisruntime.portal.PortalItem; import com.esri.arcgisruntime.symbology.SimpleLineSymbol; import com.esri.arcgisruntime.tasks.geodatabase.GenerateGeodatabaseParameters; import com.esri.arcgisruntime.tasks.geodatabase.GenerateLayerOption; import com.esri.arcgisruntime.tasks.offlinemap.GenerateOfflineMapJob; import com.esri.arcgisruntime.tasks.offlinemap.GenerateOfflineMapResult; import com.esri.arcgisruntime.tasks.offlinemap.OfflineMapParametersKey; import com.esri.arcgisruntime.tasks.offlinemap.OfflineMapTask; import com.esri.arcgisruntime.tasks.tilecache.ExportTileCacheParameters;  import javafx.application.Platform; import javafx.fxml.FXML; import javafx.geometry.Point2D; import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.ProgressBar; import javafx.scene.control.Spinner; import javafx.scene.paint.Color;  public class GenerateOfflineMapOverridesController {   @FXML private MapView mapView;  @FXML private Spinner<Integer> minScaleLevelSpinner;  @FXML private Spinner<Integer> maxScaleLevelSpinner;  @FXML private Spinner<Integer> extentBufferDistanceSpinner;  @FXML private Spinner<Integer> minHydrantFlowRateSpinner;  @FXML private CheckBox systemValvesCheckBox;  @FXML private CheckBox serviceConnectionsCheckBox;  @FXML private CheckBox waterPipesCheckBox;  @FXML private Button generateOfflineMapButton;  @FXML private Button cancelJobButton;  @FXML private ProgressBar progressBar;   private ArcGISMap map;  private GraphicsOverlay graphicsOverlay;  private Graphic downloadArea;  private GenerateOfflineMapJob job;   @FXML  private void initialize() {   // authentication with an API key or named user is required to access basemaps and other location services  String yourAPIKey = System.getProperty("apiKey");  ArcGISRuntimeEnvironment.setApiKey(yourAPIKey);   // create a portal item with the itemId of the web map  var portal = new Portal("https://www.arcgis.com");  var portalItem = new PortalItem(portal, "acc027394bc84c2fb04d1ed317aac674");   // create a map with the portal item  map = new ArcGISMap(portalItem);  // display the generate offline map area as a red box on the map  updateDownloadArea();  map.addDoneLoadingListener(() -> {  // enable the generate offline map button when the map is loaded  if (map.getLoadStatus() == LoadStatus.LOADED) {  generateOfflineMapButton.setDisable(false);   // create a graphics overlay for displaying the download area  graphicsOverlay = new GraphicsOverlay();  mapView.getGraphicsOverlays().add(graphicsOverlay);   // show a red border around the download area  downloadArea = new Graphic();  graphicsOverlay.getGraphics().add(downloadArea);  var simpleLineSymbol = new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.RED, 2);  downloadArea.setSymbol(simpleLineSymbol);   }  });   // update the download area whenever the viewpoint changes  mapView.addViewpointChangedListener(viewpointChangedEvent -> updateDownloadArea());   // set the map to the map view  mapView.setMap(map);  }   /**  * Called when the Generate offline map button is clicked. Builds parameters for the offline map task from the UI  * inputs and executes the task.  */  @FXML  private void generateOfflineMap() {  // show the progress bar  progressBar.setVisible(true);   // create an offline map task with the map  var offlineMapTask = new OfflineMapTask(map);   // get default offline map parameters for this task given the download area  offlineMapTask.createDefaultGenerateOfflineMapParametersAsync(downloadArea.getGeometry())  .toCompletableFuture().whenComplete((parameters, exception) -> {  if (exception == null) {  // get additional offline parameters (overrides) for this task  offlineMapTask.createGenerateOfflineMapParameterOverridesAsync(parameters).toCompletableFuture()  .whenComplete((overrides, throwable) -> {  if (throwable == null) {  try {  // get the export tile cache parameters for the base layer  var basemapParamKey = new OfflineMapParametersKey(  mapView.getMap().getBasemap().getBaseLayers().get(0));  ExportTileCacheParameters exportTileCacheParameters =  overrides.getExportTileCacheParameters().get(basemapParamKey);   // create a new sublist of level IDs in the range requested by the user  exportTileCacheParameters.getLevelIDs().clear();  for (int i = minScaleLevelSpinner.getValue(); i < maxScaleLevelSpinner.getValue(); i++) {  exportTileCacheParameters.getLevelIDs().add(i);  }  // set the area of interest to the original download area plus a buffer  exportTileCacheParameters.setAreaOfInterest(GeometryEngine.buffer(downloadArea.getGeometry(),  extentBufferDistanceSpinner.getValue()));   // configure layer option parameters for each layer depending on the options selected in the UI  map.getOperationalLayers().stream()  .filter(layer -> layer instanceof FeatureLayer)  .map(featureLayer -> (FeatureLayer) featureLayer)  .forEach(featureLayer -> {  ServiceFeatureTable featureTable = (ServiceFeatureTable) featureLayer.getFeatureTable();  long layerId = featureTable.getLayerInfo().getServiceLayerId();  // get the layer option parameters specifically for this layer  var offlineMapParametersKey = new OfflineMapParametersKey(featureLayer);  GenerateGeodatabaseParameters generateGeodatabaseParameters = overrides.getGenerateGeodatabaseParameters()  .get(offlineMapParametersKey);  List<GenerateLayerOption> layerOptions = generateGeodatabaseParameters.getLayerOptions();  // use an iterator so we can remove layer options while looping over them  Iterator<GenerateLayerOption> layerOptionsIterator = layerOptions.iterator();  if (!layerOptions.isEmpty()) {  while (layerOptionsIterator.hasNext()) {  GenerateLayerOption layerOption = layerOptionsIterator.next();  if (layerOption.getLayerId() == layerId) {  switch (featureLayer.getName()) {  // remove the System Valve layer from the layer options if it should not be included  case "System Valve":  if (!systemValvesCheckBox.isSelected()) {  layerOptionsIterator.remove();  }  break;  // remove the Service Connection layer from the layer options if it should not be included  case "Service Connection":  if (!serviceConnectionsCheckBox.isSelected()) {  layerOptionsIterator.remove();  }  break;  // only download hydrant features if their flow is above the minimum specified in the UI  case "Hydrant":  layerOption.setWhereClause("FLOW >= " + minHydrantFlowRateSpinner.getValue());  layerOption.setQueryOption(GenerateLayerOption.QueryOption.USE_FILTER);  break;  // clip water main feature geometries to the extent if the checkbox is selected  case "Main":  layerOption.setUseGeometry(waterPipesCheckBox.isSelected());  }  }  }  }  });   // create an offline map job with the download directory path and parameters and start the job  Path tempDirectory = Files.createTempDirectory("offline_map");  job = offlineMapTask.generateOfflineMap(parameters, tempDirectory.toAbsolutePath().toString(), overrides);  job.start();  generateOfflineMapButton.setDisable(true);  cancelJobButton.setDisable(false);  job.addJobDoneListener(() -> {  if (job.getStatus() == GenerateOfflineMapJob.Status.SUCCEEDED) {  // replace the current map with the result offline map when the job finishes  GenerateOfflineMapResult result = job.getResult();  mapView.setMap(result.getOfflineMap());  graphicsOverlay.getGraphics().clear();  // disable button since the offline map is already generated  generateOfflineMapButton.setDisable(true);  } else {  new Alert(Alert.AlertType.WARNING, job.getError().getMessage()).show();  generateOfflineMapButton.setDisable(false);  }  Platform.runLater(() -> progressBar.setVisible(false));  cancelJobButton.setDisable(true);  });  // show the job's progress with the progress bar  job.addProgressChangedListener(() -> progressBar.setProgress(job.getProgress() / 100.0));  } catch (IOException e){  new Alert(Alert.AlertType.ERROR, "Error configuring the offline map task").show();  }  } else {  // if the generation of offline map parameter overrides completed exceptionally, display an error  new Alert(Alert.AlertType.ERROR, "Error creating override parameters").show();  }  });  } else {  // if the generation of default offline map parameters completed exceptionally, display an error  new Alert(Alert.AlertType.ERROR, "Error creating offline map task default parameters").show();  }  });  }   /**  * Updates the download area graphic to show a red border around the current view extent that will be downloaded if  * taken offline.  */  private void updateDownloadArea() {  if (map.getLoadStatus() == LoadStatus.LOADED) {  // upper left corner of the area to take offline  Point2D minScreenPoint = new Point2D(50, 50);  // lower right corner of the downloaded area  Point2D maxScreenPoint = new Point2D(mapView.getWidth() - 50, mapView.getHeight() - 50);  // convert screen points to map points  Point minPoint = mapView.screenToLocation(minScreenPoint);  Point maxPoint = mapView.screenToLocation(maxScreenPoint);  // use the points to define and return an envelope  if (minPoint != null && maxPoint != null) {  var envelope = new Envelope(minPoint, maxPoint);  downloadArea.setGeometry(envelope);  }  }  }   /**  * Cancels the current offline map job.  */  @FXML  private void cancelJob() {  if (job != null) {  job.cancelAsync();  }  }   /**  * Stops and releases all resources used in the application.  */  void terminate() {   if (mapView != null) {  mapView.dispose();  }  }  }

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.