Skip To Content ArcGIS for Developers Sign In Dashboard

Generate Offline Map (Overrides)


 * 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
 * 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.


import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;

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 com.esri.arcgisruntime.concurrent.ListenableFuture;
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.layers.Layer;
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.GenerateOfflineMapParameterOverrides;
import com.esri.arcgisruntime.tasks.offlinemap.GenerateOfflineMapParameters;
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;

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;

  private void initialize() {
    // handle authentication with the portal
    AuthenticationManager.setAuthenticationChallengeHandler(new DefaultAuthenticationChallengeHandler());

    // create a portal item with the itemId of the web map
    Portal portal = new Portal("", true);
    PortalItem portalItem = new PortalItem(portal, "acc027394bc84c2fb04d1ed317aac674");

    // create a map with the portal item
    map = new ArcGISMap(portalItem);
    map.addDoneLoadingListener(() -> {
      // enable the generate offline map button when the map is loaded
      if (map.getLoadStatus() == LoadStatus.LOADED) {

        // create a graphics overlay for displaying the download area
        graphicsOverlay = new GraphicsOverlay();

        // show a red border around the download area
        downloadArea = new Graphic();
        SimpleLineSymbol simpleLineSymbol = new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, 0xFFFF0000, 2);


    // update the download area whenever the viewpoint changes
    mapView.addViewpointChangedListener(viewpointChangedEvent -> updateDownloadArea());

    // set the map to the map view

   * Called when the Generate offline map button is clicked. Builds parameters for the offline map task from the UI
   * inputs and executes the task.
  private void generateOfflineMap() {
    try {
      // show the progress bar

      // create an offline map task with the map
      OfflineMapTask offlineMapTask = new OfflineMapTask(map);

      // get default offline map parameters for this task given the download area
      ListenableFuture<GenerateOfflineMapParameters> generateOfflineMapParametersFuture = offlineMapTask

      generateOfflineMapParametersFuture.addDoneListener(() -> {
        try {
          final GenerateOfflineMapParameters parameters = generateOfflineMapParametersFuture.get();

          // get additional offline parameters (overrides) for this task
          ListenableFuture<GenerateOfflineMapParameterOverrides> parameterOverridesFuture = offlineMapTask

          parameterOverridesFuture.addDoneListener(() -> {
            try {
              GenerateOfflineMapParameterOverrides overrides = parameterOverridesFuture.get();

              // get the export tile cache parameters for the base layer
              OfflineMapParametersKey basemapParamKey = new OfflineMapParametersKey(
              ExportTileCacheParameters exportTileCacheParameters =

              // create a new sublist of level IDs in the range requested by the user
              for (int i = minScaleLevelSpinner.getValue(); i < maxScaleLevelSpinner.getValue(); i++) {
              // set the area of interest to the original download area plus a buffer

              // configure layer option parameters for each layer depending on the options selected in the UI
              for (Layer layer : map.getOperationalLayers()) {
                if (layer instanceof FeatureLayer) {
                  FeatureLayer featureLayer = (FeatureLayer) layer;
                  ServiceFeatureTable featureTable = (ServiceFeatureTable) featureLayer.getFeatureTable();
                  long layerId = featureTable.getLayerInfo().getServiceLayerId();
                  // get the layer option parameters specifically for this layer
                  OfflineMapParametersKey key = new OfflineMapParametersKey(layer);
                  GenerateGeodatabaseParameters generateGeodatabaseParameters =
                  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 =;
                      if (layerOption.getLayerId() == layerId) {
                        switch (layer.getName()) {
                          // remove the System Valve layer from the layer options if it should not be included
                          case "System Valve":
                            if (!systemValvesCheckBox.isSelected()) {
                          // remove the Service Connection layer from the layer options if it should not be included
                          case "Service Connection":
                            if (!serviceConnectionsCheckBox.isSelected()) {
                          // only download hydrant features if their flow is above the minimum specified in the UI
                          case "Hydrant":
                            layerOption.setWhereClause("FLOW >= " + minHydrantFlowRateSpinner.getValue());
                          //clip water main feature geometries to the extent if the checkbox is selected
                          case "Main":

              // 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.addJobDoneListener(() -> {
                if (job.getStatus() == GenerateOfflineMapJob.Status.SUCCEEDED) {
                  // replace the current map with the result offline map when the job finishes
                  GenerateOfflineMapResult result = job.getResult();
                  // disable button since the offline map is already generated
                } else {
                  new Alert(Alert.AlertType.WARNING, job.getError().getAdditionalMessage()).show();
                Platform.runLater(() -> progressBar.setVisible(false));
              // show the job's progress with the progress bar
              job.addProgressChangedListener(() -> progressBar.setProgress(job.getProgress() / 100.0));
            } catch (ExecutionException | InterruptedException | IOException ex) {
              new Alert(Alert.AlertType.ERROR, "Error configuring the offline map task").show();
        } catch (Exception ex) {
          new Alert(Alert.AlertType.ERROR, "Error creating override parameters").show();
    } catch (Exception ex) {
      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) {
        Envelope envelope = new Envelope(minPoint, maxPoint);

   * Cancels the current offline map job.
  private void cancelJob() {
    if (job != null) {

   * Stops the animation and disposes of application resources.
  void terminate() {

    if (mapView != null) {


In this topic
  1. Code