Download Tile Cache

Download Sample Application

This sample demonstrates how you can create a local basemap from an online map service. You can use the AGSExportTileCacheTask to generate a tile cache on the server, store it in a single tile package file and download it to your Mac. Using a TPK as a basemap, your application can then go offline. It is important to remember that generating a tile package on the server and downloading it to your Mac will take time. The length of time is affected by both the geographical extent and the levels of detail (LODS) you choose. In this sample the geographical extent has been preset as an area of Los Angeles with LODs 5-8. You can adapt the code provided in this sample to generate tpks for your area of interest at the resolution you require. NOTE: This functionality is in BETA. Do not use this functionality or the generated tile packages in a deployed application.

Sample Code

//SWIFT SAMPLE CODE
/*
Copyright 2014 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.
*/

import Cocoa
import ArcGIS

let kOnlineTiledMapServiceURL = "http://sampleserver6.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer"

class CreateOfflineBasemapSwiftSample: NSViewController, AGSLayerDelegate, AGSMapViewLayerDelegate, AGSMapViewTouchDelegate {
    
    @IBOutlet weak var messageLabel:NSTextField!
    @IBOutlet weak var createTPKButton:NSButton!
    @IBOutlet weak var modeMatrix:NSMatrix!
    @IBOutlet weak var mapView:AGSMapView!
    
    var tpkPath:String!
    var tileCacheTask:AGSExportTileCacheTask!
    var areaOfInterest:AGSEnvelope!
    var lodsArray = Array<Int>()
    var tilecacheJob:AGSCancellable!
    var tiledLayer:AGSTiledMapServiceLayer!
    var localTiledLayer:AGSLocalTiledLayer!
    var graphicsLayer:AGSGraphicsLayer!
    
    
    
    //// -------------------------------------------------------------------------------
    ////  awakeFromNib
    //// -------------------------------------------------------------------------------
    override func awakeFromNib() {
        // load online layer
        self.loadLayer("online")
        
        //Set area of interest to Initial Extent of the service
        self.areaOfInterest = AGSEnvelope(xmin: -13400000, ymin:3800000, xmax:-12900000, ymax:4400000, spatialReference:AGSSpatialReference.webMercatorSpatialReference())
        self.modeMatrix.enabled = false
        
        //Create a graphic showing the area of interest
        self.graphicsLayer = AGSGraphicsLayer()
        self.mapView.addMapLayer(self.graphicsLayer, withName:"GraphicsLayer")
        let simpleFillSymbol = AGSSimpleFillSymbol(color: nil, outlineColor: NSColor.redColor())
        simpleFillSymbol.outline.width = 5.0
        let geom = self.areaOfInterest
        let graphic = AGSGraphic(geometry: geom, symbol: simpleFillSymbol, attributes: nil)
        self.graphicsLayer.addGraphic(graphic)
    }
    
    func layerDidLoad(layer: AGSLayer!) {
        println("Layer did load... \(layer.name)")
    }
    
    func layer(layer: AGSLayer!, didFailToLoadWithError error: NSError!) {
        self.showErrorWithTitle("Layer did not load", message:error.localizedDescription)
    }
    
    @IBAction func changeMode(sender: NSMatrix) {
        if sender.selectedCell().tag() == 0 {
            self.loadLayer("offline")
        }
        else if sender.selectedCell().tag() == 1 {
            self.loadLayer("online")
        }
    }
    
    @IBAction func createTPK(sender: AnyObject) {
    
        self.createTPKButton.enabled = false
    
        for (var i = 5 ; i < 9; i++) {
            self.lodsArray.append(i)
        }
        
        //Get path to store tpk in Mac
        let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
        self.tpkPath = paths[0] as String
        
        //Create tile cache parameters
        let exportTileCacheParameters = AGSExportTileCacheParams(levelsOfDetail: self.lodsArray, areaOfInterest: self.areaOfInterest)
        
        
        
        self.tileCacheTask = AGSExportTileCacheTask(URL: NSURL(string: kOnlineTiledMapServiceURL))
        self.tileCacheTask.loadCompletion = { [weak self] (error) in
            if let weakSelf = self {
                if error != nil {
                    weakSelf.showErrorWithTitle("Error in creating Tile Cache Task!", message:error.localizedDescription)
                    weakSelf.createTPKButton.enabled = true
                }
                else {
                    println("Tile cache task loaded")
                    
                    //Generate Tile Cache and fetch TPK
                    weakSelf.tileCacheTask.exportTileCacheWithParameters(exportTileCacheParameters, downloadFolderPath: weakSelf.tpkPath, useExisting: false, status: { [weak self] (status, userInfo) -> Void in
                        if let weakSelf = self {
                            weakSelf.messageLabel.stringValue = AGSResumableTaskJobStatusAsString(status)
                            println("userInfo: \(userInfo)")
                            if userInfo != nil {
                                if let allMessages = userInfo["messages"] as? [AGSGPMessage] {
                                    if allMessages.count > 0 {
                                        let gpDescription = allMessages[allMessages.count-1].description
                                        weakSelf.messageLabel.stringValue = weakSelf.parseMessagesDescription(gpDescription)
                                        if allMessages.count-2 >= 0 {
                                            let detailsMessages = allMessages[allMessages.count-2]
                                            let string = "Job Executing: \(detailsMessages.description)"
                                            weakSelf.messageLabel.stringValue = string
                                        }
                                    }
                                }
                            }
                        }
                    }, completion: { [weak self] (localTiledLayer, error) -> Void in
                        if let weakSelf = self {
                            if error != nil {
                                weakSelf.showErrorWithTitle("Error generating tiles!", message:error.localizedDescription)
                                weakSelf.createTPKButton.enabled = true
                            }
                            else {
                                weakSelf.localTiledLayer = localTiledLayer
                                weakSelf.loadLayer("offline")
                                
                                weakSelf.createTPKButton.enabled = true
                                weakSelf.modeMatrix.enabled = true
                                weakSelf.modeMatrix.selectCellWithTag(0)
                                
                                let string = "The Job (AGSExportTileCacheTask) has completed and the .tpk is located here: \(weakSelf.localTiledLayer.cachePath)"
                                weakSelf.messageLabel.stringValue = string
                            }
                        }
                    })
                }
            }
        }
        
    }
    
    
    func loadLayer(type: String) {
        // reset map
        self.mapView.reset()
        
        //enable wrap around
        self.mapView.enableWrapAround()
        
        //set layer delegate to know when mapView loads
        self.mapView.layerDelegate = self
        self.mapView.touchDelegate = self
        self.mapView.showMagnifierOnTapAndHold = true
        
        // check layer type
        if type == "online" {
            self.tiledLayer = AGSTiledMapServiceLayer(URL: NSURL(string: kOnlineTiledMapServiceURL))
            self.tiledLayer.delegate = self
            self.mapView.addMapLayer(self.tiledLayer, withName:"Online Tiled Layer")
        }
        else {
            self.localTiledLayer.delegate = self
            self.mapView.addMapLayer(self.localTiledLayer, withName:"Local Tiled Layer")
        }
    
        // zoom to extent
        self.mapView.zoomToEnvelope(AGSEnvelope(xmin:-13700000, ymin:3400000, xmax:-12700000, ymax:4900000, spatialReference:AGSSpatialReference.webMercatorSpatialReference()), animated:true)
    }
    
    
    func parseMessagesDescription(description:String) -> String {
        if let range = description.rangeOfString("description:", options: nil, range: nil, locale: nil) {
            let substring = description.substringFromIndex(range.endIndex)
            return substring
        }
        return description
    }
    
    func showErrorWithTitle(title:String, message:String) {
        if let viewWindow = self.view.window {
            let alert = NSAlert()
            alert.messageText = title
            alert.informativeText = message
            alert.beginSheetModalForWindow(viewWindow, modalDelegate: self, didEndSelector: nil, contextInfo: nil)
        }
    }
    
}
//OBJECTIVE C SAMPLE CODE
/*
 Copyright 2013 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.
 */

#import "CreateOfflineBasemapSample.h"

#define kOnlineTiledMapServiceURL @"http://sampleserver6.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer"

@interface CreateOfflineBasemapSample ()
@end

@implementation CreateOfflineBasemapSample

//// -------------------------------------------------------------------------------
////  awakeFromNib
//// -------------------------------------------------------------------------------
- (void)awakeFromNib {
    
    // load online layer
    [self loadLayer:@"online"];
    
    //Set area of interest to Initial Extent of the service
    self.areaOfInterest = [AGSEnvelope envelopeWithXmin:-13400000 ymin:3800000 xmax:-12900000 ymax:4400000 spatialReference:[AGSSpatialReference webMercatorSpatialReference]];
    [self.modeMatrix setEnabled:NO];
    
    //Create a graphic showing the area of interest
    self.graphicsLayer = [AGSGraphicsLayer graphicsLayer];
    [self.mapView addMapLayer:self.graphicsLayer withName:@"GraphicsLayer"];
    AGSSimpleFillSymbol *simpleFillSymbol = [AGSSimpleFillSymbol simpleFillSymbolWithColor:nil outlineColor:[NSColor redColor]];
    simpleFillSymbol.outline.width = 5.0;
    AGSGeometry *geom = self.areaOfInterest;
    AGSGraphic *graphic = [AGSGraphic graphicWithGeometry:geom symbol:simpleFillSymbol attributes:nil];
    [self.graphicsLayer addGraphic:graphic];
}

-(void)layerDidLoad:(AGSLayer *)layer{
    NSLog(@"Layer did load... %@", layer.name);
}

-(void)layer:(AGSLayer *)layer didFailToLoadWithError:(NSError *)error{
    [self showErrorWithTitle:@"Layer did not load" message:[NSString stringWithFormat:@"%@",error]];
}

-(IBAction)changeMode:(id)sender {
    if ([[sender selectedCell] tag]==0) {
        [self loadLayer:@"offline"];
    }
    else if ([[sender selectedCell] tag]==1) {
        [self loadLayer:@"online"];
    }
}


-(IBAction)createTPK:(id)sender {
    
    [self.createTPKButton setEnabled:NO];
    
    self.lodsArray = [[NSMutableArray alloc] init];
    for (int i =5; i < 9; i++) {
        [self.lodsArray addObject:[NSNumber numberWithInt:i]];
    }
    
    //Get path to store tpk in Mac
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    self.tpkPath = [paths objectAtIndex:0];
    
    //Create tile cache parameters
    AGSExportTileCacheParams *exportTileCacheParameters = [[AGSExportTileCacheParams alloc]
                                                           initWithLevelsOfDetail:self.lodsArray
                                                           areaOfInterest:self.areaOfInterest];
    
    __weak CreateOfflineBasemapSample *weakSelf = self;
    
    self.tileCacheTask = [[AGSExportTileCacheTask alloc] initWithURL:[NSURL URLWithString:kOnlineTiledMapServiceURL] credential:nil];
    self.tileCacheTask.loadCompletion = ^(NSError *error) {
        
        if (error) {
            [weakSelf showErrorWithTitle:@"Error in creating Tile Cache Task!" message:[NSString stringWithFormat:@"%@",error]];
            [weakSelf.createTPKButton setEnabled:YES];
        }
        else {
            
            NSLog(@"Tile cache task loaded");
            
            //Generate Tile Cache and fetch TPK
            weakSelf.tilecacheJob = [weakSelf.tileCacheTask exportTileCacheWithParameters:exportTileCacheParameters
                                                                       downloadFolderPath:[weakSelf tpkPath]
                                                                              useExisting:NO
                                                                              //status block
                                                                              status:^(AGSResumableTaskJobStatus status, NSDictionary *userInfo) {
                                                                                  weakSelf.messageLabel.stringValue = AGSResumableTaskJobStatusAsString(status);
                                                                                  NSArray *allMessages =  [userInfo objectForKey:@"messages"];
                                                                                       
                                                                                  if ( allMessages.count > 0) {
                                                                                      NSString *gpDescription = [[allMessages objectAtIndex:allMessages.count-1 ] description];
                                                                                      weakSelf.messageLabel.stringValue = [weakSelf parseMessagesDescription:gpDescription];
                                                                                      AGSGPMessage *detailsMessages = [allMessages objectAtIndex:allMessages.count-2 ];
                                                                                      if (detailsMessages != nil) {
                                                                                          NSMutableString *string = [NSMutableString string];
                                                                                          [string appendString:@"Job Executing: "];
                                                                                          [string appendString:detailsMessages.description];
                                                                                          weakSelf.messageLabel.stringValue = string;
                                                                                      }
                                                                                  }
                                                                                  NSLog(@"userInfo: %@", userInfo);
                                                                                }
                                                                                //completion block
                                                                                completion:^(
                                                                                             AGSLocalTiledLayer *localTiledLayer,NSError *error) {
                                                                                                if (error) {
                                                                                                    [weakSelf showErrorWithTitle:@"Error generating tiles!" message:[NSString stringWithFormat:@"%@",error]];
                                                                                                    [weakSelf.createTPKButton setEnabled:YES];
                                                                                                }
                                                                                                else {
                                                                                                    weakSelf.localTiledLayer = localTiledLayer;
                                                                                                    [weakSelf loadLayer:@"offline"];
                                                                                       
                                                                                                    [weakSelf.createTPKButton setEnabled:YES];
                                                                                                    [weakSelf.modeMatrix setEnabled:YES];
                                                                                                    [weakSelf.modeMatrix selectCellWithTag:0];
                                                                                       
                                                                                                    NSMutableString *string = [NSMutableString string];
                                                                                                    [string appendString:@"The Job (AGSExportTileCacheTask) has completed and the .tpk is located here: "];
                                                                                                    [string appendString:weakSelf.localTiledLayer.cachePath];
                                                                                       
                                                                                                    weakSelf.messageLabel.stringValue = string;
                                                                                       
                                                                                   }
                                                                               }];
            
        }
    };
}


-(void)loadLayer:(NSString*)type{
    
    // reset map
    [self.mapView reset];
    
    //enable wrap around
    [self.mapView enableWrapAround];
    
    //set layer delegate to know when mapView loads
    self.mapView.layerDelegate = self;
    self.mapView.touchDelegate = self;
    self.mapView.showMagnifierOnTapAndHold = YES;
    
    // check layer type
    if ([type  isEqual:@"online"]) {
        self.tiledLayer = [[AGSTiledMapServiceLayer alloc] initWithURL:[NSURL URLWithString:kOnlineTiledMapServiceURL]];
        self.tiledLayer.delegate = self;
        [self.mapView addMapLayer:self.tiledLayer withName:@"Online Tiled Layer"];}
    else {
        self.localTiledLayer.delegate = self;
        [self.mapView addMapLayer:self.localTiledLayer withName:@"Local Tiled Layer"];
    }
	
    // zoom to extent
    [self.mapView zoomToEnvelope:[AGSEnvelope
                                  envelopeWithXmin:-13700000 ymin:3400000 xmax:-12700000 ymax:4900000
                                  spatialReference:[AGSSpatialReference webMercatorSpatialReference]] animated:YES];
}


- (NSString *) parseMessagesDescription:(NSString*)description {
    NSRange range = [description rangeOfString:@"description:"];
    if ( range.length > 0 ) {
        NSString *substring = [description substringFromIndex:NSMaxRange(range)];
        return substring;
    }
    return description;
}

- (void)showErrorWithTitle:(NSString*)title message:(NSString*)message {
    NSAlert *alert = [[NSAlert alloc] init];
    [alert setMessageText:title];
    [alert setInformativeText:message];
    [alert beginSheetModalForWindow:self.view.window modalDelegate:self didEndSelector:nil contextInfo:nil];
}

@end

//OBJECTIVE C SAMPLE CODE
/*
 Copyright 2013 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.
 */

#import <Cocoa/Cocoa.h>

@interface CreateOfflineBasemapSample: NSViewController < AGSMapViewLayerDelegate, AGSLayerDelegate, AGSMapViewTouchDelegate, AGSPopupsContainerDelegate, NSTableViewDataSource, NSTableViewDelegate>

@property (strong) IBOutlet NSTextField *messageLabel;
@property (nonatomic, strong) IBOutlet NSButton *createTPKButton;
@property (nonatomic, strong) IBOutlet NSMatrix *modeMatrix;
@property (strong) IBOutlet AGSMapView *mapView;
@property (nonatomic,strong) NSString *tpkPath;
@property (nonatomic,strong) AGSExportTileCacheTask *tileCacheTask;
@property (strong,nonatomic) AGSEnvelope *areaOfInterest;
@property (strong,nonatomic) NSMutableArray *lodsArray;
@property (weak) id<AGSCancellable> tilecacheJob;
@property (strong) AGSTiledMapServiceLayer *tiledLayer;
@property (strong) AGSLocalTiledLayer *localTiledLayer;
@property (strong) AGSGraphicsLayer *graphicsLayer;

//IBActions - methods called by IBOutlets
- (IBAction)createTPK:(id)sender;
- (IBAction)changeMode:(id)sender;

@end

Feedback on this topic?