From 77657b38969a55377117dfde5fc9d5e3478208d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Thu, 3 Mar 2016 16:28:51 -0800 Subject: [ios] Implemented offline API in iOS SDK Fixes #3892. --- platform/darwin/include/MGLDownloadController.h | 30 ++++ platform/darwin/include/MGLDownloadRegion.h | 13 ++ platform/darwin/include/MGLDownloadable.h | 49 +++++++ .../darwin/include/MGLTilePyramidDownloadRegion.h | 19 +++ platform/darwin/include/MGLTypes.h | 7 + platform/darwin/src/MGLDownloadController.mm | 134 +++++++++++++++++ .../darwin/src/MGLDownloadController_Private.h | 15 ++ platform/darwin/src/MGLDownloadRegion_Private.h | 17 +++ platform/darwin/src/MGLDownloadable.mm | 160 +++++++++++++++++++++ platform/darwin/src/MGLDownloadable_Private.h | 26 ++++ .../darwin/src/MGLTilePyramidDownloadRegion.mm | 62 ++++++++ 11 files changed, 532 insertions(+) create mode 100644 platform/darwin/include/MGLDownloadController.h create mode 100644 platform/darwin/include/MGLDownloadRegion.h create mode 100644 platform/darwin/include/MGLDownloadable.h create mode 100644 platform/darwin/include/MGLTilePyramidDownloadRegion.h create mode 100644 platform/darwin/src/MGLDownloadController.mm create mode 100644 platform/darwin/src/MGLDownloadController_Private.h create mode 100644 platform/darwin/src/MGLDownloadRegion_Private.h create mode 100644 platform/darwin/src/MGLDownloadable.mm create mode 100644 platform/darwin/src/MGLDownloadable_Private.h create mode 100644 platform/darwin/src/MGLTilePyramidDownloadRegion.mm (limited to 'platform/darwin') diff --git a/platform/darwin/include/MGLDownloadController.h b/platform/darwin/include/MGLDownloadController.h new file mode 100644 index 0000000000..86bd1e6573 --- /dev/null +++ b/platform/darwin/include/MGLDownloadController.h @@ -0,0 +1,30 @@ +#import + +#import "MGLTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@class MGLDownloadable; +@protocol MGLDownloadRegion; + +typedef void (^MGLDownloadableRegistrationCompletionHandler)(MGLDownloadable *downloadable, NSError *error); +typedef void (^MGLDownloadableRemovalCompletionHandler)(NSError *error); +typedef void (^MGLDownloadablesRequestCompletionHandler)(NS_ARRAY_OF(MGLDownloadable *) *downloadables, NSError *error); + +@interface MGLDownloadController : NSObject + ++ (instancetype)sharedController; + +- (instancetype)init NS_UNAVAILABLE; + +- (void)addDownloadableForRegion:(id )downloadRegion withContext:(NSData *)context completionHandler:(MGLDownloadableRegistrationCompletionHandler)completion; + +- (void)removeDownloadable:(MGLDownloadable *)downloadable withCompletionHandler:(MGLDownloadableRemovalCompletionHandler)completion; + +- (void)requestDownloadablesWithCompletionHandler:(MGLDownloadablesRequestCompletionHandler)completion; + +- (void)setMaximumAllowedMapboxTiles:(uint64_t)maximumCount; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/include/MGLDownloadRegion.h b/platform/darwin/include/MGLDownloadRegion.h new file mode 100644 index 0000000000..5910c2117b --- /dev/null +++ b/platform/darwin/include/MGLDownloadRegion.h @@ -0,0 +1,13 @@ +#import + +#import "MGLTypes.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol MGLDownloadRegion + +@property (nonatomic, readonly) NSURL *styleURL; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/include/MGLDownloadable.h b/platform/darwin/include/MGLDownloadable.h new file mode 100644 index 0000000000..522a3c37ab --- /dev/null +++ b/platform/darwin/include/MGLDownloadable.h @@ -0,0 +1,49 @@ +#import + +#import "MGLDownloadRegion.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol MGLDownloadableDelegate; + +typedef NS_ENUM (NSInteger, MGLDownloadableState) { + MGLDownloadableStateInactive = 0, + MGLDownloadableStateActive = 1, + MGLDownloadableStateComplete = 2, +}; + +typedef struct MGLDownloadableProgress { + uint64_t countOfResourcesCompleted; + uint64_t countOfBytesCompleted; + uint64_t countOfResourcesExpected; + uint64_t maximumResourcesExpected; +} MGLDownloadableProgress; + +@interface MGLDownloadable : NSObject + +@property (nonatomic, readonly) id region; +@property (nonatomic, readonly) NSData *context; +@property (nonatomic, readonly) MGLDownloadableState state; +@property (nonatomic, readonly) MGLDownloadableProgress progress; +@property (nonatomic, weak, nullable) id delegate; + +- (instancetype)init NS_UNAVAILABLE; + +- (void)resume; +- (void)suspend; + +- (void)requestProgress; + +@end + +@protocol MGLDownloadableDelegate + +@optional + +- (void)downloadable:(MGLDownloadable *)downloadable progressDidChange:(MGLDownloadableProgress)progress; +- (void)downloadable:(MGLDownloadable *)downloadable didReceiveError:(NSError *)error; +- (void)downloadable:(MGLDownloadable *)downloadable didReceiveMaximumAllowedMapboxTiles:(uint64_t)maximumCount; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/include/MGLTilePyramidDownloadRegion.h b/platform/darwin/include/MGLTilePyramidDownloadRegion.h new file mode 100644 index 0000000000..6269775f19 --- /dev/null +++ b/platform/darwin/include/MGLTilePyramidDownloadRegion.h @@ -0,0 +1,19 @@ +#import + +#import "MGLDownloadRegion.h" +#import "MGLGeometry.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MGLTilePyramidDownloadRegion : NSObject + +@property (nonatomic, readonly) MGLCoordinateBounds bounds; +@property (nonatomic, readonly) double minimumZoomLevel; +@property (nonatomic, readonly) double maximumZoomLevel; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithStyleURL:(nullable NSURL *)styleURL bounds:(MGLCoordinateBounds)bounds fromZoomLevel:(double)minimumZoomLevel toZoomLevel:(double)maximumZoomLevel NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/include/MGLTypes.h b/platform/darwin/include/MGLTypes.h index c107055dbb..213b90670e 100644 --- a/platform/darwin/include/MGLTypes.h +++ b/platform/darwin/include/MGLTypes.h @@ -15,6 +15,13 @@ NS_ASSUME_NONNULL_BEGIN /** Indicates an error occurred in the Mapbox SDK. */ extern NSString * const MGLErrorDomain; +typedef NS_ENUM(NSInteger, MGLErrorCode) { + MGLErrorCodeUnknown = -1, + MGLErrorCodeNotFound = 1, + MGLErrorCodeBadServerResponse = 2, + MGLErrorCodeConnectionFailed = 3, +}; + /** The mode used to track the user location on the map. */ typedef NS_ENUM(NSUInteger, MGLUserTrackingMode) { /** The map does not follow the user location. */ diff --git a/platform/darwin/src/MGLDownloadController.mm b/platform/darwin/src/MGLDownloadController.mm new file mode 100644 index 0000000000..e4dd273032 --- /dev/null +++ b/platform/darwin/src/MGLDownloadController.mm @@ -0,0 +1,134 @@ +#import "MGLDownloadController_Private.h" + +#import "MGLAccountManager_Private.h" +#import "MGLGeometry_Private.h" +#import "MGLDownloadable_Private.h" +#import "MGLDownloadRegion_Private.h" +#import "MGLTilePyramidDownloadRegion.h" + +#include + +@interface MGLDownloadController () + +@property (nonatomic) mbgl::DefaultFileSource *mbglFileSource; + +- (instancetype)initWithFileName:(NSString *)fileName NS_DESIGNATED_INITIALIZER; + +@end + +@implementation MGLDownloadController + ++ (instancetype)sharedController { + static dispatch_once_t onceToken; + static MGLDownloadController *sharedController; + dispatch_once(&onceToken, ^{ + sharedController = [[self alloc] initWithFileName:@"offline.db"]; + }); + return sharedController; +} + +- (instancetype)initWithFileName:(NSString *)fileName { + if (self = [super init]) { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *fileCachePath = [paths.firstObject stringByAppendingPathComponent:fileName]; + _mbglFileSource = new mbgl::DefaultFileSource(fileCachePath.UTF8String, [NSBundle mainBundle].resourceURL.path.UTF8String); + + // Observe for changes to the global access token (and find out the current one). + [[MGLAccountManager sharedManager] addObserver:self + forKeyPath:@"accessToken" + options:(NSKeyValueObservingOptionInitial | + NSKeyValueObservingOptionNew) + context:NULL]; + } + return self; +} + +- (void)dealloc { + [[MGLAccountManager sharedManager] removeObserver:self forKeyPath:@"accessToken"]; + + delete _mbglFileSource; + _mbglFileSource = nullptr; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NS_DICTIONARY_OF(NSString *, id) *)change context:(__unused void *)context { + // Synchronize the file source’s access token with the global one in MGLAccountManager. + if ([keyPath isEqualToString:@"accessToken"] && object == [MGLAccountManager sharedManager]) { + NSString *accessToken = change[NSKeyValueChangeNewKey]; + if (![accessToken isKindOfClass:[NSNull class]]) { + self.mbglFileSource->setAccessToken(accessToken.UTF8String); + } + } +} + +- (void)addDownloadableForRegion:(id )downloadRegion withContext:(NSData *)context completionHandler:(MGLDownloadableRegistrationCompletionHandler)completion { + if (![downloadRegion conformsToProtocol:@protocol(MGLDownloadRegion_Private)]) { + [NSException raise:@"Unsupported region type" format: + @"Regions of type %@ are unsupported.", NSStringFromClass([downloadRegion class])]; + return; + } + + const mbgl::OfflineTilePyramidRegionDefinition regionDefinition = [(id )downloadRegion offlineRegionDefinition]; + mbgl::OfflineRegionMetadata metadata(context.length); + [context getBytes:&metadata[0] length:metadata.size()]; + self.mbglFileSource->createOfflineRegion(regionDefinition, metadata, [&, completion](std::exception_ptr exception, mbgl::optional region) { + NSError *error; + if (exception) { + NSString *errorDescription = @(mbgl::util::toString(exception).c_str()); + error = [NSError errorWithDomain:MGLErrorDomain code:-1 userInfo:errorDescription ? @{ + NSLocalizedDescriptionKey: errorDescription, + } : nil]; + } + if (completion) { + MGLDownloadable *downloadable = [[MGLDownloadable alloc] initWithMBGLRegion:new mbgl::OfflineRegion(std::move(*region))]; + dispatch_async(dispatch_get_main_queue(), [&, completion, error, downloadable](void) { + completion(downloadable, error); + }); + } + }); +} + +- (void)removeDownloadable:(MGLDownloadable *)downloadable withCompletionHandler:(MGLDownloadableRemovalCompletionHandler)completion { + self.mbglFileSource->deleteOfflineRegion(std::move(*downloadable.mbglOfflineRegion), [&, completion](std::exception_ptr exception) { + NSError *error; + if (exception) { + error = [NSError errorWithDomain:MGLErrorDomain code:-1 userInfo:@{ + NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()), + }]; + } + if (completion) { + dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) { + completion(error); + }); + } + }); +} + +- (void)requestDownloadablesWithCompletionHandler:(MGLDownloadablesRequestCompletionHandler)completion { + self.mbglFileSource->listOfflineRegions([&, completion](std::exception_ptr exception, mbgl::optional> regions) { + NSError *error; + if (exception) { + error = [NSError errorWithDomain:MGLErrorDomain code:-1 userInfo:@{ + NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()), + }]; + } + NSMutableArray *downloadables; + if (regions) { + downloadables = [NSMutableArray arrayWithCapacity:regions->size()]; + for (mbgl::OfflineRegion ®ion : *regions) { + MGLDownloadable *downloadable = [[MGLDownloadable alloc] initWithMBGLRegion:new mbgl::OfflineRegion(std::move(region))]; + [downloadables addObject:downloadable]; + } + } + if (completion) { + dispatch_async(dispatch_get_main_queue(), [&, completion, error, downloadables](void) { + completion(downloadables, error); + }); + } + }); +} + +- (void)setMaximumAllowedMapboxTiles:(uint64_t)maximumCount { + _mbglFileSource->setOfflineMapboxTileCountLimit(maximumCount); +} + +@end diff --git a/platform/darwin/src/MGLDownloadController_Private.h b/platform/darwin/src/MGLDownloadController_Private.h new file mode 100644 index 0000000000..2e26ab90cb --- /dev/null +++ b/platform/darwin/src/MGLDownloadController_Private.h @@ -0,0 +1,15 @@ +#import "MGLDownloadController.h" + +#import "MGLDownloadable.h" + +#include + +NS_ASSUME_NONNULL_BEGIN + +@interface MGLDownloadController (Private) + +@property (nonatomic) mbgl::DefaultFileSource *mbglFileSource; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLDownloadRegion_Private.h b/platform/darwin/src/MGLDownloadRegion_Private.h new file mode 100644 index 0000000000..785775198a --- /dev/null +++ b/platform/darwin/src/MGLDownloadRegion_Private.h @@ -0,0 +1,17 @@ +#import + +#import "MGLDownloadRegion.h" + +#include + +NS_ASSUME_NONNULL_BEGIN + +@protocol MGLDownloadRegion_Private + +- (instancetype)initWithOfflineRegionDefinition:(const mbgl::OfflineRegionDefinition &)definition; + +- (const mbgl::OfflineRegionDefinition)offlineRegionDefinition; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLDownloadable.mm b/platform/darwin/src/MGLDownloadable.mm new file mode 100644 index 0000000000..507461b4b7 --- /dev/null +++ b/platform/darwin/src/MGLDownloadable.mm @@ -0,0 +1,160 @@ +#import "MGLDownloadable_Private.h" + +#import "MGLDownloadController_Private.h" +#import "MGLDownloadRegion_Private.h" +#import "MGLTilePyramidDownloadRegion.h" + +#include +#include + +class MBGLOfflineRegionObserver; + +@interface MGLDownloadable () + +@property (nonatomic, readwrite) mbgl::OfflineRegion *mbglOfflineRegion; +@property (nonatomic, readwrite) MBGLOfflineRegionObserver *mbglOfflineRegionObserver; +@property (nonatomic, readwrite) MGLDownloadableState state; +@property (nonatomic, readwrite) MGLDownloadableProgress progress; + +@end + +@implementation MGLDownloadable + +- (instancetype)init { + [NSException raise:@"Method unavailable" + format: + @"-[MGLDownloadable init] is unavailable. " + @"Use +[MGLDownloadController addDownloadRegion:context:completionHandler:] instead."]; + return nil; +} + +- (instancetype)initWithMBGLRegion:(mbgl::OfflineRegion *)region { + if (self = [super init]) { + _mbglOfflineRegion = region; + _state = MGLDownloadableStateInactive; + _mbglOfflineRegionObserver = new MBGLOfflineRegionObserver(self); + + mbgl::DefaultFileSource *mbglFileSource = [[MGLDownloadController sharedController] mbglFileSource]; + mbglFileSource->setOfflineRegionObserver(*_mbglOfflineRegion, std::make_unique(*_mbglOfflineRegionObserver)); + } + return self; +} + +- (void)dealloc { + delete _mbglOfflineRegionObserver; + _mbglOfflineRegionObserver = nullptr; +} + +- (id )region { + const mbgl::OfflineRegionDefinition ®ionDefinition = _mbglOfflineRegion->getDefinition(); + NSAssert([MGLTilePyramidDownloadRegion conformsToProtocol:@protocol(MGLDownloadRegion_Private)], @"MGLTilePyramidDownloadRegion should conform to MGLDownloadRegion_Private."); + return [(id )[MGLTilePyramidDownloadRegion alloc] initWithOfflineRegionDefinition:regionDefinition]; +} + +- (NSData *)context { + const mbgl::OfflineRegionMetadata &metadata = _mbglOfflineRegion->getMetadata(); + return [NSData dataWithBytes:&metadata[0] length:metadata.size()]; +} + +- (void)resume { + mbgl::DefaultFileSource *mbglFileSource = [[MGLDownloadController sharedController] mbglFileSource]; + mbglFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Active); + self.state = MGLDownloadableStateActive; +} + +- (void)suspend { + mbgl::DefaultFileSource *mbglFileSource = [[MGLDownloadController sharedController] mbglFileSource]; + mbglFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Inactive); + self.state = MGLDownloadableStateInactive; +} + +- (void)requestProgress { + mbgl::DefaultFileSource *mbglFileSource = [[MGLDownloadController sharedController] mbglFileSource]; + + __weak MGLDownloadable *weakSelf = self; + mbglFileSource->getOfflineRegionStatus(*_mbglOfflineRegion, [&, weakSelf](__unused std::exception_ptr exception, mbgl::optional status) { + if (status) { + mbgl::OfflineRegionStatus checkedStatus = *status; + dispatch_async(dispatch_get_main_queue(), ^{ + MGLDownloadable *strongSelf = weakSelf; + [strongSelf offlineRegionStatusDidChange:checkedStatus]; + }); + } + }); +} + +- (void)offlineRegionStatusDidChange:(mbgl::OfflineRegionStatus)status { + switch (status.downloadState) { + case mbgl::OfflineRegionDownloadState::Inactive: + self.state = status.complete() ? MGLDownloadableStateComplete : MGLDownloadableStateInactive; + break; + + case mbgl::OfflineRegionDownloadState::Active: + self.state = MGLDownloadableStateActive; + break; + } + + MGLDownloadableProgress progress; + progress.countOfResourcesCompleted = status.completedResourceCount; + progress.countOfBytesCompleted = status.completedResourceSize; + progress.countOfResourcesExpected = status.requiredResourceCount; + progress.maximumResourcesExpected = status.requiredResourceCountIsPrecise ? status.requiredResourceCount : UINT64_MAX; + self.progress = progress; + + if ([self.delegate respondsToSelector:@selector(downloadable:progressDidChange:)]) { + [self.delegate downloadable:self progressDidChange:progress]; + } +} + +NSError *MGLErrorFromResponseError(mbgl::Response::Error error) { + NSInteger errorCode = MGLErrorCodeUnknown; + switch (error.reason) { + case mbgl::Response::Error::Reason::NotFound: + errorCode = MGLErrorCodeNotFound; + break; + + case mbgl::Response::Error::Reason::Server: + errorCode = MGLErrorCodeBadServerResponse; + break; + + case mbgl::Response::Error::Reason::Connection: + errorCode = MGLErrorCodeConnectionFailed; + break; + + default: + break; + } + return [NSError errorWithDomain:MGLErrorDomain code:errorCode userInfo:@{ + NSLocalizedFailureReasonErrorKey: @(error.message.c_str()) + }]; +} + +void MBGLOfflineRegionObserver::statusChanged(mbgl::OfflineRegionStatus status) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSCAssert(downloadable, @"MBGLOfflineRegionObserver is dangling without an associated MGLDownloadable."); + + [downloadable offlineRegionStatusDidChange:status]; + }); +} + +void MBGLOfflineRegionObserver::responseError(mbgl::Response::Error error) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSCAssert(downloadable, @"MBGLOfflineRegionObserver is dangling without an associated MGLDownloadable."); + + if ([downloadable.delegate respondsToSelector:@selector(downloadable:didReceiveError:)]) { + [downloadable.delegate downloadable:downloadable didReceiveError:MGLErrorFromResponseError(error)]; + } + }); +} + +void MBGLOfflineRegionObserver::mapboxTileCountLimitExceeded(uint64_t limit) { + dispatch_async(dispatch_get_main_queue(), ^{ + NSCAssert(downloadable, @"MBGLOfflineRegionObserver is dangling without an associated MGLDownloadable."); + + if ([downloadable.delegate respondsToSelector:@selector(downloadable:didReceiveMaximumAllowedMapboxTiles:)]) { + [downloadable.delegate downloadable:downloadable didReceiveMaximumAllowedMapboxTiles:limit]; + } + }); +} + +@end diff --git a/platform/darwin/src/MGLDownloadable_Private.h b/platform/darwin/src/MGLDownloadable_Private.h new file mode 100644 index 0000000000..a9b2460c19 --- /dev/null +++ b/platform/darwin/src/MGLDownloadable_Private.h @@ -0,0 +1,26 @@ +#import "MGLDownloadable.h" + +#include + +class MBGLOfflineRegionObserver; + +@interface MGLDownloadable (Private) + +@property (nonatomic) mbgl::OfflineRegion *mbglOfflineRegion; +@property (nonatomic, readonly) MBGLOfflineRegionObserver *mbglOfflineRegionObserver; + +- (instancetype)initWithMBGLRegion:(mbgl::OfflineRegion *)region; + +@end + +class MBGLOfflineRegionObserver : public mbgl::OfflineRegionObserver { +public: + MBGLOfflineRegionObserver(MGLDownloadable *downloadable_) : downloadable(downloadable_) {} + + void statusChanged(mbgl::OfflineRegionStatus status) override; + void responseError(mbgl::Response::Error error) override; + void mapboxTileCountLimitExceeded(uint64_t limit) override; + +private: + __weak MGLDownloadable *downloadable = nullptr; +}; diff --git a/platform/darwin/src/MGLTilePyramidDownloadRegion.mm b/platform/darwin/src/MGLTilePyramidDownloadRegion.mm new file mode 100644 index 0000000000..942450c0c2 --- /dev/null +++ b/platform/darwin/src/MGLTilePyramidDownloadRegion.mm @@ -0,0 +1,62 @@ +#import "MGLTilePyramidDownloadRegion.h" + +#import "MGLDownloadRegion_Private.h" +#import "MGLGeometry_Private.h" +#import "MGLStyle.h" + +@interface MGLTilePyramidDownloadRegion () + +@end + +@implementation MGLTilePyramidDownloadRegion { + NSURL *_styleURL; +} + +@synthesize styleURL = _styleURL; + +- (instancetype)init { + [NSException raise:@"Method unavailable" + format: + @"-[MGLTilePyramidDownloadRegion init] is unavailable. " + @"Use -initWithStyleURL:bounds:fromZoomLevel:toZoomLevel: instead."]; + return nil; +} + +- (instancetype)initWithStyleURL:(NSURL *)styleURL bounds:(MGLCoordinateBounds)bounds fromZoomLevel:(double)minimumZoomLevel toZoomLevel:(double)maximumZoomLevel { + if (self = [super init]) { + if (!styleURL) { + styleURL = [MGLStyle streetsStyleURL]; + } + + if (!styleURL.scheme) { + // Assume a relative path into the application bundle. + styleURL = [NSURL URLWithString:[@"asset://" stringByAppendingString:styleURL.absoluteString]]; + } + + _styleURL = styleURL; + _bounds = bounds; + _minimumZoomLevel = minimumZoomLevel; + _maximumZoomLevel = maximumZoomLevel; + } + return self; +} + +- (instancetype)initWithOfflineRegionDefinition:(const mbgl::OfflineRegionDefinition &)definition { + NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())]; + MGLCoordinateBounds bounds = MGLCoordinateBoundsFromLatLngBounds(definition.bounds); + return [self initWithStyleURL:styleURL bounds:bounds fromZoomLevel:definition.minZoom toZoomLevel:definition.maxZoom]; +} + +- (const mbgl::OfflineRegionDefinition)offlineRegionDefinition { +#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR + const float scaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)] ? [[UIScreen mainScreen] nativeScale] : [[UIScreen mainScreen] scale]; +#elif TARGET_OS_MAC + const float scaleFactor = [NSScreen mainScreen].backingScaleFactor; +#endif + return mbgl::OfflineTilePyramidRegionDefinition(_styleURL.absoluteString.UTF8String, + MGLLatLngBoundsFromCoordinateBounds(_bounds), + _minimumZoomLevel, _maximumZoomLevel, + scaleFactor); +} + +@end -- cgit v1.2.1