From 5213133dc74267bc04d312e5127336b24459b346 Mon Sep 17 00:00:00 2001 From: Fabian Guerra Date: Mon, 18 Sep 2017 17:04:03 -0400 Subject: [PATCH 01/11] [ios, macos] Improve snap shotter documentation. --- platform/darwin/src/MGLMapSnapshotter.h | 59 ++++++++++++------- platform/darwin/src/MGLMapSnapshotter.mm | 28 +++++---- platform/darwin/src/MGLTypes.h | 2 + platform/ios/app/MBXSnapshotsViewController.m | 28 ++++----- platform/macos/app/MapDocument.m | 29 +++++---- 5 files changed, 91 insertions(+), 55 deletions(-) diff --git a/platform/darwin/src/MGLMapSnapshotter.h b/platform/darwin/src/MGLMapSnapshotter.h index a2a4f1b3311..d39b252b517 100644 --- a/platform/darwin/src/MGLMapSnapshotter.h +++ b/platform/darwin/src/MGLMapSnapshotter.h @@ -12,40 +12,46 @@ MGL_EXPORT @interface MGLMapSnapshotOptions : NSObject /** - Creates a set of options with the minimum required information - @param styleURL the style url to use - @param camera the camera settings - @param size the image size + Creates a set of options with the minimum required information. + + @param styleURL URL of the map style to snapshot. The URL may be a full HTTP or HTTPS URL, + a Mapbox URL indicating the style’s map ID (mapbox://styles/{user}/{style}), or a path + to a local file relative to the application’s resource path. Specify nil for the default style. + @param size The image size. */ -- (instancetype)initWithStyleURL:(NSURL*)styleURL camera:(MGLMapCamera*)camera size:(CGSize)size; +- (instancetype)initWithStyleURL:(nullable NSURL*)styleURL camera:(MGLMapCamera*)camera size:(CGSize)size; -#pragma mark - Configuring the map +#pragma mark - Configuring the Map /** - The style URL for these options. + URL of the map style to snapshot. The URL may be a full HTTP or HTTPS URL, + a Mapbox URL indicating the style’s map ID (mapbox://styles/{user}/{style}), or a path + to a local file relative to the application’s resource path. Specify nil for the default style. */ -@property (nonatomic, readonly) NSURL* styleURL; +@property (nonatomic, readonly) NSURL *styleURL; /** - The zoom. Default is 0. + The default zoom level is 0. Overrides the altitude in the mapCamera options if set. */ -@property (nonatomic) double zoom; +@property (nonatomic) double zoomLevel; /** - The `MGLMapcamera` options to use. + A camera representing the viewport visible in the snapshot. */ -@property (nonatomic) MGLMapCamera* camera; +@property (nonatomic) MGLMapCamera *camera; /** - A region to capture. Overrides the center coordinate - in the mapCamera options if set + The cooordinate rectangle that encompasses the bounds to capture. Overrides the center coordinate + in the mapCamera options if set. */ -@property (nonatomic) MGLCoordinateBounds region; +@property (nonatomic) MGLCoordinateBounds coordinateBounds; -#pragma mark - Configuring the image +#pragma mark - Configuring the Image /** - The size of the output image. Minimum is 64x64 + The size of the output image. + + The image may be no less than 64 pixels wide and no less than 64 pixels tall. */ @property (nonatomic, readonly) CGSize size; @@ -57,18 +63,26 @@ MGL_EXPORT @end +#if TARGET_OS_IPHONE /** A block to processes the result or error of a snapshot request. - The result will be either an `MGLImage` or a `NSError` + @param snapshot The `UIImage` that was generated or `nil` if an error occurred. + @param error The error that occured or `nil` when succesful. + */ +typedef void (^MGLMapSnapshotCompletionHandler)(UIImage* _Nullable snapshot, NSError* _Nullable error); +#else +/** + A block to processes the result or error of a snapshot request. - @param snapshot The image that was generated or `nil` if an error occurred. + @param snapshot The `NSImage` that was generated or `nil` if an error occurred. @param error The eror that occured or `nil` when succesful. */ -typedef void (^MGLMapSnapshotCompletionHandler)(MGLImage* _Nullable snapshot, NSError* _Nullable error); +typedef void (^MGLMapSnapshotCompletionHandler)(NSImage* _Nullable snapshot, NSError* _Nullable error); +#endif /** - A utility object for capturing map-based images. + An immutable utility object for capturing map-based images. */ MGL_EXPORT @interface MGLMapSnapshotter : NSObject @@ -92,6 +106,9 @@ MGL_EXPORT /** Cancels the snapshot creation request, if any. + + Once you call this method, you cannot resume the snapshot. In order to obtain the + snapshot, create a new `MGLMapSnapshotter` object. */ - (void)cancel; diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index c81fd39c4aa..b1254b5d3b4 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -13,6 +13,7 @@ #import "MGLOfflineStorage_Private.h" #import "MGLGeometry_Private.h" #import "NSBundle+MGLAdditions.h" +#import "MGLStyle.h" #if TARGET_OS_IPHONE #import "UIImage+MGLAdditions.h" @@ -20,12 +21,18 @@ #import "NSImage+MGLAdditions.h" #endif +const CGPoint MGLLogoImagePosition = CGPointMake(8, 8); + @implementation MGLMapSnapshotOptions -- (instancetype _Nonnull)initWithStyleURL:(NSURL* _Nonnull)styleURL camera:(MGLMapCamera*)camera size:(CGSize) size; +- (instancetype _Nonnull)initWithStyleURL:(nullable NSURL*)styleURL camera:(MGLMapCamera*)camera size:(CGSize) size; { self = [super init]; if (self) { + if ( !styleURL) + { + styleURL = [MGLStyle streetsStyleURLWithVersion:MGLStyleDefaultVersion]; + } _styleURL = styleURL; _size = size; _camera = camera; @@ -73,17 +80,17 @@ - (instancetype)initWithOptions:(MGLMapSnapshotOptions*)options; cameraOptions.center = MGLLatLngFromLocationCoordinate2D(options.camera.centerCoordinate); } cameraOptions.angle = MAX(0, options.camera.heading) * mbgl::util::DEG2RAD; - cameraOptions.zoom = MAX(0, options.zoom); + cameraOptions.zoom = MAX(0, options.zoomLevel); cameraOptions.pitch = MAX(0, options.camera.pitch); // Region - mbgl::optional region; - if (!MGLCoordinateBoundsIsEmpty(options.region)) { - region = MGLLatLngBoundsFromCoordinateBounds(options.region); + mbgl::optional coordinateBounds; + if (!MGLCoordinateBoundsIsEmpty(options.coordinateBounds)) { + coordinateBounds = MGLLatLngBoundsFromCoordinateBounds(options.coordinateBounds); } // Create the snapshotter - mbglMapSnapshotter = std::make_unique(*mbglFileSource, *mbglThreadPool, styleURL, size, pixelRatio, cameraOptions, region); + mbglMapSnapshotter = std::make_unique(*mbglFileSource, *mbglThreadPool, styleURL, size, pixelRatio, cameraOptions, coordinateBounds); } return self; } @@ -96,7 +103,8 @@ - (void)startWithCompletionHandler:(MGLMapSnapshotCompletionHandler)completion; - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completion; { if ([self isLoading]) { - NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"Already started this snapshotter"}; + NSString *errorMessage = NSLocalizedStringWithDefaultValue(@"ALREADY_STARTED_SNAPSHOTTER", nil, nil, @"Already started this snapshotter", "User-friendly error description"); + NSDictionary *userInfo = @{NSLocalizedDescriptionKey: errorMessage}; NSError *error = [NSError errorWithDomain:MGLErrorDomain code:1 userInfo:userInfo]; dispatch_async(queue, ^{ completion(nil, error); @@ -112,7 +120,7 @@ - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshot if (mbglError) { NSString *description = @(mbgl::util::toString(mbglError).c_str()); NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description}; - NSError *error = [NSError errorWithDomain:MGLErrorDomain code:1 userInfo:userInfo]; + NSError *error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeSnapshotFailed userInfo:userInfo]; // Dispatch result to origin queue dispatch_async(queue, ^{ @@ -130,7 +138,7 @@ - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshot UIGraphicsBeginImageContext(mglImage.size); [mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)]; - [logoImage drawInRect:CGRectMake(8, mglImage.size.height - (8 + logoImage.size.height), logoImage.size.width,logoImage.size.height)]; + [logoImage drawInRect:CGRectMake(MGLLogoImagePosition.x, mglImage.size.height - (MGLLogoImagePosition.y + logoImage.size.height), logoImage.size.width,logoImage.size.height)]; UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); @@ -139,7 +147,7 @@ - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshot NSImage *compositedImage = mglImage; [compositedImage lockFocus]; - [logoImage drawInRect:CGRectMake(8, 8, logoImage.size.width,logoImage.size.height)]; + [logoImage drawInRect:CGRectMake(MGLLogoImagePosition.x, MGLLogoImagePosition.y, logoImage.size.width,logoImage.size.height)]; [compositedImage unlockFocus]; #endif diff --git a/platform/darwin/src/MGLTypes.h b/platform/darwin/src/MGLTypes.h index b3227e1cdfb..5c32791c2fe 100644 --- a/platform/darwin/src/MGLTypes.h +++ b/platform/darwin/src/MGLTypes.h @@ -47,6 +47,8 @@ typedef NS_ENUM(NSInteger, MGLErrorCode) { MGLErrorCodeParseStyleFailed = 4, /** An attempt to load the style failed. */ MGLErrorCodeLoadStyleFailed = 5, + /** An error occurred while snapshotting the map. */ + MGLErrorCodeSnapshotFailed = 6, }; /** Options for enabling debugging features in an `MGLMapView` instance. */ diff --git a/platform/ios/app/MBXSnapshotsViewController.m b/platform/ios/app/MBXSnapshotsViewController.m index d26479f0854..ab5ad97c904 100644 --- a/platform/ios/app/MBXSnapshotsViewController.m +++ b/platform/ios/app/MBXSnapshotsViewController.m @@ -18,27 +18,27 @@ @interface MBXSnapshotsViewController () @implementation MBXSnapshotsViewController { // Top row - MGLMapSnapshotter* snapshotterTL; - MGLMapSnapshotter* snapshotterTM; - MGLMapSnapshotter* snapshotterTR; + MGLMapSnapshotter* topLeftSnapshotter; + MGLMapSnapshotter* topCenterSnapshotter; + MGLMapSnapshotter* topRightSnapshotter; // Bottom row - MGLMapSnapshotter* snapshotterBL; - MGLMapSnapshotter* snapshotterBM; - MGLMapSnapshotter* snapshotterBR; + MGLMapSnapshotter* bottomLeftSnapshotter; + MGLMapSnapshotter* bottomCenterSnapshotter; + MGLMapSnapshotter* bottomRightSnapshotter; } - (void)viewDidLoad { [super viewDidLoad]; // Start snapshotters - snapshotterTL = [self startSnapshotterForImageView:_snapshotImageViewTL coordinates:CLLocationCoordinate2DMake(37.7184, -122.4365)]; - snapshotterTM = [self startSnapshotterForImageView:_snapshotImageViewTM coordinates:CLLocationCoordinate2DMake(38.8936, -77.0146)]; - snapshotterTR = [self startSnapshotterForImageView:_snapshotImageViewTR coordinates:CLLocationCoordinate2DMake(-13.1356, -74.2442)]; + topLeftSnapshotter = [self startSnapshotterForImageView:_snapshotImageViewTL coordinates:CLLocationCoordinate2DMake(37.7184, -122.4365)]; + topCenterSnapshotter = [self startSnapshotterForImageView:_snapshotImageViewTM coordinates:CLLocationCoordinate2DMake(38.8936, -77.0146)]; + topRightSnapshotter = [self startSnapshotterForImageView:_snapshotImageViewTR coordinates:CLLocationCoordinate2DMake(-13.1356, -74.2442)]; - snapshotterBL = [self startSnapshotterForImageView:_snapshotImageViewBL coordinates:CLLocationCoordinate2DMake(52.5072, 13.4247)]; - snapshotterBM = [self startSnapshotterForImageView:_snapshotImageViewBM coordinates:CLLocationCoordinate2DMake(60.2118, 24.6754)]; - snapshotterBR = [self startSnapshotterForImageView:_snapshotImageViewBR coordinates:CLLocationCoordinate2DMake(31.2780, 121.4286)]; + bottomLeftSnapshotter = [self startSnapshotterForImageView:_snapshotImageViewBL coordinates:CLLocationCoordinate2DMake(52.5072, 13.4247)]; + bottomCenterSnapshotter = [self startSnapshotterForImageView:_snapshotImageViewBM coordinates:CLLocationCoordinate2DMake(60.2118, 24.6754)]; + bottomRightSnapshotter = [self startSnapshotterForImageView:_snapshotImageViewBR coordinates:CLLocationCoordinate2DMake(31.2780, 121.4286)]; } - (MGLMapSnapshotter*) startSnapshotterForImageView:(UIImageView*) imageView coordinates:(CLLocationCoordinate2D) coordinates { @@ -46,8 +46,8 @@ - (MGLMapSnapshotter*) startSnapshotterForImageView:(UIImageView*) imageView coo MGLMapCamera* mapCamera = [[MGLMapCamera alloc] init]; mapCamera.pitch = 20; mapCamera.centerCoordinate = coordinates; - MGLMapSnapshotOptions* options = [[MGLMapSnapshotOptions alloc] initWithStyleURL:[NSURL URLWithString:@"mapbox://styles/mapbox/traffic-day-v2"] camera:mapCamera size:CGSizeMake(imageView.frame.size.width, imageView.frame.size.height)]; - options.zoom = 10; + MGLMapSnapshotOptions* options = [[MGLMapSnapshotOptions alloc] initWithStyleURL:[MGLStyle satelliteStreetsStyleURL] camera:mapCamera size:CGSizeMake(imageView.frame.size.width, imageView.frame.size.height)]; + options.zoomLevel = 10; // Create and start the snapshotter MGLMapSnapshotter* snapshotter = [[MGLMapSnapshotter alloc] initWithOptions:options]; diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index 36ca4ad2285..d466d2ef4b9 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -163,7 +163,7 @@ - (IBAction)takeSnapshot:(id)sender { MGLMapCamera *camera = self.mapView.camera; MGLMapSnapshotOptions* options = [[MGLMapSnapshotOptions alloc] initWithStyleURL:self.mapView.styleURL camera:camera size:self.mapView.bounds.size]; - options.zoom = self.mapView.zoomLevel; + options.zoomLevel = self.mapView.zoomLevel; // Create and start the snapshotter snapshotter = [[MGLMapSnapshotter alloc] initWithOptions:options]; @@ -177,6 +177,7 @@ - (IBAction)takeSnapshot:(id)sender { // Set the default name for the file and show the panel. NSSavePanel* panel = [NSSavePanel savePanel]; + [panel setNameFieldStringValue:newName]; [panel beginSheetModalForWindow:window completionHandler:^(NSInteger result){ if (result == NSFileHandlingPanelOKButton) { @@ -196,16 +197,24 @@ - (IBAction)takeSnapshot:(id)sender { } - NSString *extension = [[fileURL pathExtension] lowercaseString]; - NSBitmapImageFileType fileType; - if ([extension isEqualToString:@"png"]) { + NSString *uti; + [fileURL getResourceValue:&uti forKey:NSURLTypeIdentifierKey error:nil]; + NSBitmapImageFileType fileType = NSTIFFFileType; + + if (UTTypeConformsTo((__bridge CFStringRef)uti, kUTTypePNG)) { fileType = NSPNGFileType; - } else if ([extension isEqualToString:@"gif"]) { - fileType = NSGIFFileType; - } else if ([extension isEqualToString:@"jpg"] || [extension isEqualToString:@"jpeg"]) { - fileType = NSJPEGFileType; - } else { - fileType = NSTIFFFileType; + } + if (UTTypeConformsTo((__bridge CFStringRef)uti, kUTTypeGIF)) { + fileType = NSGIFFileType; + } + if (UTTypeConformsTo((__bridge CFStringRef)uti, kUTTypeJPEG)) { + fileType = NSJPEGFileType; + } + if (UTTypeConformsTo((__bridge CFStringRef)uti, kUTTypeJPEG2000)) { + fileType = NSJPEGFileType; + } + if (UTTypeConformsTo((__bridge CFStringRef)uti, kUTTypeBMP)) { + fileType = NSBitmapImageFileTypeBMP; } NSData *imageData = [bitmapRep representationUsingType:fileType properties:@{}]; From 64e0cc2eeb96347ac0959a256e45c330728dff59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Fri, 29 Sep 2017 00:08:20 -0700 Subject: [PATCH 02/11] [macos] Save snapshots in correct format --- platform/macos/app/MapDocument.m | 48 +++++++++++++------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index d466d2ef4b9..feef53062b5 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -162,58 +162,48 @@ - (NSURL *)shareURL { - (IBAction)takeSnapshot:(id)sender { MGLMapCamera *camera = self.mapView.camera; - MGLMapSnapshotOptions* options = [[MGLMapSnapshotOptions alloc] initWithStyleURL:self.mapView.styleURL camera:camera size:self.mapView.bounds.size]; + MGLMapSnapshotOptions *options = [[MGLMapSnapshotOptions alloc] initWithStyleURL:self.mapView.styleURL camera:camera size:self.mapView.bounds.size]; options.zoomLevel = self.mapView.zoomLevel; // Create and start the snapshotter snapshotter = [[MGLMapSnapshotter alloc] initWithOptions:options]; - [snapshotter startWithCompletionHandler: ^(NSImage *image, NSError *error) { + [snapshotter startWithCompletionHandler:^(NSImage *image, NSError *error) { if (error) { - NSLog(@"Could not load snapshot: %@", [error localizedDescription]); + NSLog(@"Could not load snapshot: %@", error.localizedDescription); } else { - NSWindow* window = [[[self windowControllers] objectAtIndex:0] window]; - - NSString* newName = [[@"snapshot" stringByDeletingPathExtension] stringByAppendingPathExtension:@"png"]; - // Set the default name for the file and show the panel. - NSSavePanel* panel = [NSSavePanel savePanel]; + NSSavePanel *panel = [NSSavePanel savePanel]; + panel.nameFieldStringValue = [self.mapView.styleURL.lastPathComponent.stringByDeletingPathExtension stringByAppendingPathExtension:@"png"]; + panel.allowedFileTypes = [@[(NSString *)kUTTypePNG] arrayByAddingObjectsFromArray:[NSBitmapImageRep imageUnfilteredTypes]]; - [panel setNameFieldStringValue:newName]; - [panel beginSheetModalForWindow:window completionHandler:^(NSInteger result){ + [panel beginSheetModalForWindow:self.window completionHandler:^(NSInteger result) { if (result == NSFileHandlingPanelOKButton) { // Write the contents in the new format. - NSURL* fileURL = [panel URL]; + NSURL *fileURL = panel.URL; - NSBitmapImageRep *bitmapRep = nil; - for (NSImageRep *imageRep in [image representations]) { - if ([imageRep isKindOfClass:[NSBitmapImageRep class]]){ + NSBitmapImageRep *bitmapRep; + for (NSImageRep *imageRep in image.representations) { + if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) { bitmapRep = (NSBitmapImageRep *)imageRep; break; // stop on first bitmap rep we find } } if (!bitmapRep) { - bitmapRep = [NSBitmapImageRep imageRepWithData:[image TIFFRepresentation]]; - + bitmapRep = [NSBitmapImageRep imageRepWithData:image.TIFFRepresentation]; } - NSString *uti; - [fileURL getResourceValue:&uti forKey:NSURLTypeIdentifierKey error:nil]; + CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileURL.pathExtension, NULL /* inConformingToUTI */); NSBitmapImageFileType fileType = NSTIFFFileType; - - if (UTTypeConformsTo((__bridge CFStringRef)uti, kUTTypePNG)) { + if (UTTypeConformsTo(uti, kUTTypePNG)) { fileType = NSPNGFileType; - } - if (UTTypeConformsTo((__bridge CFStringRef)uti, kUTTypeGIF)) { + } else if (UTTypeConformsTo(uti, kUTTypeGIF)) { fileType = NSGIFFileType; - } - if (UTTypeConformsTo((__bridge CFStringRef)uti, kUTTypeJPEG)) { - fileType = NSJPEGFileType; - } - if (UTTypeConformsTo((__bridge CFStringRef)uti, kUTTypeJPEG2000)) { + } else if (UTTypeConformsTo(uti, kUTTypeJPEG2000)) { + fileType = NSJPEG2000FileType; + } else if (UTTypeConformsTo(uti, kUTTypeJPEG)) { fileType = NSJPEGFileType; - } - if (UTTypeConformsTo((__bridge CFStringRef)uti, kUTTypeBMP)) { + } else if (UTTypeConformsTo(uti, kUTTypeBMP)) { fileType = NSBitmapImageFileTypeBMP; } From d16aea75b90593a7e7e1d5334df86be9992f6d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Fri, 29 Sep 2017 00:08:34 -0700 Subject: [PATCH 03/11] [macos] Renamed snapshot item to Export Image --- platform/macos/app/Base.lproj/MainMenu.xib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/macos/app/Base.lproj/MainMenu.xib b/platform/macos/app/Base.lproj/MainMenu.xib index 9a53ba9d4b8..32438388481 100644 --- a/platform/macos/app/Base.lproj/MainMenu.xib +++ b/platform/macos/app/Base.lproj/MainMenu.xib @@ -128,7 +128,7 @@ - + From dff1bc0f517bd27eb421ba8bc82dbd26fae9a37b Mon Sep 17 00:00:00 2001 From: Fabian Guerra Date: Fri, 29 Sep 2017 16:56:45 -0400 Subject: [PATCH 04/11] [ios, macos] Clarify Snapshotter documentation. --- platform/darwin/src/MGLMapSnapshotter.h | 27 +++++++++++++++--------- platform/darwin/src/MGLMapSnapshotter.mm | 25 ++++++++++++---------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/platform/darwin/src/MGLMapSnapshotter.h b/platform/darwin/src/MGLMapSnapshotter.h index d39b252b517..1b43f9fc1ee 100644 --- a/platform/darwin/src/MGLMapSnapshotter.h +++ b/platform/darwin/src/MGLMapSnapshotter.h @@ -15,34 +15,41 @@ MGL_EXPORT Creates a set of options with the minimum required information. @param styleURL URL of the map style to snapshot. The URL may be a full HTTP or HTTPS URL, - a Mapbox URL indicating the style’s map ID (mapbox://styles/{user}/{style}), or a path - to a local file relative to the application’s resource path. Specify nil for the default style. + a Mapbox URL indicating the style’s map ID (`mapbox://styles/{user}/{style`}), or a path + to a local file relative to the application’s resource path. Specify `nil` for the default style. @param size The image size. */ -- (instancetype)initWithStyleURL:(nullable NSURL*)styleURL camera:(MGLMapCamera*)camera size:(CGSize)size; +- (instancetype)initWithStyleURL:(nullable NSURL *)styleURL camera:(MGLMapCamera *)camera size:(CGSize)size; #pragma mark - Configuring the Map /** - URL of the map style to snapshot. The URL may be a full HTTP or HTTPS URL, - a Mapbox URL indicating the style’s map ID (mapbox://styles/{user}/{style}), or a path - to a local file relative to the application’s resource path. Specify nil for the default style. + URL of the map style to snapshot. */ @property (nonatomic, readonly) NSURL *styleURL; /** - The default zoom level is 0. Overrides the altitude in the mapCamera options if set. + The zoom level. + + The default zoom level is 0. If this property is non-zero and the camera property + is non-nil, the camera’s altitude is ignored in favor of this property’s value. */ @property (nonatomic) double zoomLevel; /** A camera representing the viewport visible in the snapshot. + + If this property is non-nil and the `coordinateBounds` property is set to a non-empty + coordinate bounds, the camera’s center coordinate and altitude are ignored in favor + of the `coordinateBounds` property. */ @property (nonatomic) MGLMapCamera *camera; /** - The cooordinate rectangle that encompasses the bounds to capture. Overrides the center coordinate - in the mapCamera options if set. + The cooordinate rectangle that encompasses the bounds to capture. + + If this property is non-empty and the camera property is non-nil, the camera’s + center coordinate and altitude are ignored in favor of this property’s value. */ @property (nonatomic) MGLCoordinateBounds coordinateBounds; @@ -68,7 +75,7 @@ MGL_EXPORT A block to processes the result or error of a snapshot request. @param snapshot The `UIImage` that was generated or `nil` if an error occurred. - @param error The error that occured or `nil` when succesful. + @param error The error that occured or `nil` when successful. */ typedef void (^MGLMapSnapshotCompletionHandler)(UIImage* _Nullable snapshot, NSError* _Nullable error); #else diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index b1254b5d3b4..3dbebf1f08d 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -22,6 +22,7 @@ #endif const CGPoint MGLLogoImagePosition = CGPointMake(8, 8); +const CGFloat MGLSnapshotterMinimumPixelSize = 64; @implementation MGLMapSnapshotOptions @@ -50,9 +51,9 @@ - (instancetype _Nonnull)initWithStyleURL:(nullable NSURL*)styleURL camera:(MGLM @implementation MGLMapSnapshotter { - std::shared_ptr mbglThreadPool; - std::unique_ptr mbglMapSnapshotter; - std::unique_ptr> snapshotCallback; + std::shared_ptr _mbglThreadPool; + std::unique_ptr _mbglMapSnapshotter; + std::unique_ptr> _snapshotCallback; } - (instancetype)initWithOptions:(MGLMapSnapshotOptions*)options; @@ -62,14 +63,16 @@ - (instancetype)initWithOptions:(MGLMapSnapshotOptions*)options; _loading = false; mbgl::DefaultFileSource *mbglFileSource = [MGLOfflineStorage sharedOfflineStorage].mbglFileSource; - mbglThreadPool = mbgl::sharedThreadPool(); + _mbglThreadPool = mbgl::sharedThreadPool(); std::string styleURL = std::string([options.styleURL.absoluteString UTF8String]); // 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(MAX(options.size.width, 64)), - static_cast(MAX(options.size.height, 64)) + static_cast(MAX(options.size.width, MGLSnapshotterMinimumPixelSize/options.scale)), + static_cast(MAX(options.size.height, MGLSnapshotterMinimumPixelSize/options.scale)) }; float pixelRatio = MAX(options.scale, 1); @@ -90,7 +93,7 @@ - (instancetype)initWithOptions:(MGLMapSnapshotOptions*)options; } // Create the snapshotter - mbglMapSnapshotter = std::make_unique(*mbglFileSource, *mbglThreadPool, styleURL, size, pixelRatio, cameraOptions, coordinateBounds); + _mbglMapSnapshotter = std::make_unique(*mbglFileSource, *_mbglThreadPool, styleURL, size, pixelRatio, cameraOptions, coordinateBounds); } return self; } @@ -115,7 +118,7 @@ - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshot _loading = true; dispatch_async(queue, ^{ - snapshotCallback = std::make_unique>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image) { + _snapshotCallback = std::make_unique>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image) { _loading = false; if (mbglError) { NSString *description = @(mbgl::util::toString(mbglError).c_str()); @@ -158,14 +161,14 @@ - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshot }); } }); - mbglMapSnapshotter->snapshot(snapshotCallback->self()); + _mbglMapSnapshotter->snapshot(_snapshotCallback->self()); }); } - (void)cancel; { - snapshotCallback.reset(); - mbglMapSnapshotter.reset(); + _snapshotCallback.reset(); + _mbglMapSnapshotter.reset(); } @end From b767c212c88cefa2917e3bcb48bf466379a521b5 Mon Sep 17 00:00:00 2001 From: Fredrik Karlsson Date: Mon, 2 Oct 2017 21:40:25 +0200 Subject: [PATCH 05/11] [ios] Fix snapshot scale --- platform/darwin/src/MGLMapSnapshotter.mm | 11 ++++++++++- platform/ios/src/UIImage+MGLAdditions.h | 2 +- platform/ios/src/UIImage+MGLAdditions.mm | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index 3dbebf1f08d..b4c1704b8d2 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -49,6 +49,10 @@ - (instancetype _Nonnull)initWithStyleURL:(nullable NSURL*)styleURL camera:(MGLM @end +@interface MGLMapSnapshotter() +@property (nonatomic) MGLMapSnapshotOptions *options; +@end + @implementation MGLMapSnapshotter { std::shared_ptr _mbglThreadPool; @@ -60,6 +64,7 @@ - (instancetype)initWithOptions:(MGLMapSnapshotOptions*)options; { self = [super init]; if (self) { + _options = options; _loading = false; mbgl::DefaultFileSource *mbglFileSource = [MGLOfflineStorage sharedOfflineStorage].mbglFileSource; @@ -130,7 +135,11 @@ - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshot completion(nil, error); }); } else { +#if TARGET_OS_IPHONE + MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image) scale:self.options.scale]; +#else MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image)]; +#endif // Process image watermark in a work queue dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); @@ -138,7 +147,7 @@ - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshot #if TARGET_OS_IPHONE UIImage *logoImage = [UIImage imageNamed:@"mapbox" inBundle:[NSBundle mgl_frameworkBundle] compatibleWithTraitCollection:nil]; - UIGraphicsBeginImageContext(mglImage.size); + UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, [UIScreen mainScreen].scale); [mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)]; [logoImage drawInRect:CGRectMake(MGLLogoImagePosition.x, mglImage.size.height - (MGLLogoImagePosition.y + logoImage.size.height), logoImage.size.width,logoImage.size.height)]; diff --git a/platform/ios/src/UIImage+MGLAdditions.h b/platform/ios/src/UIImage+MGLAdditions.h index 3c179d63246..22bb7402425 100644 --- a/platform/ios/src/UIImage+MGLAdditions.h +++ b/platform/ios/src/UIImage+MGLAdditions.h @@ -8,7 +8,7 @@ NS_ASSUME_NONNULL_BEGIN - (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image *)styleImage; -- (nullable instancetype)initWithMGLPremultipliedImage:(const mbgl::PremultipliedImage&&)mbglImage; +- (nullable instancetype)initWithMGLPremultipliedImage:(const mbgl::PremultipliedImage&&)mbglImage scale:(CGFloat)scale; - (std::unique_ptr)mgl_styleImageWithIdentifier:(NSString *)identifier; diff --git a/platform/ios/src/UIImage+MGLAdditions.mm b/platform/ios/src/UIImage+MGLAdditions.mm index 7cf1ed9bccc..8ab1d5c259e 100644 --- a/platform/ios/src/UIImage+MGLAdditions.mm +++ b/platform/ios/src/UIImage+MGLAdditions.mm @@ -22,14 +22,14 @@ - (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image *)style return self; } -- (nullable instancetype)initWithMGLPremultipliedImage:(const mbgl::PremultipliedImage&&)mbglImage +- (nullable instancetype)initWithMGLPremultipliedImage:(const mbgl::PremultipliedImage&&)mbglImage scale:(CGFloat)scale { CGImageRef image = CGImageFromMGLPremultipliedImage(mbglImage.clone()); if (!image) { return nil; } - self = [self initWithCGImage:image scale:1.0 orientation:UIImageOrientationUp]; + self = [self initWithCGImage:image scale:scale orientation:UIImageOrientationUp]; CGImageRelease(image); return self; From 6ea1e4129e5381aa6c0722797a72997a990b2710 Mon Sep 17 00:00:00 2001 From: Fabian Guerra Date: Mon, 2 Oct 2017 19:35:08 -0400 Subject: [PATCH 06/11] [macOS] Fix snapshotter 4x scaling. --- platform/darwin/src/MGLMapSnapshotter.mm | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index b4c1704b8d2..e94ee0aaf34 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -156,11 +156,21 @@ - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshot UIGraphicsEndImageContext(); #else NSImage *logoImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox" ofType:@"pdf"]]; - NSImage *compositedImage = mglImage; + NSImage *sourceImage = mglImage; + + NSSize targetSize = NSMakeSize(self.options.size.width, self.options.size.height); + NSRect targetFrame = NSMakeRect(0, 0, targetSize.width, targetSize.height); + NSImage *compositedImage = nil; + NSImageRep *sourceImageRep = [sourceImage bestRepresentationForRect:targetFrame + context:nil + hints:nil]; + compositedImage = [[NSImage alloc] initWithSize:targetSize]; [compositedImage lockFocus]; + [sourceImageRep drawInRect: targetFrame]; [logoImage drawInRect:CGRectMake(MGLLogoImagePosition.x, MGLLogoImagePosition.y, logoImage.size.width,logoImage.size.height)]; [compositedImage unlockFocus]; + #endif // Dispatch result to origin queue From 6beeb8787e196103846c9ba59f9d7894c8635634 Mon Sep 17 00:00:00 2001 From: Fabian Guerra Date: Mon, 2 Oct 2017 21:13:43 -0400 Subject: [PATCH 07/11] [ios] Fix snapshotter final image scale. --- platform/darwin/src/MGLMapSnapshotter.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index e94ee0aaf34..8d94b40acf0 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -147,7 +147,7 @@ - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshot #if TARGET_OS_IPHONE UIImage *logoImage = [UIImage imageNamed:@"mapbox" inBundle:[NSBundle mgl_frameworkBundle] compatibleWithTraitCollection:nil]; - UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, [UIScreen mainScreen].scale); + UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, self.options.scale); [mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)]; [logoImage drawInRect:CGRectMake(MGLLogoImagePosition.x, mglImage.size.height - (MGLLogoImagePosition.y + logoImage.size.height), logoImage.size.width,logoImage.size.height)]; From 9eaed012dd33b34758582907100ba748a850ca9f Mon Sep 17 00:00:00 2001 From: Fabian Guerra Date: Tue, 3 Oct 2017 11:31:30 -0400 Subject: [PATCH 08/11] [ios, macos] Update snapshotter size documentation. --- platform/darwin/src/MGLMapSnapshotter.h | 3 +-- platform/darwin/src/MGLMapSnapshotter.mm | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/platform/darwin/src/MGLMapSnapshotter.h b/platform/darwin/src/MGLMapSnapshotter.h index 1b43f9fc1ee..be4258e0ad7 100644 --- a/platform/darwin/src/MGLMapSnapshotter.h +++ b/platform/darwin/src/MGLMapSnapshotter.h @@ -56,9 +56,8 @@ MGL_EXPORT #pragma mark - Configuring the Image /** - The size of the output image. + The size of the output image, measured in points. - The image may be no less than 64 pixels wide and no less than 64 pixels tall. */ @property (nonatomic, readonly) CGSize size; diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index 8d94b40acf0..edc6aaf00d9 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -74,10 +74,9 @@ - (instancetype)initWithOptions:(MGLMapSnapshotOptions*)options; // 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(MAX(options.size.width, MGLSnapshotterMinimumPixelSize/options.scale)), - static_cast(MAX(options.size.height, MGLSnapshotterMinimumPixelSize/options.scale)) + static_cast(MAX(options.size.width, MGLSnapshotterMinimumPixelSize)), + static_cast(MAX(options.size.height, MGLSnapshotterMinimumPixelSize)) }; float pixelRatio = MAX(options.scale, 1); From 21fe8117d63ea0f7503a3779287f1c42fcbb1e4e Mon Sep 17 00:00:00 2001 From: Fabian Guerra Date: Tue, 3 Oct 2017 12:21:13 -0400 Subject: [PATCH 09/11] [ios, macos] Throw an exception when the snapshotter has already started. --- platform/darwin/src/MGLMapSnapshotter.mm | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index edc6aaf00d9..5901f36e5d6 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -110,13 +110,8 @@ - (void)startWithCompletionHandler:(MGLMapSnapshotCompletionHandler)completion; - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completion; { if ([self isLoading]) { - NSString *errorMessage = NSLocalizedStringWithDefaultValue(@"ALREADY_STARTED_SNAPSHOTTER", nil, nil, @"Already started this snapshotter", "User-friendly error description"); - NSDictionary *userInfo = @{NSLocalizedDescriptionKey: errorMessage}; - NSError *error = [NSError errorWithDomain:MGLErrorDomain code:1 userInfo:userInfo]; - dispatch_async(queue, ^{ - completion(nil, error); - }); - return; + [NSException raise:@"MGLAlreadyStartedSnapshotterException" + format:@"Already started this snapshotter."]; } _loading = true; From a072ecc0a9b60891feef003e5fd5b436fa83db87 Mon Sep 17 00:00:00 2001 From: Fabian Guerra Date: Tue, 3 Oct 2017 20:29:13 -0400 Subject: [PATCH 10/11] [ios, macos] Add snapshotter header example. --- platform/darwin/src/MGLMapSnapshotter.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/platform/darwin/src/MGLMapSnapshotter.h b/platform/darwin/src/MGLMapSnapshotter.h index be4258e0ad7..615d39bee4d 100644 --- a/platform/darwin/src/MGLMapSnapshotter.h +++ b/platform/darwin/src/MGLMapSnapshotter.h @@ -89,6 +89,26 @@ typedef void (^MGLMapSnapshotCompletionHandler)(NSImage* _Nullable snapshot, NSE /** An immutable utility object for capturing map-based images. + + ### Example + + ```swift + var camera = MGLMapCamera() + camera.centerCoordinate = CLLocationCoordinate2D(latitude: 37.7184, longitude: -122.4365) + camera.pitch = 20 + + var options = MGLMapSnapshotOptions(styleURL: MGLStyle.satelliteStreetsStyleURL(), camera: camera, size: CGSize(width: 320, height: 480)) + options.zoomLevel = 10 + + var snapshotter = MGLMapSnapshotter(options: options) + snapshotter.start { (image, error) in + if error { + // error handler + } else { + // image handler + } + } + ``` */ MGL_EXPORT @interface MGLMapSnapshotter : NSObject From 73f84e7d24bb1f75e4fb11ee4f2086aa70a76444 Mon Sep 17 00:00:00 2001 From: Fabian Guerra Date: Tue, 3 Oct 2017 20:30:41 -0400 Subject: [PATCH 11/11] [ios, macos] Use one of the predefined Foundation's exception names. --- platform/darwin/src/MGLMapSnapshotter.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index 5901f36e5d6..12b932daa39 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -110,7 +110,7 @@ - (void)startWithCompletionHandler:(MGLMapSnapshotCompletionHandler)completion; - (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completion; { if ([self isLoading]) { - [NSException raise:@"MGLAlreadyStartedSnapshotterException" + [NSException raise:NSInternalInconsistencyException format:@"Already started this snapshotter."]; }