Files
MapLibreParticleLab/platform/darwin/src/MGLMapSnapshotter.mm
Thiago Marcos P. Santos 06f1dc48a2 [core] Make the BackgroundScheduler a singleton
- Do not carry it over everywhere as parameter, it is a shared
  instance anyway and the lifecycle is pretty much the app lifecycle
  from the moment we instantiate a map.
- Rename to BackgroundScheduler because it is a Scheduler that will
  do tasks in the background, we don't make assumptions if it is a
  thread pool or a single thread.
- Most importantly, remove the dependency from `core` on `platform`.
2019-05-10 14:20:38 +03:00

631 lines
26 KiB
Plaintext

#import "MGLMapSnapshotter.h"
#import <mbgl/actor/actor.hpp>
#import <mbgl/actor/scheduler.hpp>
#import <mbgl/util/geo.hpp>
#import <mbgl/map/map_options.hpp>
#import <mbgl/map/map_snapshotter.hpp>
#import <mbgl/map/camera.hpp>
#import <mbgl/storage/resource_options.hpp>
#import <mbgl/storage/default_file_source.hpp>
#import <mbgl/util/string.hpp>
#import "MGLOfflineStorage_Private.h"
#import "MGLGeometry_Private.h"
#import "NSBundle+MGLAdditions.h"
#import "MGLStyle.h"
#import "MGLAttributionInfo_Private.h"
#import "MGLLoggingConfiguration_Private.h"
#import "MGLRendererConfiguration.h"
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
#import "MGLMapboxEvents.h"
#endif
#if TARGET_OS_IPHONE
#import "UIImage+MGLAdditions.h"
#else
#import "NSImage+MGLAdditions.h"
#import <CoreGraphics/CoreGraphics.h>
#import <QuartzCore/QuartzCore.h>
#endif
const CGPoint MGLLogoImagePosition = CGPointMake(8, 8);
const CGFloat MGLSnapshotterMinimumPixelSize = 64;
@implementation MGLMapSnapshotOptions
- (instancetype _Nonnull)initWithStyleURL:(nullable NSURL *)styleURL camera:(MGLMapCamera *)camera size:(CGSize)size
{
MGLLogDebug(@"Initializing withStyleURL: %@ camera: %@ size: %@", styleURL, camera, MGLStringFromSize(size));
self = [super init];
if (self)
{
if ( !styleURL)
{
styleURL = [MGLStyle streetsStyleURLWithVersion:MGLStyleDefaultVersion];
}
_styleURL = styleURL;
_size = size;
_camera = camera;
#if TARGET_OS_IPHONE
_scale = [UIScreen mainScreen].scale;
#else
_scale = [NSScreen mainScreen].backingScaleFactor;
#endif
}
return self;
}
@end
@interface MGLMapSnapshot()
- (instancetype)initWithImage:(nullable MGLImage *)image scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn;
@property (nonatomic) CGFloat scale;
@end
@implementation MGLMapSnapshot {
mbgl::MapSnapshotter::PointForFn _pointForFn;
mbgl::MapSnapshotter::LatLngForFn _latLngForFn;
}
- (instancetype)initWithImage:(nullable MGLImage *)image scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn
{
self = [super init];
if (self) {
_pointForFn = std::move(pointForFn);
_latLngForFn = std::move(latLngForFn);
_scale = scale;
_image = image;
}
return self;
}
#if TARGET_OS_IPHONE
- (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate
{
mbgl::ScreenCoordinate sc = _pointForFn(MGLLatLngFromLocationCoordinate2D(coordinate));
return CGPointMake(sc.x, sc.y);
}
- (CLLocationCoordinate2D)coordinateForPoint:(CGPoint)point
{
mbgl::LatLng latLng = _latLngForFn(mbgl::ScreenCoordinate(point.x, point.y));
return MGLLocationCoordinate2DFromLatLng(latLng);
}
#else
- (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate
{
mbgl::ScreenCoordinate sc = _pointForFn(MGLLatLngFromLocationCoordinate2D(coordinate));
return NSMakePoint(sc.x, self.image.size.height - sc.y);
}
- (CLLocationCoordinate2D)coordinateForPoint:(NSPoint)point
{
auto screenCoord = mbgl::ScreenCoordinate(point.x, self.image.size.height - point.y);
mbgl::LatLng latLng = _latLngForFn(screenCoord);
return MGLLocationCoordinate2DFromLatLng(latLng);
}
#endif
@end
@interface MGLMapSnapshotter()
@property (nonatomic) BOOL cancelled;
@property (nonatomic) BOOL terminated;
@property (nonatomic) dispatch_queue_t resultQueue;
@property (nonatomic, copy) MGLMapSnapshotCompletionHandler completion;
+ (void)completeWithErrorCode:(MGLErrorCode)errorCode description:(nonnull NSString*)description onQueue:(dispatch_queue_t)queue completion:(MGLMapSnapshotCompletionHandler)completion;
@end
@implementation MGLMapSnapshotter {
std::unique_ptr<mbgl::MapSnapshotter> _mbglMapSnapshotter;
std::unique_ptr<mbgl::Actor<mbgl::MapSnapshotter::Callback>> _snapshotCallback;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
if (_completion) {
MGLAssert(_snapshotCallback, @"Snapshot in progress - there should be a valid callback");
[MGLMapSnapshotter completeWithErrorCode:MGLErrorCodeSnapshotFailed
description:@"MGLMapSnapshotter deallocated prior to snapshot completion."
onQueue:_resultQueue
completion:_completion];
}
}
- (instancetype)init {
NSAssert(NO, @"Please use -[MGLMapSnapshotter initWithOptions:]");
[super doesNotRecognizeSelector:_cmd];
return nil;
}
- (instancetype)initWithOptions:(MGLMapSnapshotOptions *)options
{
MGLLogDebug(@"Initializing withOptions: %@", options);
self = [super init];
if (self) {
[self setOptions:options];
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];
#else
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:NSApplicationWillTerminateNotification object:nil];
#endif
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
[MGLMapboxEvents pushTurnstileEvent];
#endif
}
return self;
}
- (void)applicationWillTerminate:(NSNotification *)notification
{
if (self.completion) {
[self cancel];
}
_mbglMapSnapshotter.reset();
_snapshotCallback.reset();
self.terminated = YES;
}
- (void)startWithCompletionHandler:(MGLMapSnapshotCompletionHandler)completion
{
MGLLogDebug(@"Starting withCompletionHandler: %@", completion);
[self startWithQueue:dispatch_get_main_queue() completionHandler:completion];
}
- (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completion
{
if (!mbgl::Scheduler::GetCurrent()) {
[NSException raise:NSInvalidArgumentException
format:@"startWithQueue:completionHandler: must be called from a thread with an active run loop."];
}
if (self.completion) {
// Consider replacing this exception with an error passed to the completion block.
[NSException raise:NSInternalInconsistencyException
format:@"Already started this snapshotter."];
}
if (self.terminated) {
[NSException raise:NSInternalInconsistencyException
format:@"Starting a snapshotter after application termination is not supported."];
}
self.completion = completion;
self.resultQueue = queue;
self.cancelled = NO;
__weak __typeof__(self) weakSelf = self;
// mbgl::Scheduler::GetCurrent() scheduler means "run callback on current (ie UI/main) thread"
// capture weakSelf to avoid retain cycle if callback is never called (ie snapshot cancelled)
_snapshotCallback = std::make_unique<mbgl::Actor<mbgl::MapSnapshotter::Callback>>(
*mbgl::Scheduler::GetCurrent(),
[=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn, mbgl::MapSnapshotter::LatLngForFn latLngForFn) {
__typeof__(self) strongSelf = weakSelf;
// If self had died, _snapshotCallback would have been destroyed and this block would not be executed
MGLCAssert(strongSelf, @"Snapshot callback executed after being destroyed.");
if (!strongSelf.completion)
return;
if (mbglError) {
NSString *description = @(mbgl::util::toString(mbglError).c_str());
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description};
NSError *error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeSnapshotFailed userInfo:userInfo];
// Dispatch to result queue
dispatch_async(queue, ^{
strongSelf.completion(nil, error);
strongSelf.completion = nil;
});
} else {
#if TARGET_OS_IPHONE
MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image) scale:strongSelf.options.scale];
#else
MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image)];
mglImage.size = NSMakeSize(mglImage.size.width / strongSelf.options.scale,
mglImage.size.height / strongSelf.options.scale);
#endif
[strongSelf drawAttributedSnapshot:attributions snapshotImage:mglImage pointForFn:pointForFn latLngForFn:latLngForFn];
}
strongSelf->_snapshotCallback = NULL;
});
// Launches snapshot on background Thread owned by mbglMapSnapshotter
// _snapshotCallback->self() is an ActorRef: if the callback is destroyed, further messages
// to the callback are just no-ops
_mbglMapSnapshotter->snapshot(_snapshotCallback->self());
}
+ (MGLImage*)drawAttributedSnapshotWorker:(mbgl::MapSnapshotter::Attributions)attributions snapshotImage:(MGLImage *)mglImage pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn scale:(CGFloat)scale size:(CGSize)size {
NSArray<MGLAttributionInfo *>* attributionInfo = [MGLMapSnapshotter generateAttributionInfos:attributions];
#if TARGET_OS_IPHONE
MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong;
for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) {
attributionInfoStyle = (MGLAttributionInfoStyle)styleValue;
CGSize attributionSize = [MGLMapSnapshotter attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle attributionInfo:attributionInfo];
if (attributionSize.width <= mglImage.size.width) {
break;
}
}
UIImage *logoImage = [MGLMapSnapshotter logoImageWithStyle:attributionInfoStyle];
CGSize attributionBackgroundSize = [MGLMapSnapshotter attributionTextSizeWithStyle:attributionInfoStyle attributionInfo:attributionInfo];
CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, mglImage.size.height - (MGLLogoImagePosition.y + logoImage.size.height), logoImage.size.width, logoImage.size.height);
CGPoint attributionOrigin = CGPointMake(mglImage.size.width - 10 - attributionBackgroundSize.width,
logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1);
if (!logoImage) {
CGSize defaultLogoSize = [MGLMapSnapshotter mapboxLongStyleLogo].size;
logoImageRect = CGRectMake(0, mglImage.size.height - (MGLLogoImagePosition.y + defaultLogoSize.height), 0, defaultLogoSize.height);
attributionOrigin = CGPointMake(10, logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1);
}
CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x,
attributionOrigin.y,
attributionBackgroundSize.width,
attributionBackgroundSize.height);
CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10,
attributionBackgroundFrame.origin.y - 1);
CGRect cropRect = CGRectMake(attributionBackgroundFrame.origin.x * mglImage.scale,
attributionBackgroundFrame.origin.y * mglImage.scale,
attributionBackgroundSize.width * mglImage.scale,
attributionBackgroundSize.height * mglImage.scale);
UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, scale);
[mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)];
[logoImage drawInRect:logoImageRect];
UIImage *currentImage = UIGraphicsGetImageFromCurrentImageContext();
CGImageRef attributionImageRef = CGImageCreateWithImageInRect([currentImage CGImage], cropRect);
UIImage *attributionImage = [UIImage imageWithCGImage:attributionImageRef];
CGImageRelease(attributionImageRef);
CIImage *ciAttributionImage = [[CIImage alloc] initWithCGImage:attributionImage.CGImage];
UIImage *blurredAttributionBackground = [MGLMapSnapshotter blurredAttributionBackground:ciAttributionImage];
[blurredAttributionBackground drawInRect:attributionBackgroundFrame];
[MGLMapSnapshotter drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition attributionInfo:attributionInfo];
UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return compositedImage;
#else
NSSize targetSize = NSMakeSize(size.width, size.height);
NSRect targetFrame = NSMakeRect(0, 0, targetSize.width, targetSize.height);
MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong;
for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) {
attributionInfoStyle = (MGLAttributionInfoStyle)styleValue;
CGSize attributionSize = [MGLMapSnapshotter attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle attributionInfo:attributionInfo];
if (attributionSize.width <= mglImage.size.width) {
break;
}
}
NSImage *logoImage = [MGLMapSnapshotter logoImageWithStyle:attributionInfoStyle];
CGSize attributionBackgroundSize = [MGLMapSnapshotter attributionTextSizeWithStyle:attributionInfoStyle attributionInfo:attributionInfo];
NSImage *sourceImage = mglImage;
CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, MGLLogoImagePosition.y, logoImage.size.width, logoImage.size.height);
CGPoint attributionOrigin = CGPointMake(targetFrame.size.width - 10 - attributionBackgroundSize.width,
MGLLogoImagePosition.y + 1);
if (!logoImage) {
CGSize defaultLogoSize = [MGLMapSnapshotter mapboxLongStyleLogo].size;
logoImageRect = CGRectMake(0, MGLLogoImagePosition.y, 0, defaultLogoSize.height);
attributionOrigin = CGPointMake(10, attributionOrigin.y);
}
CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x,
attributionOrigin.y,
attributionBackgroundSize.width,
attributionBackgroundSize.height);
CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10,
logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2));
NSImage *compositedImage = nil;
NSImageRep *sourceImageRep = [sourceImage bestRepresentationForRect:targetFrame
context:nil
hints:nil];
compositedImage = [[NSImage alloc] initWithSize:targetSize];
[compositedImage lockFocus];
[sourceImageRep drawInRect: targetFrame];
if (logoImage) {
[logoImage drawInRect:logoImageRect];
}
NSBitmapImageRep *attributionBackground = [[NSBitmapImageRep alloc] initWithFocusedViewRect:attributionBackgroundFrame];
CIImage *attributionBackgroundImage = [[CIImage alloc] initWithCGImage:[attributionBackground CGImage]];
NSImage *blurredAttributionBackground = [MGLMapSnapshotter blurredAttributionBackground:attributionBackgroundImage];
[blurredAttributionBackground drawInRect:attributionBackgroundFrame];
[MGLMapSnapshotter drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition attributionInfo:attributionInfo];
[compositedImage unlockFocus];
return compositedImage;
#endif
}
- (void)drawAttributedSnapshot:(mbgl::MapSnapshotter::Attributions)attributions snapshotImage:(MGLImage *)mglImage pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn {
// Process image watermark in a work queue
dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t resultQueue = self.resultQueue;
// Capture scale and size by value to avoid accessing self from another thread
CGFloat scale = self.options.scale;
CGSize size = self.options.size;
// pointForFn is a copyable std::function that captures state by value: see MapSnapshotter::Impl::snapshot
__weak __typeof__(self) weakself = self;
dispatch_async(workQueue, ^{
// Call a class method to ensure we're not accidentally capturing self
MGLImage *compositedImage = [MGLMapSnapshotter drawAttributedSnapshotWorker:attributions snapshotImage:mglImage pointForFn:pointForFn latLngForFn:latLngForFn scale:scale size:size];
// Dispatch result to origin queue
dispatch_async(resultQueue, ^{
__typeof__(self) strongself = weakself;
if (strongself.completion) {
MGLMapSnapshot* snapshot = [[MGLMapSnapshot alloc] initWithImage:compositedImage
scale:scale
pointForFn:pointForFn
latLngForFn:latLngForFn];
strongself.completion(snapshot, nil);
strongself.completion = nil;
}
});
});
}
+ (NSArray<MGLAttributionInfo *>*) generateAttributionInfos:(mbgl::MapSnapshotter::Attributions)attributions {
NSMutableArray *infos = [NSMutableArray array];
#if TARGET_OS_IPHONE
CGFloat fontSize = [UIFont smallSystemFontSize];
UIColor *attributeFontColor = [UIColor blackColor];
#else
CGFloat fontSize = [NSFont systemFontSizeForControlSize:NSMiniControlSize];
NSColor *attributeFontColor = [NSColor blackColor];
#endif
for (auto attribution = attributions.begin(); attribution != attributions.end(); ++attribution) {
NSString *attributionHTMLString = @(attribution->c_str());
NSArray *tileSetInfos = [MGLAttributionInfo attributionInfosFromHTMLString:attributionHTMLString
fontSize:fontSize
linkColor:attributeFontColor];
[infos growArrayByAddingAttributionInfosFromArray:tileSetInfos];
}
return infos;
}
+ (void)drawAttributionTextWithStyle:(MGLAttributionInfoStyle)attributionInfoStyle origin:(CGPoint)origin attributionInfo:(NSArray<MGLAttributionInfo *>*)attributionInfo
{
for (MGLAttributionInfo *info in attributionInfo) {
if (info.isFeedbackLink) {
continue;
}
NSAttributedString *attribution = [info titleWithStyle:attributionInfoStyle];
[attribution drawAtPoint:origin];
origin.x += [attribution size].width + 10;
}
}
+ (MGLImage *)blurredAttributionBackground:(CIImage *)backgroundImage
{
CGAffineTransform transform = CGAffineTransformIdentity;
CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"];
[clamp setValue:backgroundImage forKey:kCIInputImageKey];
[clamp setValue:[NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)] forKey:@"inputTransform"];
CIFilter *attributionBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
[attributionBlurFilter setValue:[clamp outputImage] forKey:kCIInputImageKey];
[attributionBlurFilter setValue:@10 forKey:kCIInputRadiusKey];
CIFilter *attributionColorFilter = [CIFilter filterWithName:@"CIColorControls"];
[attributionColorFilter setValue:[attributionBlurFilter outputImage] forKey:kCIInputImageKey];
[attributionColorFilter setValue:@(0.1) forKey:kCIInputBrightnessKey];
CIImage *blurredImage = attributionColorFilter.outputImage;
CIContext *ctx = [CIContext contextWithOptions:nil];
CGImageRef cgimg = [ctx createCGImage:blurredImage fromRect:[backgroundImage extent]];
MGLImage *image;
#if TARGET_OS_IPHONE
image = [UIImage imageWithCGImage:cgimg];
#else
image = [[NSImage alloc] initWithCGImage:cgimg size:[backgroundImage extent].size];
#endif
CGImageRelease(cgimg);
return image;
}
+ (MGLImage *)logoImageWithStyle:(MGLAttributionInfoStyle)style
{
MGLImage *logoImage;
switch (style) {
case MGLAttributionInfoStyleLong:
logoImage = [MGLMapSnapshotter mapboxLongStyleLogo];
break;
case MGLAttributionInfoStyleMedium:
#if TARGET_OS_IPHONE
logoImage = [UIImage imageNamed:@"mapbox_helmet" inBundle:[NSBundle mgl_frameworkBundle] compatibleWithTraitCollection:nil];
#else
logoImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox_helmet" ofType:@"pdf"]];
#endif
break;
case MGLAttributionInfoStyleShort:
logoImage = nil;
break;
}
return logoImage;
}
+ (MGLImage *)mapboxLongStyleLogo
{
MGLImage *logoImage;
#if TARGET_OS_IPHONE
logoImage =[UIImage imageNamed:@"mapbox" inBundle:[NSBundle mgl_frameworkBundle] compatibleWithTraitCollection:nil];
#else
logoImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox" ofType:@"pdf"]];
#endif
return logoImage;
}
+ (CGSize)attributionSizeWithLogoStyle:(MGLAttributionInfoStyle)logoStyle sourceAttributionStyle:(MGLAttributionInfoStyle)attributionStyle attributionInfo:(NSArray<MGLAttributionInfo *>*)attributionInfo
{
MGLImage *logoImage = [self logoImageWithStyle:logoStyle];
CGSize attributionBackgroundSize = [MGLMapSnapshotter attributionTextSizeWithStyle:attributionStyle attributionInfo:attributionInfo];
CGSize attributionSize = CGSizeZero;
if (logoImage) {
attributionSize.width = MGLLogoImagePosition.x + logoImage.size.width + 10;
}
attributionSize.width = attributionSize.width + 10 + attributionBackgroundSize.width + 10;
attributionSize.height = MAX(logoImage.size.height, attributionBackgroundSize.height);
return attributionSize;
}
+ (CGSize)attributionTextSizeWithStyle:(MGLAttributionInfoStyle)attributionStyle attributionInfo:(NSArray<MGLAttributionInfo *>*)attributionInfo
{
CGSize attributionBackgroundSize = CGSizeMake(10, 0);
for (MGLAttributionInfo *info in attributionInfo) {
if (info.isFeedbackLink) {
continue;
}
CGSize attributionSize = [info titleWithStyle:attributionStyle].size;
attributionBackgroundSize.width += attributionSize.width + 10;
attributionBackgroundSize.height = MAX(attributionSize.height, attributionBackgroundSize.height);
}
return attributionBackgroundSize;
}
- (void)cancel
{
MGLLogInfo(@"Cancelling snapshotter.");
self.cancelled = YES;
if (_snapshotCallback) {
[MGLMapSnapshotter completeWithErrorCode:MGLErrorCodeSnapshotFailed
description:[NSString stringWithFormat:@"MGLMapSnapshotter cancelled from %s", __PRETTY_FUNCTION__]
onQueue:self.resultQueue
completion:self.completion];
self.completion = nil;
}
_snapshotCallback.reset();
_mbglMapSnapshotter.reset();
}
+ (void)completeWithErrorCode:(MGLErrorCode)errorCode description:(nonnull NSString*)description onQueue:(dispatch_queue_t)queue completion:(MGLMapSnapshotCompletionHandler)completion {
// The snapshot hasn't completed, so we should alert the caller
if (completion && queue) {
dispatch_async(queue, ^{
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description};
NSError *error = [NSError errorWithDomain:MGLErrorDomain
code:errorCode
userInfo:userInfo];
completion(NULL, error);
});
}
}
- (void)setOptions:(MGLMapSnapshotOptions *)options
{
if (_terminated) {
[NSException raise:NSInternalInconsistencyException
format:@"Calling MGLMapSnapshotter.options after application termination is not supported."];
}
MGLLogDebug(@"Setting options: %@", options);
if (_completion) {
[self cancel];
}
_cancelled = NO;
_options = options;
auto mbglFileSource = [[MGLOfflineStorage sharedOfflineStorage] mbglFileSource];
std::string styleURL = std::string([options.styleURL.absoluteString UTF8String]);
std::pair<bool, std::string> style = std::make_pair(false, styleURL);
// Size; taking into account the minimum texture size for OpenGL ES
// For non retina screens the ratio is 1:1 MGLSnapshotterMinimumPixelSize
mbgl::Size size = {
static_cast<uint32_t>(MAX(options.size.width, MGLSnapshotterMinimumPixelSize)),
static_cast<uint32_t>(MAX(options.size.height, MGLSnapshotterMinimumPixelSize))
};
float pixelRatio = MAX(options.scale, 1);
// Camera options
mbgl::CameraOptions cameraOptions;
if (CLLocationCoordinate2DIsValid(options.camera.centerCoordinate)) {
cameraOptions.center = MGLLatLngFromLocationCoordinate2D(options.camera.centerCoordinate);
}
cameraOptions.bearing = MAX(0, options.camera.heading);
cameraOptions.zoom = MAX(0, options.zoomLevel);
cameraOptions.pitch = MAX(0, options.camera.pitch);
// Region
mbgl::optional<mbgl::LatLngBounds> coordinateBounds;
if (!MGLCoordinateBoundsIsEmpty(options.coordinateBounds)) {
coordinateBounds = MGLLatLngBoundsFromCoordinateBounds(options.coordinateBounds);
}
// App-global configuration
MGLRendererConfiguration* config = [MGLRendererConfiguration currentConfiguration];
mbgl::ResourceOptions resourceOptions;
resourceOptions.withCachePath([[MGLOfflineStorage sharedOfflineStorage] mbglCachePath])
.withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String);
// Create the snapshotter
_mbglMapSnapshotter = std::make_unique<mbgl::MapSnapshotter>(
style, size, pixelRatio, cameraOptions, coordinateBounds, config.cacheDir, config.localFontFamilyName, resourceOptions);
}
@end