- (void)beginBackgroundCacheForTileSource:(id )tileSource southWest:(CLLocationCoordinate2D)southWest northEast:(CLLocationCoordinate2D)northEast minZoom:(float)minZoom maxZoom:(float)maxZoom { if (self.isBackgroundCaching) return; _activeTileSource = tileSource; _backgroundFetchQueue = [[NSOperationQueue alloc] init]; [_backgroundFetchQueue setMaxConcurrentOperationCount:6]; int minCacheZoom = (int)minZoom; int maxCacheZoom = (int)maxZoom; float minCacheLat = southWest.latitude; float maxCacheLat = northEast.latitude; float minCacheLon = southWest.longitude; float maxCacheLon = northEast.longitude; if (maxCacheZoom < minCacheZoom || maxCacheLat <= minCacheLat || maxCacheLon <= minCacheLon) return; int n, xMin, yMax, xMax, yMin; int totalTiles = 0; for (int zoom = minCacheZoom; zoom <= maxCacheZoom; zoom++) { n = pow(2.0, zoom); xMin = floor(((minCacheLon + 180.0) / 360.0) * n); yMax = floor((1.0 - (logf(tanf(minCacheLat * M_PI / 180.0) + 1.0 / cosf(minCacheLat * M_PI / 180.0)) / M_PI)) / 2.0 * n); xMax = floor(((maxCacheLon + 180.0) / 360.0) * n); yMin = floor((1.0 - (logf(tanf(maxCacheLat * M_PI / 180.0) + 1.0 / cosf(maxCacheLat * M_PI / 180.0)) / M_PI)) / 2.0 * n); totalTiles += (xMax + 1 - xMin) * (yMax + 1 - yMin); } [_backgroundCacheDelegate tileCache:self didBeginBackgroundCacheWithCount:totalTiles forTileSource:_activeTileSource]; __block int progTile = 0; for (int zoom = minCacheZoom; zoom <= maxCacheZoom; zoom++) { n = pow(2.0, zoom); xMin = floor(((minCacheLon + 180.0) / 360.0) * n); yMax = floor((1.0 - (logf(tanf(minCacheLat * M_PI / 180.0) + 1.0 / cosf(minCacheLat * M_PI / 180.0)) / M_PI)) / 2.0 * n); xMax = floor(((maxCacheLon + 180.0) / 360.0) * n); yMin = floor((1.0 - (logf(tanf(maxCacheLat * M_PI / 180.0) + 1.0 / cosf(maxCacheLat * M_PI / 180.0)) / M_PI)) / 2.0 * n); for (int x = xMin; x <= xMax; x++) { for (int y = yMin; y <= yMax; y++) { RMTileCacheDownloadOperation *operation = [[RMTileCacheDownloadOperation alloc] initWithTile:RMTileMake(x, y, zoom) forTileSource:_activeTileSource usingCache:self]; __block RMTileCacheDownloadOperation *internalOperation = operation; [operation setCompletionBlock:^(void) { dispatch_sync(dispatch_get_main_queue(), ^(void) { if ( ! [internalOperation isCancelled]) { progTile++; [_backgroundCacheDelegate tileCache:self didBackgroundCacheTile:RMTileMake(x, y, zoom) withIndex:progTile ofTotalTileCount:totalTiles]; [this then fetches http://tile.openstreetmap.org/%d/%d/%d.png where the args are zoom, x, y] if (progTile == totalTiles) { _backgroundFetchQueue = nil; _activeTileSource = nil; [_backgroundCacheDelegate tileCacheDidFinishBackgroundCache:self]; } } internalOperation = nil; }); }]; [_backgroundFetchQueue addOperation:operation]; } }