-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[ios, macos] Improve snap shotter documentation. #10020
Conversation
126c793
to
187ee23
Compare
|
||
/** | ||
The size of the output image. Minimum is 64x64 | ||
The size of the output image. Minimum is 64x64. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#9891 (comment) asks if this is points or pixels. My reading of the implementation is that it is pixels. We should add that context here.
187ee23
to
d3cfe72
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#9891 (comment) asks if the cancel
method can be called more than once on the same instance of snapshotter. It appears to me that it can be and that N+1 calls would not cause any problem but I've not actually tested. I agree we should note that multiple calls are/are not a problem.
d3cfe72
to
95fda11
Compare
Tail work #10038 for making snapshotter reusable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This lgtm 👍
@1ec5 do you think there is anything else here we must do for v3.7.0?
95fda11
to
4b2a93c
Compare
@@ -12,40 +12,41 @@ MGL_EXPORT | |||
@interface MGLMapSnapshotOptions : NSObject | |||
|
|||
/** | |||
Creates a set of options with the minimum required information | |||
Creates a set of options with the minimum required information. | |||
|
|||
@param styleURL the style url to use |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parameter documentation should start with a capital letter and end with a period. URL should be capitalized. Note that this differs from the prevailing code style in the Android SDK.
This is based on the documentation for -[MGLMapView initWithFrame: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. Specifynil
for the default style.
(Can local styles be snapshotted?)
/ref #9891 (comment)
@@ -12,40 +12,41 @@ MGL_EXPORT | |||
@interface MGLMapSnapshotOptions : NSObject | |||
|
|||
/** | |||
Creates a set of options with the minimum required information | |||
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 | |||
*/ | |||
- (instancetype)initWithStyleURL:(NSURL*)styleURL camera:(MGLMapCamera*)camera size:(CGSize)size; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
styleURL
should be marked nullable, given the change to the implementation.
/ref #9891 (comment)
|
||
/** | ||
The zoom. Default is 0. | ||
The zoom level. Default is 0. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separate paragraph:
The default zoom level is 0.
*/ | ||
@property (nonatomic) double zoom; | ||
@property (nonatomic) double zoomLevel; | ||
|
||
/** | ||
The `MGLMapcamera` options to use. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: MGLMapCamera. In any case, this description would make more sense:
A camera representing the viewport visible in the snapshot.
*/ | ||
@property (nonatomic) double zoom; | ||
@property (nonatomic) double zoomLevel; | ||
|
||
/** | ||
The `MGLMapcamera` options to use. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like the coordinateBounds
documentation, this comment should explain what happens if both it and coordinateBounds
are set.
What happens if the camera specifies an altitude that differs from the zoomLevel
property? What value ends up being preferred?
@@ -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_SNAPSHOTER", nil, nil, @"Already started this snapshotter", "User-friendly error description"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NSError is meant to be used for errors that are outside the developer’s control, and its localized error description may be displayed to the end user. But if a snapshotter can’t be reused and the developer tries to reuse one, then that’s avoidable programmer error, and we should assert instead of raising an error. If a snapshotter can be reused, then this error should be removed.
Either way, it does seem useful to retain an error parameter just in case, even if it goes unused for the time being.
platform/darwin/src/MGLTypes.h
Outdated
@@ -47,6 +47,8 @@ typedef NS_ENUM(NSInteger, MGLErrorCode) { | |||
MGLErrorCodeParseStyleFailed = 4, | |||
/** An attempt to load the style failed. */ | |||
MGLErrorCodeLoadStyleFailed = 5, | |||
/** An error occurred while attempting to get the callback. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An error occurred while snapshotting the map.
platform/darwin/src/MGLTypes.h
Outdated
@@ -47,6 +47,8 @@ typedef NS_ENUM(NSInteger, MGLErrorCode) { | |||
MGLErrorCodeParseStyleFailed = 4, | |||
/** An attempt to load the style failed. */ | |||
MGLErrorCodeLoadStyleFailed = 5, | |||
/** An error occurred while attempting to get the callback. */ | |||
MGLErrorCodeSnapShotterCallbackFailed = 6, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MGLErrorCodeSnapshotFailed
platform/macos/app/MapDocument.m
Outdated
|
||
for (NSString *imageType in [NSImage imageTypes]) { | ||
if ([uti isEqualToString:imageType]) { | ||
if ([uti containsString:@"png"]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public.png
isn’t the only UTI that contains the substring png
, and UTIs that don’t contain png
may still be PNGs. Use UTTypeConformsTo()
from the Core Services framework to perform this check:
UTTypeConformsTo((__bridge CFStringRef)uti, kUTTypePNG)
platform/macos/app/MapDocument.m
Outdated
} else if ([uti containsString:@"gif"]) { | ||
fileType = NSGIFFileType; | ||
} else if ([uti containsString:@"jpeg"]) { | ||
fileType = NSJPEGFileType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NSBitmapImageFileTypeBMP
and NSBitmapImageFileTypeJPEG2000
are also available.
7cc01ab
to
0895f64
Compare
0895f64
to
5213133
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to the below, #9891 (comment) points out that the menu item should be in title case, not sentence case.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, let’s clarify that we really mean pixels here, because most developers will read this thinking it’s a typo:
The image may be no less than 64 pixels wide (32 points wide on a 2× display) and no less than 64 pixels tall.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ultimately we should handle the conversion ourselves so that developers don’t have to multiply by the backing scale factor, which is inconvenient and inconsistent with MapKit: #10088.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Internally we handle the scale factor so size is actually in points instead of pixels in the end. Do you think this clarifies the issue?
The size of the output image may be no less than the following dimensions:
Non-retina 64 points wide and tall.
2× 32 points wide and tall.
3× 22 points wide and tall.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
|
||
/** | ||
The `MGLMapcamera` options to use. | ||
A camera representing the viewport visible in the snapshot. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 thecoordinateBounds
property.
|
||
/** | ||
The zoom. Default is 0. | ||
The default zoom level is 0. Overrides the altitude in the mapCamera options if set. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 zoom; | ||
@property (nonatomic) double zoomLevel; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It sure would be nice to implement #5583 instead of offering three different ways to set the viewport on a snapshot that interact in unintuitive ways.
/** | ||
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: successful. (×2)
|
||
/** | ||
A utility object for capturing map-based images. | ||
An immutable utility object for capturing map-based images. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As tail work, it would be helpful to add a small code example of working with MGLMapSnapshotter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tail work ticket in our ios-sdk repo https://github.com/mapbox/ios-sdk/issues/332
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was referring to a short code example here inside the header, similar to what we do for classes like MGLShapeSource and MGLMapView, so that the example shows up at the beginning of the MGLMapSnapshotter class reference.
@@ -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]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per #10020 (comment), should we remove this error or replace it with an NSException?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it makes sense let the developer know that it has a snapshotter task running it's easy to forget this. A NSException is too drastic, I would like to continue with the NSError for the time being and evaluate this again once we made the snapshotter reusable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To elaborate, NSError is intended to problems outside the developer’s control, for issues that can’t be fixed in the developer’s own code. If the developer is reusing a snapshotter in their own code, I think that would be an error on their part and an appropriate use of NSException. But if MapKit simply sets the NSError in this case, then I guess it’s OK to follow their lead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with you NSError is intended to problems outside the developer's control. NSException is a better choice. Making the changes.
platform/macos/app/MapDocument.m
Outdated
fileType = NSJPEGFileType; | ||
} | ||
if (UTTypeConformsTo((__bridge CFStringRef)uti, kUTTypeJPEG2000)) { | ||
fileType = NSJPEGFileType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kUTTypeJPEG2000
translates to NSJPEG2000FileType
, not NSJPEGFileType
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in #10090.
} | ||
|
||
// Create the snapshotter | ||
mbglMapSnapshotter = std::make_unique<mbgl::MapSnapshotter>(*mbglFileSource, *mbglThreadPool, styleURL, size, pixelRatio, cameraOptions, region); | ||
mbglMapSnapshotter = std::make_unique<mbgl::MapSnapshotter>(*mbglFileSource, *mbglThreadPool, styleURL, size, pixelRatio, cameraOptions, coordinateBounds); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mbglMapSnapshotter
and the other ivars on MGLMapSnapshotter should begin with underscores.
platform/macos/app/MapDocument.m
Outdated
NSBitmapImageFileType fileType; | ||
if ([extension isEqualToString:@"png"]) { | ||
NSString *uti; | ||
[fileURL getResourceValue:&uti forKey:NSURLTypeIdentifierKey error:nil]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out this doesn’t work because no file has been laid down by this point. This code ends up producing a TIFF no matter what extension is specified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in #10090.
mbgl::Size size = { | ||
static_cast<uint32_t>(MAX(options.size.width, 64)), | ||
static_cast<uint32_t>(MAX(options.size.height, 64)) | ||
static_cast<uint32_t>(MAX(options.size.width, MGLSnapshotterMinimumPixelSize/options.scale)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the developer sets a scale other than the device’s native scale, does the effective minimum pixel size change?
|
||
// Process image watermark in a work queue | ||
dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); | ||
dispatch_async(workQueue, ^{ | ||
#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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't expose a scale
property then override it with [UIScreen mainScreen].scale
. Either remove the scale or grab the scale from the options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fixed it in you branch and going to cp into mine https://github.com/mapbox/mapbox-gl-native/pull/10105/files#diff-56bc5c1e41273957efd873aa83e6c522R139
|
||
/** | ||
The size of the output image. Minimum is 64x64 | ||
The size of the output image. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per #10088 (comment), this property is actually intended to be measured in points (even though the minimum is in pixels).
The size of the output image, measured in points.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is good to go, other than the exception name.
completion(nil, error); | ||
}); | ||
return; | ||
[NSException raise:@"MGLAlreadyStartedSnapshotterException" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per #7258, use one of Foundation's predefined exception names or pull this string out into a public constant.
Fixes #9920