Estimating Map downloadable size while Caching the map

440
12
Jump to solution
04-10-2024 05:35 AM
AjiteshUpadhyaya
New Contributor III

Hello Experts, 

We would need to show the Estimated time to complete the caching of map. This can be done if we have some reference on how much is already downloaded to the device and the total size of the map. 

For the same, i have written code something like below, but not getting how can i calculate/get how much has been downloaded.

-(void) cacheBasemapParameters:(AGSExportTileCacheParameters *)params withSteps:(int) steps
{
	[self deleteExistingTPKFile];

    // Use a weak variable for self within the blocks
    __weak EsriMapViewController *weakSelf = self;

    // temp file
    NSURL *tempFileDirectoryUrl = [[NSFileManager.defaultManager temporaryDirectory] URLByAppendingPathComponent:NSProcessInfo.processInfo.globallyUniqueString];
	NSURL* docDirectoryURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
																 inDomains:NSUserDomainMask] lastObject];

    [NSFileManager.defaultManager createDirectoryAtURL:tempFileDirectoryUrl withIntermediateDirectories:YES attributes:nil error:nil];

    NSString *extension = @".tpk";
    if (self.tileCacheTask.mapServiceInfo.exportTileCacheCompactV2Allowed)
	{
        extension = @".tpkx";
    }
    NSURL *tempFileUrl = [docDirectoryURL URLByAppendingPathComponent:[@"myTileCache" stringByAppendingString:extension]];

    // Kick-off operation
    AGSExportTileCacheJob *job = [self.tileCacheTask exportTileCacheJobWithParameters:params downloadFileURL:tempFileUrl];
    self.cacheJob = job;
    [self.cacheJob startWithStatusHandler:^(AGSJobStatus status) {
        //Print the job status
        
        DLog(@"%d", status);
        //NSArray *allMessages =  [userInfo objectForKey:@"messages"];
        
        //Display download progress if we are fetching result
        /*if (status == 3) {
            NSNumber* totalBytesDownloaded = userInfo[@"AGSDownloadProgressTotalBytesDownloaded"];
            NSNumber* totalBytesExpected = userInfo[@"AGSDownloadProgressTotalBytesExpected"];
            if(totalBytesDownloaded!=nil && totalBytesExpected!=nil){
                double dPercentage = (double)([totalBytesDownloaded doubleValue]/[totalBytesExpected doubleValue]) * 100;
                [weakSelf showProgressWithMessage:[NSString stringWithFormat:@"%@\n%.2f%% %@", statusMessageTitle, dPercentage, [weakSelf.dataSource textValueForKey:@"DownloadStatus"]]];
                
            }
        }else if ( allMessages.count) {
            [weakSelf showProgressWithMessage:[self extractMostRecentMessage:allMessages]];
        } */
        
        
    } completion:^(AGSTileCache * _Nullable result, NSError * _Nullable error) {
        if (error){
            [weakSelf hideProgress];

            //alert the user
            [ViewUtils showSimpleAlertWithTitle:[self.dataSource textValueForKey:@"Error"]
                                        message:[self.dataSource textValueForKey:@"BasemapDownloadError"]
                              cancelButtonTitle:[self.dataSource textValueForKey:@"Ok"]];
            
            // no need to proceed further if basemaps cannot be downloaded.
        }
        else{
            DLog(@"Successfully downloaded basemap");
            // start downloading feature layers
            if(weakSelf.featureLayersToCache.count > 0){
                // delete all cached feature layers
                [self deleteCachedGeodatabases];
                [self.gdbFeatureLayerMap removeAllObjects];
            }
            [weakSelf cacheNextFeatureLayer];
        }
    }];
}

 

Any idea on how to get it done? 

 

 

Thanks,

Ajitesh 

0 Kudos
1 Solution

Accepted Solutions
Ting
by Esri Contributor
Esri Contributor

Can you share the code snippet you use to retrieve the file size downloaded?

Instead of monitoring the file size, I suggest observing the exportTileCacheJob.progress instead. Sth like below

 

jobProgressObservation = exportTileCacheJob.progress.observe(\.fractionCompleted, options: .new) { [weak self] (progress, _) in
    print(progress.fractionCompleted)
}

 

While it doesn't tell how much file size has been downloaded, it gives you a series of percentage of the job's current progress.

 

For example, below is the extent I tried to export.

extent.png

Tile count:  321, File size:  7.1 MB

progress.fractionCompleted output: 0.01, 0.02, 0.03, 0.04, 0.06, 0.07, 0.09, 0.11, 0.12, 0.17, 0.27, 0.39, 0.6, 0.8, 1.0

View solution in original post

0 Kudos
12 Replies
Ting
by Esri Contributor
Esri Contributor

Feel free to poke around the AGSEstimateTileCacheSizeResult API.

Unfortunately I don't have an example code snippet handy, so you can give it a try and let me know if you have any trouble. 😬

Please note, this API only works with image tiles, not vector tiles. We currently don't have a way to estimate the vector tile cache export size yet.

0 Kudos
AjiteshUpadhyaya
New Contributor III

Thank you Ting for your quick response. 

With AGSEstimateTileCacheSizeResult , if i read it correctly, it doesnt have any option to save the cache file locally for future uses(My application has offline capabilities). 

 

Still, i tried writing the code as below it doesnt do anything.. also, i believe it returns the file size only when it goes to 'Completion' method. 

 

any help on this is really helpful.  

[self.tileCacheTask exportTileCacheParametersWithAreaOfInterest:extent minScale:minScale maxScale:maxScale completion:^(AGSExportTileCacheParameters * _Nullable exportTileCacheParameters, NSError * _Nullable error) {
        if (error) {
            //TODO: display error
        } else {
           
            AGSEstimateTileCacheSizeJob *job = [self.tileCacheTask estimateTileCacheSizeJobWithParameters:exportTileCacheParameters];
            [job startWithStatusHandler:^(AGSJobStatus status) {
                DLog(@"estimation status: %ld", (long)status);
                NSString * statusMessageTitle = [NSString stringWithFormat:@"%@ %@",[self.dataSource textValueForKey:@"DownloadPrepare"], basemap];
                
                [self showProgressWithMessage:statusMessageTitle];
            } completion:^( AGSEstimateTileCacheSizeResult * _Nullable result, NSError * _Nullable error) {
                DLog(@"estimated file size: %llu", result.fileSize);
                NSString * statusMessageTitle = [NSString stringWithFormat:@"%@ %@",[self.dataSource textValueForKey:@"DownloadPrepare"], basemap];
                [self showProgressWithMessage:statusMessageTitle];
                //[self cacheBasemapParameters:exportTileCacheParameters withSteps:steps];
            }];

 

0 Kudos
Ting
by Esri Contributor
Esri Contributor

With AGSEstimateTileCacheSizeResult , if i read it correctly, it doesnt have any option to save the cache file locally for future uses(My application has offline capabilities). 

That is correct. This API is only for estimating the file size and tile count. You'll still need to create a AGSExportTileCacheJob to download the tile cache.

Still, i tried writing the code as below it doesn't do anything.. 


This is a typically problem for async AGSJobs. Because the jobs runs asynchronously, once the job variable is assigned, it needs to have a lifespan longer than its asynchronous completion block, in order to get the results.

The common pattern is to hold the job as an instance property, such as (self.estimateJob = …)

 

exportTask.exportTileCacheParameters(
    withAreaOfInterest: areaOfInterest,
    minScale: minScale,
    maxScale: maxScale
) { [weak self, unowned exportTask] (params: AGSExportTileCacheParameters?, error: Error?) in
    guard let self else { return }
    if let params = params {
        let job = exportTask.estimateTileCacheSizeJob(with: params)
        self.estimateJob = job
        job.start(statusHandler: nil) { result, error in
            if let result {
                print("Tile count", result.tileCount)
                let formatter = ByteCountFormatter()
                formatter.allowedUnits = .useAll
                formatter.countStyle = .file
                formatter.includesUnit = true
                formatter.isAdaptive = true
                print("File size", formatter.string(fromByteCount: Int64(result.fileSize)))
            } else if let error {
                print(error.localizedDescription)
            }
        }
        // Below export tiles logics…
    } else if let error = error {
        print(error.localizedDescription)
    }
}

 

I don't have much template code for ObjC, so bear with my Swift answer. 😉

also, i believe it returns the file size only when it goes to 'Completion' method.

This is correct. Because the estimation happens on the server side, this method is asynchronous. The code is messy with ObjC and completion block, but is much nicer with Swift Concurrency async/await. Hope you can start using it soon!

 

Please see this article for the completion handler async programming pattern.

0 Kudos
AjiteshUpadhyaya
New Contributor III

Thank you so much Ting for the help. 

It's working now. but its not getting changed frequently as it should(please see attached screenshot). Any idea how can i get hold off the status for longer time? 

Screenshot 2024-04-12 at 6.25.45 PM.jpeg

0 Kudos
Ting
by Esri Contributor
Esri Contributor

Can you elaborate on what you means? Did you intend to upload a GIF?

Do you mean you want more frequent progress value updates, or want to show the status/progress spinner for a longer period of time?

0 Kudos
AjiteshUpadhyaya
New Contributor III

I mean, to show the more frequent progress value updates. 

On the progress bar, i'm showing 61KB/36.6 MB downloaded(as per above screenshot). When it downloads more data to the device the value of 61KB should get changed and should show the updated values..

Any idea how to achieve this? Is it possible?

 

 

0 Kudos
Ting
by Esri Contributor
Esri Contributor

Thanks for the details. Upon my tests, the progress does jump from 11% to 89% without any fraction values in between. We'll see if it is a known issue or not.

0 Kudos
AjiteshUpadhyaya
New Contributor III

This is interesting..In my case, when it started it was on 291 bytes and it stays as it is till the download gets completed. Please see attached video.

0 Kudos
Ting
by Esri Contributor
Esri Contributor

Can you share the code snippet you use to retrieve the file size downloaded?

Instead of monitoring the file size, I suggest observing the exportTileCacheJob.progress instead. Sth like below

 

jobProgressObservation = exportTileCacheJob.progress.observe(\.fractionCompleted, options: .new) { [weak self] (progress, _) in
    print(progress.fractionCompleted)
}

 

While it doesn't tell how much file size has been downloaded, it gives you a series of percentage of the job's current progress.

 

For example, below is the extent I tried to export.

extent.png

Tile count:  321, File size:  7.1 MB

progress.fractionCompleted output: 0.01, 0.02, 0.03, 0.04, 0.06, 0.07, 0.09, 0.11, 0.12, 0.17, 0.27, 0.39, 0.6, 0.8, 1.0

0 Kudos