Skip to content
This repository has been archived by the owner on Jun 21, 2023. It is now read-only.

Snapshotter (runtime styling) delegates #235

Merged
merged 5 commits into from
Mar 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 79 additions & 3 deletions platform/darwin/src/MGLMapSnapshotter.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
#import "MGLTypes.h"
#import "MGLGeometry.h"
#import "MGLMapCamera.h"
#import "MGLStyle.h"

NS_ASSUME_NONNULL_BEGIN

@protocol MGLMapSnapshotterDelegate;

/**
An overlay that is placed within a `MGLMapSnapshot`.
To access this object, use `-[MGLMapSnapshotter startWithOverlayHandler:completionHandler:]`.
Expand Down Expand Up @@ -177,8 +180,9 @@ typedef void (^MGLMapSnapshotCompletionHandler)(MGLMapSnapshot* _Nullable snapsh
An `MGLMapSnapshotter` generates static raster images of the map. Each snapshot
image depicts a portion of a map defined by an `MGLMapSnapshotOptions` object
you provide. The snapshotter generates an `MGLMapSnapshot` object
asynchronously, passing it into a completion handler once tiles and other
resources needed for the snapshot are finished loading.
asynchronously, calling `MGLMapSnapshotterDelegate` methods if defined, then
passing it into a completion handler once tiles and other resources needed for
the snapshot are finished loading.

You can change the snapshotter’s options at any time and reuse the snapshotter
for multiple distinct snapshots; however, the snapshotter can only generate one
Expand Down Expand Up @@ -220,7 +224,7 @@ typedef void (^MGLMapSnapshotCompletionHandler)(MGLMapSnapshot* _Nullable snapsh
object's style, camera, and view bounds.
*/
MGL_EXPORT
@interface MGLMapSnapshotter : NSObject
@interface MGLMapSnapshotter : NSObject <MGLStylable>

- (instancetype)init NS_UNAVAILABLE;

Expand Down Expand Up @@ -284,6 +288,78 @@ MGL_EXPORT
*/
@property (nonatomic, readonly, getter=isLoading) BOOL loading;

/**
The snapshotter’s delegate.

The delegate is responsible for responding to significant changes during the
snapshotting process, such as the style loading. Implement a delegate to
customize the style that is depicted by the snapshot.

You set the delegate after initializing the snapshotter but before receiving
the snapshot, typically before starting the snapshot. The snapshotter keeps a
weak reference to its delegate, so you must keep a strong reference to it to
ensure that your style customizations apply.
*/
@property (nonatomic, weak) id <MGLMapSnapshotterDelegate> delegate;

/**
The style displayed in the resulting snapshot.

Unlike the `MGLMapSnapshotOptions.styleURL` property, this property is set to
an object that allows you to manipulate every aspect of the style locally.

This property is set to `nil` until the style finishes loading. If the style
has failed to load, this property is set to `nil`. Because the style loads
asynchronously, you should manipulate it in the
`-[MGLMapSnapshotterDelegate mapSnapshotter:didFinishLoadingStyle:]` method. It
is not possible to manipulate the style before it has finished loading.

@note The default styles provided by Mapbox contain sources and layers with
identifiers that will change over time. Applications that use APIs that
manipulate a style’s sources and layers must first set the style URL to an
explicitly versioned style using a convenience method like
`+[MGLStyle outdoorsStyleURLWithVersion:]` or a manually constructed
`NSURL`.
*/
@property (nonatomic, readonly, nullable) MGLStyle *style;

@end

/**
Optional methods about significant events when creating a snapshot using an
`MGLMapSnapshotter` object.
*/
@protocol MGLMapSnapshotterDelegate <NSObject>
@optional

/**
Tells the delegate that the snapshotter was unable to load data needed for
snapshotting the map.

This method may be called for a variety of reasons, including a network
connection failure or a failure to fetch the style from the server. You can use
the given error message to notify the user that map data is unavailable.

@param snapshotter The snapshotter that is unable to load the data.
@param error The reason the data could not be loaded.
*/
- (void)mapSnapshotterDidFail:(MGLMapSnapshotter *)snapshotter withError:(NSError *)error;
julianrex marked this conversation as resolved.
Show resolved Hide resolved

/**
Tells the delegate that the snapshotter has just finished loading a style.

This method is called in response to
`-[MGLMapSnapshotter startWithQueue:completionHandler:]` as long as the
`MGLMapSnapshotter.delegate` property is set. Changes to sources or layers of
the style being snapshotted do not cause this method to be called.

@param snapshotter The snapshotter that has just loaded a style.
@param style The style that was loaded.
*/
- (void)mapSnapshotter:(MGLMapSnapshotter *)snapshotter didFinishLoadingStyle:(MGLStyle *)style;

- (void)mapSnapshotter:(MGLMapSnapshotter *)snapshotter didFailLoadingImageNamed:(NSString *)name;
julianrex marked this conversation as resolved.
Show resolved Hide resolved

@end

NS_ASSUME_NONNULL_END
54 changes: 51 additions & 3 deletions platform/darwin/src/MGLMapSnapshotter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@

#import "MGLOfflineStorage_Private.h"
#import "MGLGeometry_Private.h"
#import "NSBundle+MGLAdditions.h"
#import "MGLStyle.h"
#import "MGLStyle_Private.h"
#import "MGLAttributionInfo_Private.h"
#import "MGLLoggingConfiguration_Private.h"
#import "MGLRendererConfiguration.h"
Expand All @@ -30,13 +29,50 @@
#import <QuartzCore/QuartzCore.h>
#endif

#import "NSBundle+MGLAdditions.h"

const CGPoint MGLLogoImagePosition = CGPointMake(8, 8);
const CGFloat MGLSnapshotterMinimumPixelSize = 64;

MGLImage *MGLAttributedSnapshot(mbgl::MapSnapshotter::Attributions attributions, MGLImage *mglImage, mbgl::MapSnapshotter::PointForFn pointForFn, mbgl::MapSnapshotter::LatLngForFn latLngForFn, MGLMapSnapshotOptions *options, MGLMapSnapshotOverlayHandler overlayHandler);
MGLMapSnapshot *MGLSnapshotWithDecoratedImage(MGLImage *mglImage, MGLMapSnapshotOptions *options, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn, mbgl::MapSnapshotter::LatLngForFn latLngForFn, MGLMapSnapshotOverlayHandler overlayHandler, NSError * _Nullable *outError);
NSArray<MGLAttributionInfo *> *MGLAttributionInfosFromAttributions(mbgl::MapSnapshotter::Attributions attributions);

class MGLMapSnapshotterDelegateHost: public mbgl::MapSnapshotterObserver {
public:
MGLMapSnapshotterDelegateHost(MGLMapSnapshotter *snapshotter_) : snapshotter(snapshotter_) {}

void onDidFailLoadingStyle(const std::string& errorMessage) {
MGLMapSnapshotter *strongSnapshotter = snapshotter;
if ([strongSnapshotter.delegate respondsToSelector:@selector(mapSnapshotterDidFail:withError:)]) {
NSString *description = @(errorMessage.c_str());
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: NSLocalizedStringWithDefaultValue(@"SNAPSHOT_LOAD_STYLE_FAILED_DESC", nil, nil, @"The snapshot failed because the style can’t be loaded.", @"User-friendly error description"),
NSLocalizedFailureReasonErrorKey: description,
};
NSError *error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeLoadStyleFailed userInfo:userInfo];
[strongSnapshotter.delegate mapSnapshotterDidFail:snapshotter withError:error];
}
}

void onDidFinishLoadingStyle() {
MGLMapSnapshotter *strongSnapshotter = snapshotter;
if ([strongSnapshotter.delegate respondsToSelector:@selector(mapSnapshotter:didFinishLoadingStyle:)]) {
[strongSnapshotter.delegate mapSnapshotter:snapshotter didFinishLoadingStyle:snapshotter.style];
}
}

void onStyleImageMissing(const std::string& imageName) {
MGLMapSnapshotter *strongSnapshotter = snapshotter;
if ([strongSnapshotter.delegate respondsToSelector:@selector(mapSnapshotter:didFailLoadingImageNamed:)]) {
[strongSnapshotter.delegate mapSnapshotter:snapshotter didFailLoadingImageNamed:@(imageName.c_str())];
}
}

private:
__weak MGLMapSnapshotter *snapshotter;
};

@interface MGLMapSnapshotOverlay() <MGLMapSnapshotProtocol>
@property (nonatomic, assign) CGFloat scale;
- (instancetype)initWithContext:(CGContextRef)context scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn;
Expand Down Expand Up @@ -208,6 +244,7 @@ @interface MGLMapSnapshotter()

@implementation MGLMapSnapshotter {
std::unique_ptr<mbgl::MapSnapshotter> _mbglMapSnapshotter;
std::unique_ptr<MGLMapSnapshotterDelegateHost> _delegateHost;
}

- (void)dealloc {
Expand Down Expand Up @@ -447,6 +484,8 @@ - (void)startWithQueue:(dispatch_queue_t)queue overlayHandler:(MGLMapSnapshotOve

[sourceImageRep drawInRect: targetFrame];

overlayHandler();

NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
if (!currentContext) {
// If the current context has been corrupted by the user,
Expand Down Expand Up @@ -658,6 +697,7 @@ - (void)cancel
_mbglMapSnapshotter->cancel();
}
_mbglMapSnapshotter.reset();
_delegateHost.reset();
}

- (void)configureWithOptions:(MGLMapSnapshotOptions *)options {
Expand All @@ -680,8 +720,9 @@ - (void)configureWithOptions:(MGLMapSnapshotOptions *)options {
.withAssetPath(NSBundle.mainBundle.resourceURL.path.UTF8String);

// Create the snapshotter
_delegateHost = std::make_unique<MGLMapSnapshotterDelegateHost>(self);
_mbglMapSnapshotter = std::make_unique<mbgl::MapSnapshotter>(
size, pixelRatio, resourceOptions, mbgl::MapSnapshotterObserver::nullObserver(), config.localFontFamilyName);
size, pixelRatio, resourceOptions, *_delegateHost, config.localFontFamilyName);

_mbglMapSnapshotter->setStyleURL(std::string(options.styleURL.absoluteString.UTF8String));

Expand All @@ -701,4 +742,11 @@ - (void)configureWithOptions:(MGLMapSnapshotOptions *)options {
}
}

- (MGLStyle *)style {
if (!_mbglMapSnapshotter) {
return nil;
}
return [[MGLStyle alloc] initWithRawStyle:&_mbglMapSnapshotter->getStyle() stylable:self];
}

@end
107 changes: 62 additions & 45 deletions platform/darwin/src/MGLOpenGLStyleLayer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,7 @@
#include <mbgl/gl/custom_layer.hpp>
#include <mbgl/math/wrap.hpp>

class MGLOpenGLLayerHost : public mbgl::style::CustomLayerHost {
public:
MGLOpenGLLayerHost(MGLOpenGLStyleLayer *styleLayer) {
layerRef = styleLayer;
layer = nil;
}

void initialize() {
if (layerRef == nil) return;
else if (layer == nil) layer = layerRef;

[layer didMoveToMapView:layer.style.mapView];
}

void render(const mbgl::style::CustomLayerRenderParameters &params) {
if(!layer) return;

MGLStyleLayerDrawingContext drawingContext = {
.size = CGSizeMake(params.width, params.height),
.centerCoordinate = CLLocationCoordinate2DMake(params.latitude, params.longitude),
.zoomLevel = params.zoom,
.direction = mbgl::util::wrap(params.bearing, 0., 360.),
.pitch = static_cast<CGFloat>(params.pitch),
.fieldOfView = static_cast<CGFloat>(params.fieldOfView),
.projectionMatrix = MGLMatrix4Make(params.projectionMatrix)
};
[layer drawInMapView:layer.style.mapView withContext:drawingContext];
}

void contextLost() {}

void deinitialize() {
if (layer == nil) return;

[layer willMoveFromMapView:layer.style.mapView];
layerRef = layer;
layer = nil;
}
private:
__weak MGLOpenGLStyleLayer * layerRef;
MGLOpenGLStyleLayer * layer = nil;
};
class MGLOpenGLLayerHost;

/**
An `MGLOpenGLStyleLayer` is a style layer that is rendered by OpenGL code that
Expand All @@ -74,6 +33,8 @@ @interface MGLOpenGLStyleLayer ()

@property (nonatomic, readonly) mbgl::style::CustomLayer *rawLayer;

@property (nonatomic, readonly, nullable) MGLMapView *mapView;

/**
The style currently containing the layer.

Expand Down Expand Up @@ -107,13 +68,20 @@ - (instancetype)initWithIdentifier:(NSString *)identifier {
return (mbgl::style::CustomLayer *)super.rawLayer;
}

- (MGLMapView *)mapView {
if ([self.style.stylable isKindOfClass:[MGLMapView class]]) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As implemented in this PR, MGLOpenGLStyleLayer doesn’t support snapshotting, but it wouldn’t be too difficult to make it work if necessary.

return (MGLMapView *)self.style.stylable;
}
return nil;
}

#if TARGET_OS_IPHONE
- (EAGLContext *)context {
return self.style.mapView.context;
return self.mapView.context;
}
#else
- (CGLContextObj)context {
return self.style.mapView.context;
return self.mapView.context;
}
#endif

Expand Down Expand Up @@ -191,11 +159,60 @@ - (void)drawInMapView:(MGLMapView *)mapView withContext:(MGLStyleLayerDrawingCon
causing the `-drawInMapView:withContext:` method to be called.
*/
- (void)setNeedsDisplay {
[self.style.mapView setNeedsRerender];
[self.mapView setNeedsRerender];
}

@end

class MGLOpenGLLayerHost : public mbgl::style::CustomLayerHost {
public:
MGLOpenGLLayerHost(MGLOpenGLStyleLayer *styleLayer) {
layerRef = styleLayer;
layer = nil;
}

void initialize() {
if (layerRef == nil) return;
else if (layer == nil) layer = layerRef;

if (layer.mapView) {
[layer didMoveToMapView:layer.mapView];
}
}

void render(const mbgl::style::CustomLayerRenderParameters &params) {
if(!layer) return;

MGLStyleLayerDrawingContext drawingContext = {
.size = CGSizeMake(params.width, params.height),
.centerCoordinate = CLLocationCoordinate2DMake(params.latitude, params.longitude),
.zoomLevel = params.zoom,
.direction = mbgl::util::wrap(params.bearing, 0., 360.),
.pitch = static_cast<CGFloat>(params.pitch),
.fieldOfView = static_cast<CGFloat>(params.fieldOfView),
.projectionMatrix = MGLMatrix4Make(params.projectionMatrix)
};
if (layer.mapView) {
[layer drawInMapView:layer.mapView withContext:drawingContext];
}
}

void contextLost() {}

void deinitialize() {
if (layer == nil) return;

if (layer.mapView) {
[layer willMoveFromMapView:layer.mapView];
}
layerRef = layer;
layer = nil;
}
private:
__weak MGLOpenGLStyleLayer * layerRef;
MGLOpenGLStyleLayer * layer = nil;
};

namespace mbgl {

MGLStyleLayer* OpenGLStyleLayerPeerFactory::createPeer(style::Layer* rawLayer) {
Expand Down
Loading