Download tile cache

Download Sample Viewer

Description

This application shows how to create a tile cache, either as a tile package (.tpk file) or compact cache, for offline use. The tile cache is created from an online service which supports the exportTiles operation using the API's ExportTileCacheTask class. Among the parameters for this task, you can set which levels of details you would like in the tile cache. For simplicity, the levels are hard-coded in this application.

Code snippet


  // set up parameters
  ExportTileCacheParameters params = new ExportTileCacheParameters(
     true,  // for tile package, false for a compact cache
     levels, 
     ExportBy.ID, 
     extent,
     map.getSpatialReference());
  
  // generate tile cache
  ExportTileCacheTask exportTileCacheTask = new ExportTileCacheTask(SERVICE_URL, null);
  exportTileCacheTask.generateTileCache(
      params, // parameters
      statusCallback, // callback after task is submitted
      downloadCallback, // callback after task is completed
      filePath); // path for download tile cache
  

Sample Code

/* Copyright 2014 Esri

All rights reserved under the copyright laws of the United States
and applicable international laws, treaties, and conventions.

You may freely redistribute and use this sample code, with or
without modification, provided you include the original copyright
notice and use restrictions.

See the use restrictions.*/
package com.esri.client.samples.tiledlayers;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Rectangle2D;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;

import com.esri.client.local.ArcGISLocalTiledLayer;
import com.esri.core.geometry.Envelope;
import com.esri.core.geometry.Geometry;
import com.esri.core.map.CallbackListener;
import com.esri.core.tasks.tilecache.ExportTileCacheParameters;
import com.esri.core.tasks.tilecache.ExportTileCacheParameters.ExportBy;
import com.esri.core.tasks.tilecache.ExportTileCacheStatus;
import com.esri.core.tasks.tilecache.ExportTileCacheTask;
import com.esri.map.ArcGISTiledMapServiceLayer;
import com.esri.map.JMap;
import com.esri.map.MapOverlay;

/***
 * This application shows how to create a tile cache, either as a tile package 
 * (.tpk file) or compact cache, for offline use. The tile cache is created 
 * from an online service which supports the 'exportTiles' operation, using 
 * the API's <code>ExportTileCacheTask</code> class.
 * <p>
 * Among the parameters for this task, you can set which levels of details you 
 * would like in the tile cache. For simplicity, the levels are hard-coded in 
 * this application.
 */
public class OfflineTiledLayer {

  private static final String SERVICE_URL =
      "http://sampleserver6.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer";
  private static final String DEFAULT_PATH = System.getProperty("user.home") + System.getProperty("file.separator") + "MyTpk.tpk";
  private static final Dimension BUTTON_DIM = new Dimension(200, 30);

  private ArcGISTiledMapServiceLayer tiledLayer;

  private JMap map;
  private JPanel contentPane;
  private SelectExtentOverlay overlay;

  private JProgressBar mainProgressBar;

  private JProgressBar secondaryProgressBar;
  private JTextField textField;
  private JButton tpkButton;
  private JToggleButton extentButton;
  protected boolean isReadyToDownload;
  private JCheckBox cbTpk;
  private JCheckBox recompress;
  private JTextField recompressQuality;
  private ExportTileCacheTask exportTileCacheTask;

  // ------------------------------------------------------------------------
  // Default constructor
  // ------------------------------------------------------------------------
  public OfflineTiledLayer() {}

  // ------------------------------------------------------------------------
  // Core Functionality
  // ------------------------------------------------------------------------

  private void onCreateTileCacheButtonClick(Geometry extent) {
    // create the parameters object
    ExportTileCacheParameters params = createParameters(extent);

    final String tileCachePath = textField.getText();
    createTileCache(params, tileCachePath);
  }

  //for this service, estimates will not change based on re-compress options since tiles are already compressed, 
  protected void onEstimateSizeButtonClick(Geometry extent) {
    // create the parameters object
    ExportTileCacheParameters params = createParameters(extent);

    updateProgressBarUI("Estimating tile cache size...", true);
    exportTileCacheTask.estimateTileCacheSize(params, new CallbackListener<Long>() {
      boolean errored = false;

      @Override
      public void onError(Throwable e) {
        errored = true;
        updateProgressBarUI(null, false);
        e.printStackTrace();
      }

      @Override
      public void onCallback(Long size) {
        updateProgressBarUI(null, false);
        long sizeKb = size.longValue()/1000;
        if (!errored) {
          JOptionPane.showMessageDialog(contentPane, 
              "Tile cache size based on current parameters: "+ sizeKb + " KB.\n" +
              "Press the 'Create tile cache' button to generate this tile cache.");
        }
      }
    });
  }

  private ExportTileCacheParameters createParameters(Geometry extent) {

    boolean createAsTilePackage = cbTpk.isSelected();
    // fix this to 5 levels
    double[] levels = {0, 1, 2, 3, 4};

    // set up parameters
    ExportTileCacheParameters params = new ExportTileCacheParameters(
        createAsTilePackage, 
        levels, 
        ExportBy.ID, 
        extent,
        map.getSpatialReference());

    params.setRecompressTileCache(recompress.isSelected());
    params.setRecompressionQuality(Integer.parseInt(recompressQuality.getText()));
    return params;
  }

  /**
   * Uses the 'generateTileCache' method to request and download the tile cache to 
   * the specified location on disk. When the tile cache is downloaded, the download 
   * complete listener callback uses the location of the tile cache to create an 
   * ArcGISLocalTiledLayer and adds it to the map.
   */
  private void createTileCache(ExportTileCacheParameters params, final String tileCachePath) {

    // ------------------------------------------------------------------------
    // Submit tile cache job and download
    // ------------------------------------------------------------------------
    updateProgressBarUI("Generating tile cache...", true);
    exportTileCacheTask.generateTileCache(
        // parameters
        params,
        // status callback
        new CallbackListener<ExportTileCacheStatus>() {
          private boolean errored = false;

          @Override
          public void onError(Throwable e) {
            errored = true;
            updateProgressBarUI(null, false);
            e.printStackTrace();
          }

          @Override
          public void onCallback(ExportTileCacheStatus status) {
            if (!errored) {
              if (status.isDownloading()) {
                updateMultiFileProgressBarUI(status);
              } else {
                updateProgressBarUI("Latest status: " + status.getStatus(), true);
              }
            }
          }
        },
        // callback when download complete
        new CallbackListener<String>() {
          private boolean errored = false;

          @Override
          public void onError(Throwable e) {
            errored = true;
            updateProgressBarUI(null, false);
            e.printStackTrace();
          }

          @Override
          public void onCallback(String path) {
            if (!errored) {
              updateProgressBarUI("Tile cache download complete. Switching to local layer...", true);
              switchToLocalLayer(path);
              updateMultiFileProgressBarUI(null);
              tpkButton.setEnabled(true); // re-enable button for another job
            }
          }
        },
        // path for downloaded tile cache
        tileCachePath);
  }

  private void switchToLocalLayer(final String path) {
    SwingUtilities.invokeLater(new Runnable() {

      @Override
      public void run() {
        final ArcGISLocalTiledLayer layer = new ArcGISLocalTiledLayer(path);

        map.getLayers().remove(tiledLayer);
        map.getLayers().add(layer);
      }
    });
  }

  // ------------------------------------------------------------------------
  // Static methods
  // ------------------------------------------------------------------------
  /**
   * Starting point of this application.
   * @param args arguments to this application.
   */
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {

      @Override
      public void run() {
        try {
          OfflineTiledLayer app = new OfflineTiledLayer();
          JFrame appWindow = app.createWindow();
          appWindow.add(app.createUI());
          appWindow.setVisible(true);
        }
        catch (Exception e) {
          e.printStackTrace();
        }
      }
    });
  }

  // ------------------------------------------------------------------------
  // Public methods
  // ------------------------------------------------------------------------
  /**
   * Creates and displays the UI, including the map and toolbar, for this application.
   */
  public JComponent createUI() {

    map = createMap();
    JLayeredPane mapPane = createLayeredPane();

    mainProgressBar = createProgressBar(mapPane);
    secondaryProgressBar = createSecondaryProgressBar(mapPane);

    mapPane.add(mainProgressBar);
    mapPane.add(secondaryProgressBar);
    mapPane.add(map);

    contentPane = new JPanel();
    contentPane.setLayout(new BorderLayout());

    // input panel
    JToolBar toolbar = createToolBar();
    contentPane.add(toolbar, BorderLayout.WEST);
    contentPane.add(mapPane, BorderLayout.CENTER);

    return contentPane;
  }

  // ------------------------------------------------------------------------
  // Private methods
  // ------------------------------------------------------------------------
  /**
   * Creates a layered pane so we can have components appear over the map, 
   * such as a progress bar.
   * 
   * @return a layered pane
   */
  private static JLayeredPane createLayeredPane() {
    JLayeredPane contentPane = new JLayeredPane();
    contentPane.setSize(1000, 700);
    contentPane.setLayout(new BorderLayout());
    contentPane.setVisible(true);
    return contentPane;
  }

  /**
   * Creates the map for the application, displaying a tiled map service layer 
   * and with a custom map overlay to facilitate drawing and selecting an extent.
   * 
   * @return a JMap
   */
  private JMap createMap() {
    final JMap jMap = new JMap();
    jMap.setZoomSnapEnabled(true);

    tiledLayer = new ArcGISTiledMapServiceLayer(SERVICE_URL);
    jMap.getLayers().add(tiledLayer);

    // create our overlay to draw/select an extent
    overlay = new SelectExtentOverlay();
    overlay.setActive(false);
    jMap.addMapOverlay(overlay);

    // create the tile cache task
    exportTileCacheTask = new ExportTileCacheTask(SERVICE_URL, null);

    return jMap;
  }

  /**
   * Creates a toolbar with the user controls for the tile cache creation process.
   * 
   * @return a toolbar
   */
  private JToolBar createToolBar() {
    JToolBar tb = new JToolBar(SwingConstants.VERTICAL);

    JPanel panelTileCache = new JPanel();
    panelTileCache.setLayout(new BoxLayout(panelTileCache, BoxLayout.Y_AXIS));
    panelTileCache.setAlignmentX(SwingConstants.LEFT);
    panelTileCache.setBorder(BorderFactory.createTitledBorder("Generate tile cache: "));

    JLabel label1 = new JLabel(" 1. Select an extent:");
    label1.setPreferredSize(BUTTON_DIM);
    label1.setMaximumSize(BUTTON_DIM);
    label1.setAlignmentX(SwingConstants.LEFT);
    // two-state button
    extentButton = new JToggleButton("Activate extent box");
    extentButton.setPreferredSize(BUTTON_DIM);
    extentButton.setMaximumSize(BUTTON_DIM);
    extentButton.setAlignmentX(SwingConstants.LEFT);
    extentButton.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent e) {
        if (((JToggleButton)e.getSource()).isSelected()) {
          overlay.setActive(true);
        } else {
          overlay.setActive(false);
        }
      }
    });
    panelTileCache.add(label1);
    panelTileCache.add(extentButton);

    // label and text input field
    JLabel label2 = new JLabel(" 2. Enter destination path:");
    label2.setPreferredSize(BUTTON_DIM);
    label2.setMaximumSize(BUTTON_DIM);
    label2.setAlignmentX(SwingConstants.LEFT);
    textField = new JTextField(DEFAULT_PATH);
    textField.setPreferredSize(BUTTON_DIM);
    textField.setMaximumSize(BUTTON_DIM);
    textField.setAlignmentX(SwingConstants.LEFT);

    panelTileCache.add(label2);
    panelTileCache.add(textField);

    JLabel compressLabel = new JLabel(" 3. Choose recompression:");
    compressLabel.setPreferredSize(BUTTON_DIM);
    compressLabel.setMaximumSize(BUTTON_DIM);
    compressLabel.setAlignmentX(SwingConstants.LEFT);
    // check box
    recompress = new JCheckBox("Recompress");
    recompress.setSelected(true);
    recompress.setPreferredSize(BUTTON_DIM);
    recompress.setMaximumSize(BUTTON_DIM);
    recompress.setAlignmentX(SwingConstants.LEFT);
    recompress.addActionListener(new ActionListener() {      
      @Override
      public void actionPerformed(ActionEvent e) {
        recompressQuality.setEnabled(recompress.isSelected());
      }
    });
    JLabel compressPerLabel = new JLabel("Recompression quality (0-100):");
    compressPerLabel.setPreferredSize(BUTTON_DIM);
    compressPerLabel.setMaximumSize(BUTTON_DIM);
    compressPerLabel.setAlignmentX(SwingConstants.LEFT);
    recompressQuality = new JTextField("90");
    recompressQuality.setPreferredSize(BUTTON_DIM);
    recompressQuality.setMaximumSize(BUTTON_DIM);
    recompressQuality.setAlignmentX(SwingConstants.LEFT);

    panelTileCache.add(compressLabel);
    panelTileCache.add(recompress);
    panelTileCache.add(compressPerLabel);
    panelTileCache.add(recompressQuality);

    JLabel createLabel = new JLabel(" 4. Create tile cache:");
    createLabel.setPreferredSize(BUTTON_DIM);
    createLabel.setMaximumSize(BUTTON_DIM);
    createLabel.setAlignmentX(SwingConstants.LEFT);
    // check box
    cbTpk = new JCheckBox("Create as a .tpk");
    cbTpk.setSelected(true);
    cbTpk.setPreferredSize(BUTTON_DIM);
    cbTpk.setMaximumSize(BUTTON_DIM);
    cbTpk.setAlignmentX(SwingConstants.LEFT);

    panelTileCache.add(createLabel);
    panelTileCache.add(cbTpk);

    JButton sizeButton = new JButton("Estimate tile cache size");
    sizeButton.setPreferredSize(BUTTON_DIM);
    sizeButton.setMaximumSize(BUTTON_DIM);
    sizeButton.setAlignmentX(SwingConstants.LEFT);
    sizeButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent event) {
        // get the last user-drawn extent from the overlay
        Envelope extentForTPK = overlay.getExtent();
        if (extentForTPK == null) {
          JOptionPane.showMessageDialog(contentPane.getParent(), 
              "Please select an extent before estimating a tile cache size.", "Warning", 
              JOptionPane.WARNING_MESSAGE);
        } else {
          onEstimateSizeButtonClick(extentForTPK);
        }
      }
    });
    panelTileCache.add(sizeButton);

    // button
    tpkButton = new JButton("Create tile cache");
    tpkButton.setPreferredSize(BUTTON_DIM);
    tpkButton.setMaximumSize(BUTTON_DIM);
    tpkButton.setAlignmentX(SwingConstants.LEFT);
    tpkButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent event) {
        // get the last user-drawn extent from the overlay
        Envelope extentForTPK = overlay.getExtent();
        if (extentForTPK == null) {
          JOptionPane.showMessageDialog(contentPane.getParent(), 
              "Please select an extent before creating a tile package.", "Warning", 
              JOptionPane.WARNING_MESSAGE);
        } else {
          tpkButton.setEnabled(false);
          onCreateTileCacheButtonClick(extentForTPK);
        }
      }
    });
    panelTileCache.add(tpkButton);

    tb.add(panelTileCache);

    return tb;
  }

  /**
   * Creates the application window.
   * @return a window.
   */
  private JFrame createWindow() {
    JFrame window = new JFrame("Offline Tiled Layer Application");
    window.setSize(1000, 700);
    window.setLocationRelativeTo(null); // center on screen
    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    window.addWindowListener(new WindowAdapter() {
      @Override
      public void windowClosing(WindowEvent windowEvent) {
        super.windowClosing(windowEvent);
        map.dispose();
      }
    });
    return window;
  }

  /**
   * Creates a progress bar.
   * @param parent progress bar's parent. The horizontal axis of the progress bar will be
   * center-aligned to the parent.
   * @return a progress bar.
   */
  private static JProgressBar createProgressBar(final JComponent parent) {
    final JProgressBar progressBar = new JProgressBar();
    progressBar.setSize(350, 20);
    parent.addComponentListener(new ComponentAdapter() {
      @Override
      public void componentResized(ComponentEvent e) {
        progressBar.setLocation(
            parent.getWidth()/2 - progressBar.getWidth()/2,
            parent.getHeight() - progressBar.getHeight() - 40);
      }
    });
    progressBar.setStringPainted(true);
    progressBar.setIndeterminate(true);
    progressBar.setVisible(false);
    progressBar.setMaximum(100);
    return progressBar;
  }

  private static JProgressBar createSecondaryProgressBar(final JComponent parent) {
    final JProgressBar progressBar = new JProgressBar();
    progressBar.setSize(350, 20);
    parent.addComponentListener(new ComponentAdapter() {
      @Override
      public void componentResized(ComponentEvent e) {
        progressBar.setLocation(parent.getWidth() / 2 - progressBar.getWidth() / 2,
            parent.getHeight() - progressBar.getHeight() - 20);
      }
    });
    progressBar.setStringPainted(true);
    progressBar.setVisible(false);
    progressBar.setMaximum(100);
    return progressBar;
  }

  /**
   * Updates progress bar UI from the Swing's Event Dispatch Thread.
   * @param str string to be set.
   * @param visible flag to indicate visibility of the progress bar.
   */
  private void updateProgressBarUI(final String str, final boolean visible) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        mainProgressBar.setIndeterminate(true);
        if (str != null) {
          mainProgressBar.setString(str);
          System.out.println(str);
        }
        mainProgressBar.setVisible(visible);
      }
    }); 
  }

  private void updateMultiFileProgressBarUI(final ExportTileCacheStatus status) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        if (status == null) {
          // Hide progress bars
          mainProgressBar.setVisible(false);
          secondaryProgressBar.setVisible(false);

          // Set main progress bar maximum back to 100
          mainProgressBar.setMaximum(100);
        } else {
          // Show files download progress
          mainProgressBar.setIndeterminate(false);
          mainProgressBar.setString(String.format("Downloading %d of %d", new Long(status.getTotalFilesDownloaded()), new Long(status.getTotalFiles())));
          mainProgressBar.setMaximum((int) status.getTotalFiles());
          mainProgressBar.setValue((int) status.getTotalFilesDownloaded());
          mainProgressBar.setVisible(true);

          // Show progress of currently downloading file
          secondaryProgressBar.setString("Downloading: " + status.getDownloadFilename());

          // We've preset the maximum value to 100 and calculate the percentage done
          // value here to allow for the fact that our file download size is a long
          // value and the progress bar only supports int.
          secondaryProgressBar.setValue((int) ((status.getTotalBytesDownloaded() * 100) / status.getDownloadSize()));
          secondaryProgressBar.setVisible(true);
        }
      }
    });
  }

  /**
   * Adapted from the ZoomBoxOverlay in the Java SE SDK's toolkit. The small 
   * change is to not automatically zoom to the drawn rectangle, but 
   * instead to store the value and use it in our tile package creation.
   * A similar end result can be achieved using a DrawingOverlay.
   */
  public class SelectExtentOverlay extends MapOverlay {

    private static final long serialVersionUID = 1L;
    private final Color TRANS_BG = new Color(0, 0, 0, 30);
    private Point _firstPoint; // java.awt.Point, in screen coordinates
    private Rectangle2D _zoomRect;
    private Envelope _extent;

    public SelectExtentOverlay(){}

    @Override
    public void onMousePressed(MouseEvent event) {
      // Get first point in screen coords.
      _firstPoint = event.getPoint();
      _zoomRect = new Rectangle2D.Double();
    }

    @Override
    public void onMouseReleased(MouseEvent event) {

      JMap jMap = this.getMap();
      com.esri.core.geometry.Point topLeft = jMap.toMapPoint(
          (int)_zoomRect.getMinX(), (int)_zoomRect.getMinY());
      com.esri.core.geometry.Point bottomRight = jMap.toMapPoint(
          (int)_zoomRect.getMaxX(), (int)_zoomRect.getMaxY());
      Envelope extent = new Envelope(topLeft.getX(), topLeft.getY(),
          bottomRight.getX(), bottomRight.getY());
      _extent = extent;

      _firstPoint = null;
      _zoomRect = null;
    }

    @Override
    public void onMouseDragged(MouseEvent event) {
      if(_firstPoint != null){
        double width = Math.abs(event.getX() - _firstPoint.getX());
        double height = Math.abs(event.getY() - _firstPoint.getY());
        Point topLeft = new Point(); // java.awt.Point
        if(_firstPoint.getX() < event.getX()){
          topLeft.setLocation(_firstPoint.getX(), 0);
        }
        else{
          topLeft.setLocation(event.getX(), 0);
        }

        if(_firstPoint.getY() < event.getY()){
          topLeft.setLocation(topLeft.getX(), _firstPoint.getY());
        }
        else{
          topLeft.setLocation(topLeft.getX(), event.getY());
        }
        _zoomRect.setRect(topLeft.getX(), topLeft.getY(), width, height);
        this.repaint();
      }
    }

    /**
     * Returns the latest user-drawn extent from the overlay.
     * @return an extent Envelope
     */
    public Envelope getExtent() {
      return _extent;
    }

    @Override
    public void paint(Graphics graphics) {
      if(_zoomRect != null){
        Graphics2D g = (Graphics2D) graphics;
        g.setColor(Color.DARK_GRAY);
        g.setStroke(new BasicStroke(2, BasicStroke.CAP_SQUARE,
            BasicStroke.JOIN_MITER, 2, new float[]{6, 6}, 0f));
        g.draw(_zoomRect);
        g.setPaint(TRANS_BG);
        g.fill(_zoomRect);
      }
    }
  }
}
Feedback on this topic?