summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoman Blum <rmnblm@gmail.com>2016-10-12 21:30:20 +0200
committerMinh Nguyễn <mxn@1ec5.org>2016-10-12 12:30:20 -0700
commita24e559ac83a47d19e28290c7ded427adeb303a3 (patch)
tree930c91599bea3b6a524559ba900fb42b8b034f98
parent7bbf4a286e36ac26e7fa7317bd7942b5163c1188 (diff)
downloadqtlocation-mapboxgl-a24e559ac83a47d19e28290c7ded427adeb303a3.tar.gz
[ios, macos] possibility to set custom images to style (#6637)
* [ios, macos] possibility to set custom images to style * [ios, macos] setImage:forName now supports MGLImage (UIImage/NSImage) * [ios, macos] update documentation for setImage:forName: and removeImageForName: * [ios, macos] rename method and fix prefix * [ios, macos] change header visibility to project * [ios, macos] update header imports * [ios, macos] delete unnecessary whitespaces * [ios, macos] remove unnecessary nil checks * [ios, macos] delete unnecessary whitespaces * [ios, macos] update documentation * [ios, macos] make mgl_spriteImage a parameter-less instance method * [ios, macos] add asserts
-rw-r--r--platform/darwin/src/MGLStyle.h88
-rw-r--r--platform/darwin/src/MGLStyle.mm48
-rw-r--r--platform/darwin/src/MGLTypes.h8
-rw-r--r--platform/ios/ios.xcodeproj/project.pbxproj12
-rw-r--r--platform/ios/src/MGLMapView.mm387
-rw-r--r--platform/ios/src/UIImage+MGLAdditions.h9
-rw-r--r--platform/ios/src/UIImage+MGLAdditions.mm27
-rw-r--r--platform/macos/macos.xcodeproj/project.pbxproj8
-rw-r--r--platform/macos/src/MGLMapView.mm306
-rw-r--r--platform/macos/src/NSImage+MGLAdditions.h9
-rw-r--r--platform/macos/src/NSImage+MGLAdditions.mm23
11 files changed, 516 insertions, 409 deletions
diff --git a/platform/darwin/src/MGLStyle.h b/platform/darwin/src/MGLStyle.h
index d4868681e1..7f01c230c5 100644
--- a/platform/darwin/src/MGLStyle.h
+++ b/platform/darwin/src/MGLStyle.h
@@ -12,14 +12,14 @@ NS_ASSUME_NONNULL_BEGIN
A version number identifying the default version of the suite of default styles
provided by Mapbox. This version number may be passed into one of the
“StyleURLWithVersion” class methods of MGLStyle.
-
+
The value of this constant generally corresponds to the latest released version
as of the date on which this SDK was published. You can use this constant to
ascertain the style used by `MGLMapView` and `MGLTilePyramidOfflineRegion` when
no style URL is specified. Consult the
<a href="https://www.mapbox.com/api-documentation/#styles">Mapbox Styles API documentation</a>
for the most up-to-date style versioning information.
-
+
@warning The value of this constant may change in a future release of the SDK.
If you use any feature that depends on a specific aspect of a default style
– for instance, the minimum zoom level that includes roads – you may use the
@@ -30,8 +30,8 @@ NS_ASSUME_NONNULL_BEGIN
static const NSInteger MGLStyleDefaultVersion = 9;
/**
- The proxy object for the current map style for customization purposes and a
- set of convenience methods for creating style URLs of default styles provided
+ The proxy object for the current map style for customization purposes and a
+ set of convenience methods for creating style URLs of default styles provided
by Mapbox.
<a href="https://www.mapbox.com/maps/">Learn more about Mapbox default styles</a>.
*/
@@ -42,9 +42,9 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Returns the URL to version 8 of the
<a href="https://www.mapbox.com/maps/streets/">Mapbox Streets</a> style.
-
+
Streets is a general-purpose style with detailed road and transit networks.
-
+
`MGLMapView` and `MGLTilePyramidOfflineRegion` use Mapbox Streets when no style
is specified explicitly.
*/
@@ -53,12 +53,12 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Returns the URL to the given version of the
<a href="https://www.mapbox.com/maps/streets/">Mapbox Streets</a> style.
-
+
Streets is a general-purpose style with detailed road and transit networks.
-
+
`MGLMapView` and `MGLTilePyramidOfflineRegion` use Mapbox Streets when no style
is specified explicitly.
-
+
@param version The style’s latest released version. As of publication, the
current version is `9`.
*/
@@ -67,7 +67,7 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Returns the URL to version 8 of the
<a href="https://www.mapbox.com/blog/emerald-gl/">Mapbox Emerald</a> style.
-
+
Emerald is a tactile style with subtle textures and dramatic hillshading.
*/
+ (NSURL *)emeraldStyleURL __attribute__((deprecated("Create an NSURL object with the string “mapbox://styles/mapbox/emerald-v8”.")));
@@ -75,9 +75,9 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Returns the URL to the given version of the
<a href="https://www.mapbox.com/maps/outdoors/">Mapbox Outdoors</a> style.
-
+
Outdoors is a general-purpose style tailored to outdoor activities.
-
+
@param version The style’s latest released version. As of publication, the
current version is `9`.
*/
@@ -86,7 +86,7 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Returns the URL to version 8 of the
<a href="https://www.mapbox.com/maps/light-dark/">Mapbox Light</a> style.
-
+
Light is a subtle, light-colored backdrop for data visualizations.
*/
+ (NSURL *)lightStyleURL __attribute__((deprecated("Use -lightStyleURLWithVersion:.")));
@@ -94,9 +94,9 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Returns the URL to the given version of the
<a href="https://www.mapbox.com/maps/light-dark/">Mapbox Light</a> style.
-
+
Light is a subtle, light-colored backdrop for data visualizations.
-
+
@param version The style’s latest released version. As of publication, the
current version is `9`.
*/
@@ -105,7 +105,7 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Returns the URL to version 8 of the
<a href="https://www.mapbox.com/maps/light-dark/">Mapbox Dark</a> style.
-
+
Dark is a subtle, dark-colored backdrop for data visualizations.
*/
+ (NSURL *)darkStyleURL __attribute__((deprecated("Use -darkStyleURLWithVersion:.")));
@@ -113,9 +113,9 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Returns the URL to the given version of the
<a href="https://www.mapbox.com/maps/light-dark/">Mapbox Dark</a> style.
-
+
Dark is a subtle, dark-colored backdrop for data visualizations.
-
+
@param version The style’s latest released version. As of publication, the
current version is `9`.
*/
@@ -124,7 +124,7 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Returns the URL to version 8 of the
<a href="https://www.mapbox.com/maps/satellite/">Mapbox Satellite</a> style.
-
+
Satellite is high-resolution satellite and aerial imagery.
*/
+ (NSURL *)satelliteStyleURL __attribute__((deprecated("Use -satelliteStyleURLWithVersion:.")));
@@ -132,9 +132,9 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Returns the URL to the given version of the
<a href="https://www.mapbox.com/maps/satellite/">Mapbox Satellite</a> style.
-
+
Satellite is high-resolution satellite and aerial imagery.
-
+
@param version The style’s latest released version. As of publication, the
current version is `9`.
*/
@@ -144,7 +144,7 @@ static const NSInteger MGLStyleDefaultVersion = 9;
Returns the URL to version 8 of the
<a href="https://www.mapbox.com/maps/satellite/">Mapbox Satellite Streets</a>
style.
-
+
Satellite Streets combines the high-resolution satellite and aerial imagery of
Mapbox Satellite with unobtrusive labels and translucent roads from Mapbox
Streets.
@@ -155,11 +155,11 @@ static const NSInteger MGLStyleDefaultVersion = 9;
Returns the URL to the given version of the
<a href="https://www.mapbox.com/maps/satellite/">Mapbox Satellite Streets</a>
style.
-
+
Satellite Streets combines the high-resolution satellite and aerial imagery of
Mapbox Satellite with unobtrusive labels and translucent roads from Mapbox
Streets.
-
+
@param version The style’s latest released version. As of publication, the
current version is `9`.
*/
@@ -167,7 +167,7 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
The name of the style.
-
+
You can customize the style’s name in Mapbox Studio.
*/
@property (readonly, copy, nullable) NSString *name;
@@ -177,7 +177,7 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Returns a layer that conforms to `MGLStyleLayer` if any layer with the given
identifier was found.
-
+
@return An instance of a concrete subclass of `MGLStyleLayer` associated with
the given identifier.
*/
@@ -186,7 +186,7 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Returns a source if any source with the given identifier was found.
-
+
@return An instance of a concrete subclass of `MGLSource` associated with the
given identifier.
*/
@@ -194,7 +194,7 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Adds a new layer on top of existing layers.
-
+
@param layer The layer object to add to the map view. This object must be an
instance of a concrete subclass of `MGLStyleLayer`.
*/
@@ -202,7 +202,7 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Inserts a new layer below another layer.
-
+
@param layer Layer to be inserted.
@param belowLayer A layer that's already on the map view.
*/
@@ -210,7 +210,7 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Removes a layer from the map view.
-
+
@param layer The layer object to remove from the map view. This object
must conform to the `MGLStyleLayer` protocol.
*/
@@ -218,14 +218,14 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Adds a new source to the map view.
-
+
@param source The source to add to the map view.
*/
- (void)addSource:(MGLSource *)source;
/**
Removes a source from the map view.
-
+
@param source The source to remove.
*/
- (void)removeSource:(MGLSource *)source;
@@ -238,7 +238,7 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Returns a Boolean value indicating whether the style class with the given
identifier is currently active.
-
+
@param styleClass The style class to query for.
@return Whether the style class is currently active.
*/
@@ -246,18 +246,36 @@ static const NSInteger MGLStyleDefaultVersion = 9;
/**
Activates the style class with the given identifier.
-
+
@param styleClass The style class to activate.
*/
- (void)addStyleClass:(NSString *)styleClass;
/**
Deactivates the style class with the given identifier.
-
+
@param styleClass The style class to deactivate.
*/
- (void)removeStyleClass:(NSString *)styleClass;
+/**
+Adds or overrides an image used by the style’s layers.
+
+To use an image in a style layer, give it a unique name using this method,
+then set the `iconImage` property of an `MGLSymbolStyleLayer` object to that name.
+
+ @param image The image for the name.
+ @param name The name of the image to set to the style.
+ */
+- (void)setImage:(MGLImage *)image forName:(NSString *)name;
+
+/**
+ Removes a name and its associated image from the style.
+
+ @param name The name of the image to remove.
+ */
+- (void)removeImageForName:(NSString *)name;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm
index f1c3e68e3a..f9f2e03d40 100644
--- a/platform/darwin/src/MGLStyle.mm
+++ b/platform/darwin/src/MGLStyle.mm
@@ -21,6 +21,7 @@
#import "MGLGeoJSONSource.h"
#include <mbgl/util/default_styles.hpp>
+#include <mbgl/sprite/sprite_image.hpp>
#include <mbgl/style/layers/fill_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/symbol_layer.hpp>
@@ -32,6 +33,12 @@
#include <mbgl/style/sources/raster_source.hpp>
#include <mbgl/mbgl.hpp>
+#if TARGET_OS_IPHONE
+ #import "UIImage+MGLAdditions.h"
+#else
+ #import "NSImage+MGLAdditions.h"
+#endif
+
@interface MGLStyle()
@property (nonatomic, weak) MGLMapView *mapView;
@property (readonly, copy, nullable) NSURL *URL;
@@ -103,7 +110,7 @@ static NSURL *MGLStyleURL_emerald;
if (!mbglLayer) {
return nil;
}
-
+
MGLStyleLayer *styleLayer;
if (auto fillLayer = mbglLayer->as<mbgl::style::FillLayer>()) {
MGLSource *source = [self sourceWithIdentifier:@(fillLayer->getSourceID().c_str())];
@@ -126,9 +133,9 @@ static NSURL *MGLStyleURL_emerald;
NSAssert(NO, @"Unrecognized layer type");
return nil;
}
-
+
styleLayer.layer = mbglLayer;
-
+
return styleLayer;
}
@@ -138,7 +145,7 @@ static NSURL *MGLStyleURL_emerald;
if (!mbglSource) {
return nil;
}
-
+
// TODO: Fill in options specific to the respective source classes
// https://github.com/mapbox/mapbox-gl-native/issues/6584
MGLSource *source;
@@ -152,9 +159,9 @@ static NSURL *MGLStyleURL_emerald;
NSAssert(NO, @"Unrecognized source type");
return nil;
}
-
+
source.source = mbglSource;
-
+
return source;
}
@@ -171,7 +178,7 @@ static NSURL *MGLStyleURL_emerald;
@"Make sure the style layer was created as a member of a concrete subclass of MGLStyleLayer.",
NSStringFromClass(self)];
}
-
+
self.mapView.mbglMap->addLayer(std::unique_ptr<mbgl::style::Layer>(layer.layer));
}
@@ -191,7 +198,7 @@ static NSURL *MGLStyleURL_emerald;
@"Make sure the style layer was created as a member of a concrete subclass of MGLStyleLayer.",
NSStringFromClass(otherLayer)];
}
-
+
const mbgl::optional<std::string> belowLayerId{otherLayer.identifier.UTF8String};
self.mapView.mbglMap->addLayer(std::unique_ptr<mbgl::style::Layer>(layer.layer), belowLayerId);
}
@@ -209,13 +216,13 @@ static NSURL *MGLStyleURL_emerald;
- (NS_ARRAY_OF(NSString *) *)styleClasses
{
const std::vector<std::string> &appliedClasses = self.mapView.mbglMap->getClasses();
-
+
NSMutableArray *returnArray = [NSMutableArray arrayWithCapacity:appliedClasses.size()];
-
+
for (auto appliedClass : appliedClasses) {
[returnArray addObject:@(appliedClass.c_str())];
}
-
+
return returnArray;
}
@@ -227,12 +234,12 @@ static NSURL *MGLStyleURL_emerald;
- (void)setStyleClasses:(NS_ARRAY_OF(NSString *) *)appliedClasses transitionDuration:(NSTimeInterval)transitionDuration
{
std::vector<std::string> newAppliedClasses;
-
+
for (NSString *appliedClass in appliedClasses)
{
newAppliedClasses.push_back([appliedClass UTF8String]);
}
-
+
mbgl::style::TransitionOptions transition { { MGLDurationInSeconds(transitionDuration) } };
self.mapView.mbglMap->setTransitionOptions(transition);
self.mapView.mbglMap->setClasses(newAppliedClasses);
@@ -259,6 +266,21 @@ static NSURL *MGLStyleURL_emerald;
}
}
+- (void)setImage:(MGLImage *)image forName:(NSString *)name
+{
+ NSAssert(image, @"image is null");
+ NSAssert(name, @"name is null");
+
+ self.mapView.mbglMap->addImage([name UTF8String], image.mgl_spriteImage);
+}
+
+- (void)removeImageForName:(NSString *)name
+{
+ NSAssert(name, @"name is null");
+
+ self.mapView.mbglMap->removeImage([name UTF8String]);
+}
+
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; name = %@, URL = %@>",
diff --git a/platform/darwin/src/MGLTypes.h b/platform/darwin/src/MGLTypes.h
index 6a323153fb..a3ea587add 100644
--- a/platform/darwin/src/MGLTypes.h
+++ b/platform/darwin/src/MGLTypes.h
@@ -3,6 +3,14 @@
#pragma once
#if TARGET_OS_IPHONE
+@class UIImage;
+#define MGLImage UIImage
+#else
+@class NSImage;
+#define MGLImage NSImage
+#endif
+
+#if TARGET_OS_IPHONE
@class UIColor;
#define MGLColor UIColor
#else
diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj
index 5c4523fa4f..ee772a9cd2 100644
--- a/platform/ios/ios.xcodeproj/project.pbxproj
+++ b/platform/ios/ios.xcodeproj/project.pbxproj
@@ -7,6 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
+ 30E578171DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */; };
+ 30E578181DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */; };
+ 30E578191DAA855E0050F07E /* UIImage+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 30E578121DAA7D690050F07E /* UIImage+MGLAdditions.mm */; };
+ 30E5781A1DAA855E0050F07E /* UIImage+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 30E578121DAA7D690050F07E /* UIImage+MGLAdditions.mm */; };
350098AF1D47E6F4004B2AF0 /* UIColor+MGLStyleAttributeAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 350098AD1D47E6F4004B2AF0 /* UIColor+MGLStyleAttributeAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
350098B01D47E6F4004B2AF0 /* UIColor+MGLStyleAttributeAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 350098AD1D47E6F4004B2AF0 /* UIColor+MGLStyleAttributeAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
350098B11D47E6F4004B2AF0 /* UIColor+MGLStyleAttributeAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 350098AE1D47E6F4004B2AF0 /* UIColor+MGLStyleAttributeAdditions.mm */; };
@@ -494,6 +498,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+MGLAdditions.h"; path = "src/UIImage+MGLAdditions.h"; sourceTree = SOURCE_ROOT; };
+ 30E578121DAA7D690050F07E /* UIImage+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "UIImage+MGLAdditions.mm"; path = "src/UIImage+MGLAdditions.mm"; sourceTree = SOURCE_ROOT; };
350098AD1D47E6F4004B2AF0 /* UIColor+MGLStyleAttributeAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIColor+MGLStyleAttributeAdditions.h"; path = "src/UIColor+MGLStyleAttributeAdditions.h"; sourceTree = SOURCE_ROOT; };
350098AE1D47E6F4004B2AF0 /* UIColor+MGLStyleAttributeAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "UIColor+MGLStyleAttributeAdditions.mm"; path = "src/UIColor+MGLStyleAttributeAdditions.mm"; sourceTree = SOURCE_ROOT; };
350098B91D480108004B2AF0 /* MGLVectorSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLVectorSource.h; sourceTree = "<group>"; };
@@ -939,6 +945,8 @@
children = (
35CE61801D4165D9004F2359 /* UIColor+MGLAdditions.h */,
35CE61811D4165D9004F2359 /* UIColor+MGLAdditions.mm */,
+ 30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */,
+ 30E578121DAA7D690050F07E /* UIImage+MGLAdditions.mm */,
);
name = Categories;
sourceTree = "<group>";
@@ -1473,6 +1481,7 @@
DA8847F81CBAFA5100AB86E3 /* MGLPointAnnotation.h in Headers */,
353933F21D3FB753003F57D7 /* MGLCircleStyleLayer.h in Headers */,
DA8847F31CBAFA5100AB86E3 /* MGLMultiPoint.h in Headers */,
+ 30E578171DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */,
DAD1656C1CF41981001FF4B9 /* MGLFeature.h in Headers */,
40EDA1C01CFE0E0200D9EA68 /* MGLAnnotationContainerView.h in Headers */,
DA88484F1CBAFB9800AB86E3 /* MGLAnnotationImage_Private.h in Headers */,
@@ -1564,6 +1573,7 @@
353933F31D3FB753003F57D7 /* MGLCircleStyleLayer.h in Headers */,
3593E5271D529EDC006D9365 /* UIColor+MGLStyleAttributeAdditions_Private.h in Headers */,
3538AA1E1D542239008EC33D /* MGLForegroundStyleLayer.h in Headers */,
+ 30E578181DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */,
40F887711D7A1E59008ECB67 /* MGLGeoJSONSource_Private.h in Headers */,
DABFB8631CBE99E500D62B32 /* MGLOfflineRegion.h in Headers */,
DA35A2B21CCA141D00E826B2 /* MGLCompassDirectionFormatter.h in Headers */,
@@ -1930,6 +1940,7 @@
DA88485D1CBAFB9800AB86E3 /* MGLFaux3DUserLocationAnnotationView.m in Sources */,
DAD165701CF41981001FF4B9 /* MGLFeature.mm in Sources */,
350098C31D48149E004B2AF0 /* NSNumber+MGLStyleAttributeAdditions.mm in Sources */,
+ 30E578191DAA855E0050F07E /* UIImage+MGLAdditions.mm in Sources */,
40EDA1C11CFE0E0500D9EA68 /* MGLAnnotationContainerView.m in Sources */,
DA8848541CBAFB9800AB86E3 /* MGLCompactCalloutView.m in Sources */,
DA8848251CBAFA6200AB86E3 /* MGLPointAnnotation.m in Sources */,
@@ -2000,6 +2011,7 @@
DAED38661D62D0FC00D7640F /* NSURL+MGLAdditions.m in Sources */,
DAD165711CF41981001FF4B9 /* MGLFeature.mm in Sources */,
350098C41D48149E004B2AF0 /* NSNumber+MGLStyleAttributeAdditions.mm in Sources */,
+ 30E5781A1DAA855E0050F07E /* UIImage+MGLAdditions.mm in Sources */,
40EDA1C21CFE0E0500D9EA68 /* MGLAnnotationContainerView.m in Sources */,
DAA4E4291CBB730400178DFB /* NSBundle+MGLAdditions.m in Sources */,
DAA4E42E1CBB730400178DFB /* MGLAPIClient.m in Sources */,
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index 8b58fbaf65..d0c95d9c0c 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -38,6 +38,7 @@
#import "NSProcessInfo+MGLAdditions.h"
#import "NSException+MGLAdditions.h"
#import "NSURL+MGLAdditions.h"
+#import "UIImage+MGLAdditions.h"
#import "MGLFaux3DUserLocationAnnotationView.h"
#import "MGLUserLocationAnnotationView.h"
@@ -257,16 +258,16 @@ public:
{
mbgl::Map *_mbglMap;
MBGLView *_mbglView;
-
+
BOOL _opaque;
NS_MUTABLE_ARRAY_OF(NSURL *) *_bundledStyleURLs;
-
+
MGLAnnotationContextMap _annotationContextsByAnnotationTag;
/// Tag of the selected annotation. If the user location annotation is selected, this ivar is set to `MGLAnnotationTagNotFound`.
MGLAnnotationTag _selectedAnnotationTag;
NS_MUTABLE_DICTIONARY_OF(NSString *, NS_MUTABLE_ARRAY_OF(MGLAnnotationView *) *) *_annotationViewReuseQueueByIdentifier;
-
+
BOOL _userLocationAnnotationIsSelected;
/// Size of the rectangle formed by unioning the maximum slop area around every annotation image and annotation image view.
CGSize _unionedAnnotationRepresentationSize;
@@ -283,18 +284,18 @@ public:
CADisplayLink *_displayLink;
BOOL _needsDisplayRefresh;
-
+
NSUInteger _changeDelimiterSuppressionDepth;
-
+
/// Center coordinate of the pinch gesture on the previous iteration of the gesture.
CLLocationCoordinate2D _previousPinchCenterCoordinate;
NSUInteger _previousPinchNumberOfTouches;
-
+
BOOL _delegateHasAlphasForShapeAnnotations;
BOOL _delegateHasStrokeColorsForShapeAnnotations;
BOOL _delegateHasFillColorsForShapeAnnotations;
BOOL _delegateHasLineWidthsForShapeAnnotations;
-
+
MGLCompassDirectionFormatter *_accessibilityCompassFormatter;
}
@@ -381,14 +382,14 @@ public:
self.accessibilityTraits = UIAccessibilityTraitAllowsDirectInteraction | UIAccessibilityTraitAdjustable;
_accessibilityCompassFormatter = [[MGLCompassDirectionFormatter alloc] init];
_accessibilityCompassFormatter.unitStyle = NSFormattingUnitStyleLong;
-
+
self.backgroundColor = [UIColor clearColor];
self.clipsToBounds = YES;
// setup mbgl view
const float scaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)] ? [[UIScreen mainScreen] nativeScale] : [[UIScreen mainScreen] scale];
_mbglView = new MBGLView(self, scaleFactor);
-
+
// Delete the pre-offline ambient cache at ~/Library/Caches/cache.db.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *fileCachePath = [paths.firstObject stringByAppendingPathComponent:@"cache.db"];
@@ -416,7 +417,7 @@ public:
_isWaitingForRedundantReachableNotification = YES;
}
[reachability startNotifier];
-
+
// Set up annotation management and selection state.
_annotationImagesByIdentifier = [NSMutableDictionary dictionary];
_annotationContextsByAnnotationTag = {};
@@ -581,7 +582,7 @@ public:
UIImage *scaleImage = [MGLMapView resourceImageNamed:@"Compass.png"];
UIGraphicsBeginImageContextWithOptions(scaleImage.size, NO, [UIScreen mainScreen].scale);
[scaleImage drawInRect:{ CGPointZero, scaleImage.size }];
-
+
CGFloat northSize = 9;
UIFont *northFont;
if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)])
@@ -600,7 +601,7 @@ public:
scaleImage.size.height * 0.45,
north.size.width, north.size.height);
[north drawInRect:stringRect];
-
+
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
@@ -627,14 +628,14 @@ public:
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_attributionButton removeObserver:self forKeyPath:@"hidden"];
-
+
// Removing the annotations unregisters any outstanding KVO observers.
NSArray *annotations = self.annotations;
if (annotations)
{
[self removeAnnotations:annotations];
}
-
+
[self validateDisplayLink];
if (_mbglMap)
@@ -665,7 +666,7 @@ public:
if (_delegate == delegate) return;
_delegate = delegate;
-
+
_delegateHasAlphasForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)];
_delegateHasStrokeColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)];
_delegateHasFillColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)];
@@ -703,7 +704,7 @@ public:
{
return;
}
-
+
CGFloat zoomFactor = self.maximumZoomLevel - self.minimumZoomLevel + 1;
CGFloat cpuFactor = [NSProcessInfo processInfo].processorCount;
CGFloat memoryFactor = (CGFloat)[NSProcessInfo processInfo].physicalMemory / 1000 / 1000 / 1000;
@@ -795,7 +796,7 @@ public:
//
[constraintParentView removeConstraints:self.logoViewConstraints];
[self.logoViewConstraints removeAllObjects];
-
+
[self.logoViewConstraints addObject:
[NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeBottom
@@ -819,7 +820,7 @@ public:
//
[constraintParentView removeConstraints:self.attributionButtonConstraints];
[self.attributionButtonConstraints removeAllObjects];
-
+
[self.attributionButtonConstraints addObject:
[NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeBottom
@@ -867,7 +868,7 @@ public:
- (void)layoutSubviews
{
[super layoutSubviews];
-
+
[self adjustContentInset];
if ( ! _isTargetingInterfaceBuilder)
@@ -885,7 +886,7 @@ public:
[self updateHeadingForDeviceOrientation];
[self updateCompass];
}
-
+
[self updateUserLocationAnnotationView];
}
@@ -907,12 +908,12 @@ public:
// This map view is an immediate child of a view controller’s content view.
viewController = (UIViewController *)self.superview.nextResponder;
}
-
+
if ( ! viewController.automaticallyAdjustsScrollViewInsets)
{
return;
}
-
+
UIEdgeInsets contentInset = UIEdgeInsetsZero;
CGPoint topPoint = CGPointMake(0, viewController.topLayoutGuide.length);
contentInset.top = [self convertPoint:topPoint fromView:viewController.view].y;
@@ -939,14 +940,14 @@ public:
{
return;
}
-
+
// After adjusting the content inset, move the center coordinate from the
// old frame of reference to the new one represented by the newly set
// content inset.
CLLocationCoordinate2D oldCenter = self.centerCoordinate;
-
+
_contentInset = contentInset;
-
+
if (self.userTrackingMode == MGLUserTrackingModeNone)
{
// Don’t call -setCenterCoordinate:, which resets the user tracking mode.
@@ -956,7 +957,7 @@ public:
{
[self didUpdateLocationWithUserTrackingAnimated:animated];
}
-
+
// Compass, logo and attribution button constraints needs to be updated.
[self setNeedsUpdateConstraints];
}
@@ -1016,7 +1017,7 @@ public:
{
_mbglMap->setConstrainMode(mbgl::ConstrainMode::HeightOnly);
}
-
+
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateFromDisplayLink)];
_displayLink.frameInterval = MGLTargetFrameInterval;
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
@@ -1053,7 +1054,7 @@ public:
[self validateLocationServices];
[MGLMapboxEvents flush];
-
+
_displayLink.paused = YES;
if ( ! self.glSnapshotView)
@@ -1094,11 +1095,11 @@ public:
[self.glSnapshotView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
[self.glView bindDrawable];
-
+
_displayLink.paused = NO;
[self validateLocationServices];
-
+
[MGLMapboxEvents pushEvent:MGLEventTypeMapLoad withAttributes:@{}];
}
}
@@ -1179,7 +1180,7 @@ public:
[self trackGestureEvent:MGLEventGesturePanStart forRecognizer:pan];
self.userTrackingMode = MGLUserTrackingModeNone;
-
+
[self notifyGestureDidBegin];
}
else if (pan.state == UIGestureRecognizerStateChanged)
@@ -1228,7 +1229,7 @@ public:
if (_mbglMap->getZoom() <= _mbglMap->getMinZoom() && pinch.scale < 1) return;
_mbglMap->cancelTransitions();
-
+
CGPoint centerPoint = [self anchorPointForGesture:pinch];
if (pinch.state == UIGestureRecognizerStateBegan)
@@ -1236,7 +1237,7 @@ public:
[self trackGestureEvent:MGLEventGesturePinchStart forRecognizer:pinch];
self.scale = _mbglMap->getScale();
-
+
[self notifyGestureDidBegin];
}
else if (pinch.state == UIGestureRecognizerStateChanged)
@@ -1244,9 +1245,9 @@ public:
CGFloat newScale = self.scale * pinch.scale;
if (log2(newScale) < _mbglMap->getMinZoom()) return;
-
+
_mbglMap->setScale(newScale, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
-
+
// The gesture recognizer only reports the gesture’s current center
// point, so use the previous center point to anchor the transition.
// If the number of touches has changed, the remembered center point is
@@ -1300,7 +1301,7 @@ public:
[self unrotateIfNeededForGesture];
}
-
+
_previousPinchCenterCoordinate = [self convertPoint:centerPoint toCoordinateFromView:self];
_previousPinchNumberOfTouches = pinch.numberOfTouches;
}
@@ -1310,7 +1311,7 @@ public:
if ( ! self.isRotateEnabled) return;
_mbglMap->cancelTransitions();
-
+
CGPoint centerPoint = [self anchorPointForGesture:rotate];
if (rotate.state == UIGestureRecognizerStateBegan)
@@ -1323,7 +1324,7 @@ public:
{
self.userTrackingMode = MGLUserTrackingModeFollow;
}
-
+
[self notifyGestureDidBegin];
}
else if (rotate.state == UIGestureRecognizerStateChanged)
@@ -1337,7 +1338,7 @@ public:
newDegrees = fminf(newDegrees, 30);
newDegrees = fmaxf(newDegrees, -30);
}
-
+
_mbglMap->setBearing(newDegrees, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
[self notifyMapChange:mbgl::MapChangeRegionIsChanging];
@@ -1378,7 +1379,7 @@ public:
return;
}
[self trackGestureEvent:MGLEventGestureSingleTap forRecognizer:singleTap];
-
+
if (self.mapViewProxyAccessibilityElement.accessibilityElementIsFocused)
{
id nextElement;
@@ -1433,7 +1434,7 @@ public:
}
}
}
-
+
MGLAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:tapPoint persistingResults:YES];
if (hitAnnotationTag != MGLAnnotationTagNotFound)
{
@@ -1514,7 +1515,7 @@ public:
self.scale = _mbglMap->getScale();
self.quickZoomStart = [quickZoom locationInView:quickZoom.view].y;
-
+
[self notifyGestureDidBegin];
}
else if (quickZoom.state == UIGestureRecognizerStateChanged)
@@ -1524,7 +1525,7 @@ public:
CGFloat newZoom = log2f(self.scale) + (distance / 75);
if (newZoom < _mbglMap->getMinZoom()) return;
-
+
CGPoint centerPoint = [self anchorPointForGesture:quickZoom];
_mbglMap->scaleBy(powf(2, newZoom) / _mbglMap->getScale(),
@@ -1557,7 +1558,7 @@ public:
CGFloat slowdown = 20.0;
CGFloat pitchNew = currentPitch - (gestureDistance / slowdown);
-
+
CGPoint centerPoint = [self anchorPointForGesture:twoFingerDrag];
_mbglMap->setPitch(pitchNew, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
@@ -1582,7 +1583,7 @@ public:
{
return self.contentCenter;
}
-
+
return [gesture locationInView:gesture.view];
}
@@ -1690,7 +1691,7 @@ public:
nil];
}
-
+
[self.attributionSheet showFromRect:self.attributionButton.frame inView:self animated:YES];
}
@@ -1791,7 +1792,7 @@ public:
// Redundantly move the associated annotation view outside the scope of the animation-less transaction block in -updateAnnotationViews.
annotationContext.annotationView.center = [self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self];
}
-
+
MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
NSString *symbolName = annotationImage.styleIconIdentifier;
@@ -1968,14 +1969,14 @@ public:
- (UIBezierPath *)accessibilityPath
{
UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.accessibilityFrame];
-
+
// Exclude any visible annotation callout view.
if (self.calloutViewForSelectedAnnotation)
{
UIBezierPath *calloutViewPath = [UIBezierPath bezierPathWithRect:self.calloutViewForSelectedAnnotation.frame];
[path appendPath:calloutViewPath];
}
-
+
return path;
}
@@ -2016,7 +2017,7 @@ public:
return nil;
}
std::vector<MGLAnnotationTag> visibleAnnotations = [self annotationTagsInRect:self.bounds];
-
+
// Ornaments
if (index == 0)
{
@@ -2034,7 +2035,7 @@ public:
{
return self.attributionButton;
}
-
+
std::sort(visibleAnnotations.begin(), visibleAnnotations.end());
CGPoint centerPoint = self.contentCenter;
if (self.userTrackingMode != MGLUserTrackingModeNone)
@@ -2051,7 +2052,7 @@ public:
coordinateB.longitude - currentCoordinate.longitude);
return deltaA < deltaB;
});
-
+
NSUInteger annotationIndex = MGLAnnotationTagNotFound;
if (index >= 0 && (NSUInteger)(index - 2) < visibleAnnotations.size())
{
@@ -2062,20 +2063,20 @@ public:
NSAssert(_annotationContextsByAnnotationTag.count(annotationTag), @"Missing annotation for tag %u.", annotationTag);
MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
id <MGLAnnotation> annotation = annotationContext.annotation;
-
+
// Let the annotation view serve as its own accessibility element.
MGLAnnotationView *annotationView = annotationContext.annotationView;
if (annotationView && annotationView.superview)
{
return annotationView;
}
-
+
// Lazily create an accessibility element for the found annotation.
if ( ! annotationContext.accessibilityElement)
{
annotationContext.accessibilityElement = [[MGLAnnotationAccessibilityElement alloc] initWithAccessibilityContainer:self tag:annotationTag];
}
-
+
// Update the accessibility element.
MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
CGRect annotationFrame = [self frameOfImage:annotationImage.image centeredAtCoordinate:annotation.coordinate];
@@ -2087,7 +2088,7 @@ public:
CGRect screenRect = UIAccessibilityConvertFrameToScreenCoordinates(annotationFrame, self);
annotationContext.accessibilityElement.accessibilityFrame = screenRect;
annotationContext.accessibilityElement.accessibilityHint = NSLocalizedStringWithDefaultValue(@"ANNOTATION_A11Y_HINT", nil, nil, @"Shows more info", @"Accessibility hint");
-
+
if ([annotation respondsToSelector:@selector(title)])
{
annotationContext.accessibilityElement.accessibilityLabel = annotation.title;
@@ -2096,7 +2097,7 @@ public:
{
annotationContext.accessibilityElement.accessibilityValue = annotation.subtitle;
}
-
+
return annotationContext.accessibilityElement;
}
@@ -2115,9 +2116,9 @@ public:
{
return 1;
}
-
+
std::vector<MGLAnnotationTag> visibleAnnotations = [self annotationTagsInRect:self.bounds];
-
+
MGLAnnotationTag tag = MGLAnnotationTagNotFound;
if ([element isKindOfClass:[MGLAnnotationView class]])
{
@@ -2136,7 +2137,7 @@ public:
{
return NSNotFound;
}
-
+
std::sort(visibleAnnotations.begin(), visibleAnnotations.end());
auto foundElement = std::find(visibleAnnotations.begin(), visibleAnnotations.end(), tag);
if (foundElement == visibleAnnotations.end())
@@ -2176,7 +2177,7 @@ public:
}
_mbglMap->scaleBy(scaleFactor, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
[self unrotateIfNeededForGesture];
-
+
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.accessibilityValue);
}
@@ -2226,7 +2227,7 @@ public:
- (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate edgePadding:(UIEdgeInsets)insets zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion
{
_mbglMap->cancelTransitions();
-
+
mbgl::CameraOptions cameraOptions;
cameraOptions.center = MGLLatLngFromLocationCoordinate2D(centerCoordinate);
cameraOptions.padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
@@ -2235,7 +2236,7 @@ public:
{
cameraOptions.angle = MGLRadiansFromDegrees(-direction);
}
-
+
mbgl::AnimationOptions animationOptions;
if (duration)
{
@@ -2275,9 +2276,9 @@ public:
{
if (zoomLevel == self.zoomLevel) return;
_mbglMap->cancelTransitions();
-
+
CGFloat duration = animated ? MGLAnimationDuration : 0;
-
+
_mbglMap->setZoom(zoomLevel,
MGLEdgeInsetsFromNSEdgeInsets(self.contentInset),
MGLDurationInSeconds(duration));
@@ -2372,7 +2373,7 @@ public:
- (void)_setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion
{
_mbglMap->cancelTransitions();
-
+
[self willChangeValueForKey:@"visibleCoordinateBounds"];
mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
@@ -2382,13 +2383,13 @@ public:
{
latLngs.push_back({coordinates[i].latitude, coordinates[i].longitude});
}
-
+
mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngs(latLngs, padding);
if (direction >= 0)
{
cameraOptions.angle = MGLRadiansFromDegrees(-direction);
}
-
+
mbgl::AnimationOptions animationOptions;
if (duration > 0)
{
@@ -2425,7 +2426,7 @@ public:
{
self.userTrackingMode = MGLUserTrackingModeFollow;
}
-
+
[self _setDirection:direction animated:animated];
}
@@ -2435,7 +2436,7 @@ public:
_mbglMap->cancelTransitions();
CGFloat duration = animated ? MGLAnimationDuration : 0;
-
+
if (self.userTrackingMode == MGLUserTrackingModeNone)
{
_mbglMap->setBearing(direction,
@@ -2510,7 +2511,7 @@ public:
});
};
}
-
+
[self willChangeValueForKey:@"camera"];
_mbglMap->easeTo(cameraOptions, animationOptions);
[self didChangeValueForKey:@"camera"];
@@ -2529,7 +2530,7 @@ public:
- (void)flyToCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration peakAltitude:(CLLocationDistance)peakAltitude completionHandler:(nullable void (^)(void))completion
{
self.userTrackingMode = MGLUserTrackingModeNone;
-
+
[self _flyToCamera:camera edgePadding:self.contentInset withDuration:duration peakAltitude:peakAltitude completionHandler:completion];
}
@@ -2562,7 +2563,7 @@ public:
});
};
}
-
+
[self willChangeValueForKey:@"camera"];
_mbglMap->flyTo(cameraOptions, animationOptions);
[self didChangeValueForKey:@"camera"];
@@ -2666,7 +2667,7 @@ public:
bounds.extend([self convertPoint:{ CGRectGetMaxX(rect), CGRectGetMinY(rect) } toLatLngFromView:view]);
bounds.extend([self convertPoint:{ CGRectGetMaxX(rect), CGRectGetMaxY(rect) } toLatLngFromView:view]);
bounds.extend([self convertPoint:{ CGRectGetMinX(rect), CGRectGetMaxY(rect) } toLatLngFromView:view]);
-
+
// The world is wrapping if a point just outside the bounds is also within
// the rect.
mbgl::LatLng outsideLatLng;
@@ -2684,14 +2685,14 @@ public:
bounds.east() + 1,
};
}
-
+
// If the world is wrapping, extend the bounds to cover all longitudes.
if (CGRectContainsPoint(rect, [self convertLatLng:outsideLatLng toPointToView:view]))
{
bounds.extend(mbgl::LatLng(bounds.south(), -180));
bounds.extend(mbgl::LatLng(bounds.south(), 180));
}
-
+
return bounds;
}
@@ -2777,7 +2778,7 @@ public:
{
return nil;
}
-
+
// Map all the annotation tags to the annotations themselves.
std::vector<id <MGLAnnotation>> annotations;
std::transform(_annotationContextsByAnnotationTag.begin(),
@@ -2787,11 +2788,11 @@ public:
{
return pair.second.annotation;
});
-
+
annotations.erase(std::remove_if(annotations.begin(), annotations.end(),
[](const id <MGLAnnotation> annotation) { return annotation == nullptr; }),
annotations.end());
-
+
return [NSArray arrayWithObjects:&annotations[0] count:annotations.size()];
}
@@ -2802,7 +2803,7 @@ public:
{
return nil;
}
-
+
MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag[tag];
return annotationContext.annotation;
}
@@ -2814,7 +2815,7 @@ public:
{
return MGLAnnotationTagNotFound;
}
-
+
for (auto &pair : _annotationContextsByAnnotationTag)
{
if (pair.second.annotation == annotation)
@@ -2845,7 +2846,7 @@ public:
BOOL delegateImplementsViewForAnnotation = [self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)];
BOOL delegateImplementsImageForPoint = [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)];
-
+
NSMutableArray *newAnnotationViews = [[NSMutableArray alloc] initWithCapacity:annotations.count];
for (id <MGLAnnotation> annotation in annotations)
@@ -2860,7 +2861,7 @@ public:
{
continue;
}
-
+
// The polyline or polygon knows how to style itself (with the map view’s help).
MGLMultiPoint *multiPoint = (MGLMultiPoint *)annotation;
if (!multiPoint.pointCount) {
@@ -2881,7 +2882,7 @@ public:
MGLAnnotationView *annotationView;
NSString *symbolName;
NSValue *annotationValue = [NSValue valueWithNonretainedObject:annotation];
-
+
if (delegateImplementsViewForAnnotation)
{
annotationView = [self annotationViewForAnnotation:annotation];
@@ -2891,7 +2892,7 @@ public:
annotationView.annotation = annotation;
annotationView.center = [self convertCoordinate:annotation.coordinate toPointToView:self];
[newAnnotationViews addObject:annotationView];
-
+
MGLAnnotationImage *annotationImage = self.invisibleAnnotationImage;
symbolName = annotationImage.styleIconIdentifier;
annotationImagesForAnnotation[annotationValue] = annotationImage;
@@ -2901,10 +2902,10 @@ public:
}
}
}
-
+
if ( ! annotationView) {
MGLAnnotationImage *annotationImage;
-
+
if (delegateImplementsImageForPoint)
{
annotationImage = [self.delegate mapView:self imageForAnnotation:annotation];
@@ -2917,9 +2918,9 @@ public:
{
annotationImage = self.defaultAnnotationImage;
}
-
+
symbolName = annotationImage.styleIconIdentifier;
-
+
if ( ! symbolName)
{
symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
@@ -2929,7 +2930,7 @@ public:
{
[self installAnnotationImage:annotationImage];
}
-
+
annotationImagesForAnnotation[annotationValue] = annotationImage;
}
@@ -2947,7 +2948,7 @@ public:
context.annotationView = annotationView;
context.viewReuseIdentifier = annotationView.reuseIdentifier;
}
-
+
_annotationContextsByAnnotationTag[annotationTag] = context;
if ([annotation isKindOfClass:[NSObject class]]) {
@@ -2958,21 +2959,21 @@ public:
}
[self updateAnnotationContainerViewWithAnnotationViews:newAnnotationViews];
-
+
[self didChangeValueForKey:@"annotations"];
-
+
if ([self.delegate respondsToSelector:@selector(mapView:didAddAnnotationViews:)])
{
[self.delegate mapView:self didAddAnnotationViews:newAnnotationViews];
}
-
+
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
}
- (void)updateAnnotationContainerViewWithAnnotationViews:(NS_ARRAY_OF(MGLAnnotationView *) *)annotationViews
{
if (annotationViews.count == 0) return;
-
+
MGLAnnotationContainerView *newAnnotationContainerView;
if (self.annotationContainerView)
{
@@ -3008,7 +3009,7 @@ public:
- (MGLAnnotationImage *)invisibleAnnotationImage
{
MGLAnnotationImage *annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLInvisibleStyleMarkerSymbolName];
-
+
if (!annotationImage)
{
UIGraphicsBeginImageContext(CGSizeMake(1, 1));
@@ -3018,27 +3019,27 @@ public:
reuseIdentifier:MGLInvisibleStyleMarkerSymbolName];
annotationImage.styleIconIdentifier = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
}
-
+
return annotationImage;
}
- (MGLAnnotationView *)annotationViewForAnnotation:(id<MGLAnnotation>)annotation
{
MGLAnnotationView *annotationView = [self.delegate mapView:self viewForAnnotation:annotation];
-
+
if (annotationView)
{
annotationView.annotation = annotation;
annotationView.mapView = self;
CGRect bounds = UIEdgeInsetsInsetRect({ CGPointZero, annotationView.frame.size }, annotationView.alignmentRectInsets);
-
+
_largestAnnotationViewSize = CGSizeMake(MAX(_largestAnnotationViewSize.width, CGRectGetWidth(bounds)),
MAX(_largestAnnotationViewSize.height, CGRectGetHeight(bounds)));
-
+
_unionedAnnotationRepresentationSize = CGSizeMake(MAX(_unionedAnnotationRepresentationSize.width, _largestAnnotationViewSize.width),
MAX(_unionedAnnotationRepresentationSize.height, _largestAnnotationViewSize.height));
}
-
+
return annotationView;
}
@@ -3046,7 +3047,7 @@ public:
{
MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation];
if (annotationTag == MGLAnnotationTagNotFound) return nil;
-
+
MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
return annotationContext.annotationView;
}
@@ -3090,29 +3091,11 @@ public:
NSString *iconIdentifier = annotationImage.styleIconIdentifier;
self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage;
annotationImage.delegate = self;
-
- // retrieve pixels
- CGImageRef image = annotationImage.image.CGImage;
- size_t width = CGImageGetWidth(image);
- size_t height = CGImageGetHeight(image);
- CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
- mbgl::PremultipliedImage cPremultipliedImage(width, height);
- size_t bytesPerPixel = 4;
- size_t bytesPerRow = bytesPerPixel * width;
- size_t bitsPerComponent = 8;
- CGContextRef context = CGBitmapContextCreate(cPremultipliedImage.data.get(), width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast);
- CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);
- CGContextRelease(context);
- CGColorSpaceRelease(colorSpace);
// add sprite
- auto cSpriteImage = std::make_shared<mbgl::SpriteImage>(
- std::move(cPremultipliedImage),
- float(annotationImage.image.scale));
+ std::shared_ptr<mbgl::SpriteImage> sprite(annotationImage.image.mgl_spriteImage);
+ _mbglMap->addAnnotationIcon(iconIdentifier.UTF8String, sprite);
- // sprite upload
- _mbglMap->addAnnotationIcon(iconIdentifier.UTF8String, cSpriteImage);
-
// Create a slop area with a “radius” equal in size to the annotation
// image’s alignment rect, allowing the eventual tap to be on any point
// within this image. Union this slop area with any existing slop areas.
@@ -3158,9 +3141,9 @@ public:
{
[self deselectAnnotation:annotation animated:NO];
}
-
+
_annotationContextsByAnnotationTag.erase(annotationTag);
-
+
if ([annotation isKindOfClass:[NSObject class]] && ![annotation isKindOfClass:[MGLMultiPoint class]])
{
[(NSObject *)annotation removeObserver:self forKeyPath:@"coordinate" context:(void *)(NSUInteger)annotationTag];
@@ -3222,7 +3205,7 @@ public:
MGLAnnotationView *reusableView = annotationViewReuseQueue.firstObject;
[reusableView prepareForReuse];
[annotationViewReuseQueue removeObject:reusableView];
-
+
return reusableView;
}
@@ -3249,14 +3232,14 @@ public:
queryRect = CGRectInset(queryRect, -MGLAnnotationImagePaddingForHitTest,
-MGLAnnotationImagePaddingForHitTest);
std::vector<MGLAnnotationTag> nearbyAnnotations = [self annotationTagsInRect:queryRect];
-
+
if (nearbyAnnotations.size())
{
// Assume that the user is fat-fingering an annotation.
CGRect hitRect = CGRectInset({ point, CGSizeZero },
-MGLAnnotationImagePaddingForHitTest,
-MGLAnnotationImagePaddingForHitTest);
-
+
// Filter out any annotation whose image or view is unselectable or for which
// hit testing fails.
auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(),
@@ -3264,10 +3247,10 @@ public:
{
id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
NSAssert(annotation, @"Unknown annotation found nearby tap");
-
+
MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag[annotationTag];
CGRect annotationRect;
-
+
MGLAnnotationView *annotationView = annotationContext.annotationView;
if (annotationView)
{
@@ -3275,7 +3258,7 @@ public:
{
return true;
}
-
+
CGPoint calloutAnchorPoint = [self convertCoordinate:annotation.coordinate toPointToView:self];
CGRect frame = CGRectInset({ calloutAnchorPoint, CGSizeZero }, -CGRectGetWidth(annotationView.frame) / 2, -CGRectGetHeight(annotationView.frame) / 2);
annotationRect = UIEdgeInsetsInsetRect(frame, annotationView.alignmentRectInsets);
@@ -3287,28 +3270,28 @@ public:
{
return true;
}
-
+
MGLAnnotationImage *fallbackAnnotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MGLDefaultStyleMarkerSymbolName];
UIImage *fallbackImage = fallbackAnnotationImage.image;
-
+
annotationRect = [self frameOfImage:annotationImage.image ?: fallbackImage centeredAtCoordinate:annotation.coordinate];
}
-
+
// Filter out the annotation if the fattened finger didn’t land
// within the image’s alignment rect.
return !!!CGRectIntersectsRect(annotationRect, hitRect);
});
-
+
nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end));
}
-
+
MGLAnnotationTag hitAnnotationTag = MGLAnnotationTagNotFound;
if (nearbyAnnotations.size())
{
// The annotation tags need to be stable in order to compare them with
// the remembered tags.
std::sort(nearbyAnnotations.begin(), nearbyAnnotations.end());
-
+
if (nearbyAnnotations == _annotationsNearbyLastTap)
{
// The first selection in the cycle should be the one nearest to the
@@ -3323,7 +3306,7 @@ public:
coordinateB.longitude - currentCoordinate.longitude);
return deltaA < deltaB;
});
-
+
// The last time we persisted a set of annotations, we had the same
// set of annotations as we do now. Cycle through them.
if (_selectedAnnotationTag == MGLAnnotationTagNotFound
@@ -3361,7 +3344,7 @@ public:
{
_annotationsNearbyLastTap = nearbyAnnotations;
}
-
+
// Choose the first nearby annotation.
if (nearbyAnnotations.size())
{
@@ -3369,7 +3352,7 @@ public:
}
}
}
-
+
return hitAnnotationTag;
}
@@ -3441,7 +3424,7 @@ public:
}
[self deselectAnnotation:self.selectedAnnotation animated:NO];
-
+
// Add the annotation to the map if it hasn’t been added yet.
MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation];
if (annotationTag == MGLAnnotationTagNotFound && annotation != self.userLocation)
@@ -3450,29 +3433,29 @@ public:
annotationTag = [self annotationTagForAnnotation:annotation];
if (annotationTag == MGLAnnotationTagNotFound) return;
}
-
+
// By default attempt to use the GL annotation image frame as the positioning rect.
CGRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag];
-
+
MGLAnnotationView *annotationView = nil;
-
+
if (annotation != self.userLocation)
{
MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
-
+
annotationView = annotationContext.annotationView;
-
+
if (annotationView && annotationView.enabled)
{
// Annotations represented by views use the view frame as the positioning rect.
positioningRect = annotationView.frame;
-
+
[annotationView.superview bringSubviewToFront:annotationView];
[annotationView setSelected:YES animated:animated];
}
}
-
+
// The client can request that any annotation be selected (even ones that are offscreen).
// The annotation can’t be selected if no part of it is hittable.
if ( ! CGRectIntersectsRect(positioningRect, self.bounds) && annotation != self.userLocation)
@@ -3502,7 +3485,7 @@ public:
if (_userLocationAnnotationIsSelected)
{
positioningRect = [self.userLocationAnnotationView.layer.presentationLayer frame];
-
+
CGRect implicitAnnotationFrame = [self.userLocationAnnotationView.layer.presentationLayer frame];
CGRect explicitAnnotationFrame = self.userLocationAnnotationView.frame;
_initialImplicitCalloutViewOffset = CGPointMake(CGRectGetMinX(explicitAnnotationFrame) - CGRectGetMinX(implicitAnnotationFrame),
@@ -3551,7 +3534,7 @@ public:
{
[self.delegate mapView:self didSelectAnnotation:annotation];
}
-
+
if (annotationView && [self.delegate respondsToSelector:@selector(mapView:didSelectAnnotationView:)])
{
[self.delegate mapView:self didSelectAnnotationView:annotationView];
@@ -3573,7 +3556,7 @@ public:
- (CGRect)positioningRectForCalloutForAnnotationWithTag:(MGLAnnotationTag)annotationTag
{
MGLAnnotationContext annotationContext = _annotationContextsByAnnotationTag[annotationTag];
-
+
id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
if ( ! annotation)
{
@@ -3588,10 +3571,10 @@ public:
{
return CGRectZero;
}
-
+
CGRect positioningRect = [self frameOfImage:image centeredAtCoordinate:annotation.coordinate];
positioningRect.origin.x -= 0.5;
-
+
return CGRectInset(positioningRect, -MGLAnnotationImagePaddingForCallout,
-MGLAnnotationImagePaddingForCallout);
}
@@ -3613,10 +3596,10 @@ public:
{
return nil;
}
-
+
NSString *customSymbol = _annotationContextsByAnnotationTag.at(annotationTag).imageReuseIdentifier;
NSString *symbolName = customSymbol.length ? customSymbol : MGLDefaultStyleMarkerSymbolName;
-
+
return [self dequeueReusableAnnotationImageWithIdentifier:symbolName];
}
@@ -3628,11 +3611,11 @@ public:
{
// dismiss popup
[self.calloutViewForSelectedAnnotation dismissCalloutAnimated:animated];
-
+
// deselect annotation view
MGLAnnotationView *annotationView = nil;
MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation];
-
+
if (annotationTag != MGLAnnotationTagNotFound)
{
MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag);
@@ -3649,7 +3632,7 @@ public:
{
[self.delegate mapView:self didDeselectAnnotation:annotation];
}
-
+
if (annotationView && [self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotationView:)])
{
[self.delegate mapView:self didDeselectAnnotationView:annotationView];
@@ -3664,7 +3647,7 @@ public:
{
return;
}
-
+
// The user location callout view initially points to the user location
// annotation’s implicit (visual) frame, which is offset from the
// annotation’s explicit frame. Now the callout view needs to rendezvous
@@ -3728,19 +3711,19 @@ public:
NSString *iconIdentifier = annotationImage.styleIconIdentifier;
NSString *fallbackReuseIdentifier = MGLDefaultStyleMarkerSymbolName;
NSString *fallbackIconIdentifier = [MGLAnnotationSpritePrefix stringByAppendingString:fallbackReuseIdentifier];
-
+
// Remove the old icon from the style.
if ( ! [iconIdentifier isEqualToString:fallbackIconIdentifier]) {
_mbglMap->removeAnnotationIcon(iconIdentifier.UTF8String);
}
-
+
if (annotationImage.image)
{
// Add the new icon to the style.
NSString *updatedIconIdentifier = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
annotationImage.styleIconIdentifier = updatedIconIdentifier;
[self installAnnotationImage:annotationImage];
-
+
if ([iconIdentifier isEqualToString:fallbackIconIdentifier])
{
// Update any annotations associated with the annotation image.
@@ -3755,7 +3738,7 @@ public:
{
[self installAnnotationImage:self.defaultAnnotationImage];
}
-
+
// Update any annotations associated with the annotation image.
[self applyIconIdentifier:fallbackIconIdentifier toAnnotationsWithImageReuseIdentifier:reuseIdentifier];
}
@@ -3834,11 +3817,11 @@ public:
{
[self.delegate mapViewWillStartLocatingUser:self];
}
-
+
self.userLocation = [[MGLUserLocation alloc] initWithMapView:self];
-
+
MGLUserLocationAnnotationView *userLocationAnnotationView;
-
+
if ([self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)])
{
userLocationAnnotationView = (MGLUserLocationAnnotationView *)[self.delegate mapView:self viewForAnnotation:self.userLocation];
@@ -3852,11 +3835,11 @@ public:
userLocationAnnotationView = nil;
}
}
-
+
self.userLocationAnnotationView = userLocationAnnotationView ?: [[MGLFaux3DUserLocationAnnotationView alloc] init];
self.userLocationAnnotationView.mapView = self;
self.userLocationAnnotationView.userLocation = self.userLocation;
-
+
self.userLocationAnnotationView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin |
UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin);
@@ -3934,7 +3917,7 @@ public:
case MGLUserTrackingModeNone:
{
self.userTrackingState = MGLUserTrackingStatePossible;
-
+
[self.locationManager stopUpdatingHeading];
// Immediately update the annotation view; other cases update inside
@@ -3965,7 +3948,7 @@ public:
{
self.userTrackingState = animated ? MGLUserTrackingStatePossible : MGLUserTrackingStateChanged;
}
-
+
self.showsUserLocation = YES;
if (self.zoomLevel < self.currentMinimumZoom)
@@ -4066,7 +4049,7 @@ public:
duration = MIN([newLocation.timestamp timeIntervalSinceDate:oldLocation.timestamp], MGLUserLocationAnimationDuration);
}
[self updateUserLocationAnnotationViewAnimatedWithDuration:duration];
-
+
if (self.userTrackingMode == MGLUserTrackingModeNone &&
self.userLocationAnnotationView.accessibilityElementIsFocused &&
[UIApplication sharedApplication].applicationState == UIApplicationStateActive)
@@ -4084,7 +4067,7 @@ public:
{
return;
}
-
+
// If the user location annotation is already where it’s supposed to be,
// don’t change the viewport.
CGPoint correctPoint = self.userLocationAnnotationViewCenter;
@@ -4094,7 +4077,7 @@ public:
{
return;
}
-
+
if (self.userTrackingMode == MGLUserTrackingModeFollowWithCourse
&& CLLocationCoordinate2DIsValid(self.targetCoordinate))
{
@@ -4135,7 +4118,7 @@ public:
- (void)didUpdateLocationSignificantlyAnimated:(BOOL)animated
{
self.userTrackingState = MGLUserTrackingStateBegan;
-
+
MGLMapCamera *camera = self.camera;
camera.centerCoordinate = self.userLocation.location.coordinate;
camera.heading = self.directionByFollowingWithCourse;
@@ -4146,7 +4129,7 @@ public:
camera.centerCoordinate.latitude,
self.frame.size);
}
-
+
__weak MGLMapView *weakSelf = self;
[self _flyToCamera:camera
edgePadding:self.edgePaddingForFollowing
@@ -4179,7 +4162,7 @@ public:
}
};
}
-
+
CLLocationCoordinate2D foci[] = {
self.userLocation.location.coordinate,
self.targetCoordinate,
@@ -4203,7 +4186,7 @@ public:
{
// Center on user location unless we're already centered there (or very close).
CGPoint correctPoint = self.userLocationAnnotationViewCenter;
-
+
// Shift the entire frame upward or downward to accommodate a shifted user
// location annotation view.
CGRect bounds = self.bounds;
@@ -4245,7 +4228,7 @@ public:
{
direction = self.userLocation.location.course;
}
-
+
if (direction >= 0)
{
if (self.userLocationVerticalAlignment == MGLAnnotationVerticalAlignmentTop)
@@ -4345,7 +4328,7 @@ public:
- (NS_ARRAY_OF(id <MGLFeature>) *)visibleFeaturesAtPoint:(CGPoint)point inStyleLayersWithIdentifiers:(NS_SET_OF(NSString *) *)styleLayerIdentifiers
{
mbgl::ScreenCoordinate screenCoordinate = { point.x, point.y };
-
+
mbgl::optional<std::vector<std::string>> optionalLayerIDs;
if (styleLayerIdentifiers)
{
@@ -4357,7 +4340,7 @@ public:
}];
optionalLayerIDs = layerIDs;
}
-
+
std::vector<mbgl::Feature> features = _mbglMap->queryRenderedFeatures(screenCoordinate, optionalLayerIDs);
return MGLFeaturesFromMBGLFeatures(features);
}
@@ -4371,7 +4354,7 @@ public:
{ CGRectGetMinX(rect), CGRectGetMinY(rect) },
{ CGRectGetMaxX(rect), CGRectGetMaxY(rect) },
};
-
+
mbgl::optional<std::vector<std::string>> optionalLayerIDs;
if (styleLayerIdentifiers) {
__block std::vector<std::string> layerIDs;
@@ -4381,7 +4364,7 @@ public:
}];
optionalLayerIDs = layerIDs;
}
-
+
std::vector<mbgl::Feature> features = _mbglMap->queryRenderedFeatures(screenBox, optionalLayerIDs);
return MGLFeaturesFromMBGLFeatures(features);
}
@@ -4412,7 +4395,7 @@ public:
&& state != UIGestureRecognizerStateChanged)
{
[self unrotateIfNeededAnimated:YES];
-
+
// Snap to north.
if ((self.direction < MGLToleranceForSnappingToNorth
|| self.direction > 360 - MGLToleranceForSnappingToNorth)
@@ -4585,21 +4568,21 @@ public:
- (void)updateAnnotationViews
{
BOOL delegateImplementsViewForAnnotation = [self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)];
-
+
if (!delegateImplementsViewForAnnotation)
{
return;
}
-
+
[CATransaction begin];
[CATransaction setDisableActions:YES];
-
+
for (auto &pair : _annotationContextsByAnnotationTag)
{
CGRect viewPort = CGRectInset(self.bounds,
-_largestAnnotationViewSize.width / 2.0 - MGLAnnotationUpdateViewportOutset.width / 2.0,
-_largestAnnotationViewSize.height / 2.0 - MGLAnnotationUpdateViewportOutset.width);
-
+
MGLAnnotationContext &annotationContext = pair.second;
MGLAnnotationView *annotationView = annotationContext.annotationView;
@@ -4617,7 +4600,7 @@ public:
annotationView.mapView = self;
annotationView.center = [self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self];
annotationContext.annotationView = annotationView;
-
+
if (!annotationView.superview) {
[self.annotationContainerView insertSubview:annotationView atIndex:0];
}
@@ -4628,7 +4611,7 @@ public:
continue;
}
}
-
+
bool annotationViewIsVisible = CGRectContainsRect(viewPort, annotationView.frame);
if (!annotationViewIsVisible && annotationContext.viewReuseIdentifier)
{
@@ -4639,18 +4622,18 @@ public:
annotationView.center = [self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self];
}
}
-
+
[CATransaction commit];
}
- (void)enqueueAnnotationViewForAnnotationContext:(MGLAnnotationContext &)annotationContext
{
MGLAnnotationView *annotationView = annotationContext.annotationView;
-
+
if (!annotationView) return;
-
+
annotationView.annotation = nil;
-
+
if (annotationContext.viewReuseIdentifier)
{
NSMutableArray *annotationViewReuseQueue = [self annotationViewReuseQueueForIdentifier:annotationContext.viewReuseIdentifier];
@@ -4680,7 +4663,7 @@ public:
{
userPoint = [self convertCoordinate:self.userLocation.coordinate toPointToView:self];
}
-
+
if ( ! annotationView.superview)
{
[self.glView addSubview:annotationView];
@@ -4709,10 +4692,10 @@ public:
annotationView.center = userPoint;
} completion:NULL];
_userLocationAnimationCompletionDate = [NSDate dateWithTimeIntervalSinceNow:duration];
-
+
annotationView.hidden = NO;
[annotationView update];
-
+
if (_userLocationAnnotationIsSelected)
{
// Ensure the callout view still points to its annotation.
@@ -4731,7 +4714,7 @@ public:
// User has moved far enough outside of the viewport that showing it or
// its callout would be useless.
annotationView.hidden = YES;
-
+
if (_userLocationAnnotationIsSelected)
{
[self deselectAnnotation:self.selectedAnnotation animated:YES];
@@ -4749,7 +4732,7 @@ public:
contentFrame = self.contentFrame;
}
CGPoint center = CGPointMake(CGRectGetMidX(contentFrame), CGRectGetMidY(contentFrame));
-
+
// When tracking course, it’s more important to see the road ahead, so
// weight the user dot down towards the bottom.
switch (self.userLocationVerticalAlignment) {
@@ -4762,7 +4745,7 @@ public:
center.y = CGRectGetMaxY(contentFrame);
break;
}
-
+
return center;
}
@@ -4771,7 +4754,7 @@ public:
CLLocationDirection direction = self.direction;
CLLocationDirection plateDirection = mbgl::util::wrap(-direction, 0., 360.);
self.compassView.transform = CGAffineTransformMakeRotation(MGLRadiansFromDegrees(plateDirection));
-
+
self.compassView.isAccessibilityElement = direction > 0;
self.compassView.accessibilityValue = [_accessibilityCompassFormatter stringFromDirection:direction];
@@ -4811,7 +4794,7 @@ public:
[NSException raise:@"Resource not found" format:
@"The resource named “%@” could not be found in the Mapbox framework bundle.", imageName];
}
-
+
return [UIImage imageWithContentsOfFile:path];
}
@@ -4928,7 +4911,7 @@ public:
{
_annotationViewReuseQueueByIdentifier[identifier] = [NSMutableArray array];
}
-
+
return _annotationViewReuseQueueByIdentifier[identifier];
}
@@ -5130,7 +5113,7 @@ public:
MGLCustomStyleLayerDrawingHandler d,
MGLCustomStyleLayerCompletionHandler f)
: prepare(p), draw(d), finish(f) {}
-
+
MGLCustomStyleLayerPreparationHandler prepare;
MGLCustomStyleLayerDrawingHandler draw;
MGLCustomStyleLayerCompletionHandler finish;
diff --git a/platform/ios/src/UIImage+MGLAdditions.h b/platform/ios/src/UIImage+MGLAdditions.h
new file mode 100644
index 0000000000..411220c503
--- /dev/null
+++ b/platform/ios/src/UIImage+MGLAdditions.h
@@ -0,0 +1,9 @@
+#import <UIKit/UIKit.h>
+
+#include <mbgl/sprite/sprite_image.hpp>
+
+@interface UIImage (MGLAdditions)
+
+- (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage;
+
+@end
diff --git a/platform/ios/src/UIImage+MGLAdditions.mm b/platform/ios/src/UIImage+MGLAdditions.mm
new file mode 100644
index 0000000000..8ec8f9e15f
--- /dev/null
+++ b/platform/ios/src/UIImage+MGLAdditions.mm
@@ -0,0 +1,27 @@
+#import "UIImage+MGLAdditions.h"
+
+@implementation UIImage (MGLAdditions)
+
+- (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage
+{
+ CGImageRef cgImage = self.CGImage;
+ size_t width = CGImageGetWidth(cgImage);
+ size_t height = CGImageGetHeight(cgImage);
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
+ mbgl::PremultipliedImage cPremultipliedImage(width, height);
+ size_t bytesPerPixel = 4;
+ size_t bytesPerRow = bytesPerPixel * width;
+ size_t bitsPerComponent = 8;
+
+ CGContextRef context = CGBitmapContextCreate(cPremultipliedImage.data.get(),
+ width, height, bitsPerComponent, bytesPerRow,
+ colorSpace, kCGImageAlphaPremultipliedLast);
+
+ CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
+ CGContextRelease(context);
+ CGColorSpaceRelease(colorSpace);
+
+ return std::make_unique<mbgl::SpriteImage>(std::move(cPremultipliedImage), float(self.scale));
+}
+
+@end
diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj
index 6855b66a17..3726376fc0 100644
--- a/platform/macos/macos.xcodeproj/project.pbxproj
+++ b/platform/macos/macos.xcodeproj/project.pbxproj
@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
+ 30E5781B1DAA857E0050F07E /* NSImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578141DAA7D920050F07E /* NSImage+MGLAdditions.h */; };
+ 30E5781C1DAA85820050F07E /* NSImage+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 30E578151DAA7D920050F07E /* NSImage+MGLAdditions.mm */; };
3508EC641D749D39009B0EE4 /* NSExpression+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 3508EC621D749D39009B0EE4 /* NSExpression+MGLAdditions.h */; };
3508EC651D749D39009B0EE4 /* NSExpression+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3508EC631D749D39009B0EE4 /* NSExpression+MGLAdditions.mm */; };
352742781D4C220900A1ECE6 /* MGLStyleAttributeValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 352742771D4C220900A1ECE6 /* MGLStyleAttributeValue.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -217,6 +219,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 30E578141DAA7D920050F07E /* NSImage+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSImage+MGLAdditions.h"; path = "src/NSImage+MGLAdditions.h"; sourceTree = SOURCE_ROOT; };
+ 30E578151DAA7D920050F07E /* NSImage+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = "NSImage+MGLAdditions.mm"; path = "src/NSImage+MGLAdditions.mm"; sourceTree = SOURCE_ROOT; };
3508EC621D749D39009B0EE4 /* NSExpression+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSExpression+MGLAdditions.h"; sourceTree = "<group>"; };
3508EC631D749D39009B0EE4 /* NSExpression+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSExpression+MGLAdditions.mm"; sourceTree = "<group>"; };
352742771D4C220900A1ECE6 /* MGLStyleAttributeValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLStyleAttributeValue.h; sourceTree = "<group>"; };
@@ -713,6 +717,8 @@
DAD1657F1CF4CF50001FF4B9 /* Categories */ = {
isa = PBXGroup;
children = (
+ 30E578141DAA7D920050F07E /* NSImage+MGLAdditions.h */,
+ 30E578151DAA7D920050F07E /* NSImage+MGLAdditions.mm */,
DAE6C37D1CC31E2A00DB3429 /* NSBundle+MGLAdditions.h */,
DAE6C37E1CC31E2A00DB3429 /* NSBundle+MGLAdditions.m */,
35C5D8431D6DD66D00E95907 /* NSComparisonPredicate+MGLAdditions.h */,
@@ -887,6 +893,7 @@
DA8F25AE1D51CB270010E6B5 /* NSArray+MGLStyleAttributeAdditions_Private.h in Headers */,
DAE6C3991CC31E2A00DB3429 /* NSException+MGLAdditions.h in Headers */,
DA8F25871D51C9E10010E6B5 /* MGLBackgroundStyleLayer.h in Headers */,
+ 30E5781B1DAA857E0050F07E /* NSImage+MGLAdditions.h in Headers */,
DAE6C3661CC31E0400DB3429 /* MGLShape.h in Headers */,
352742811D4C243B00A1ECE6 /* MGLSource.h in Headers */,
DAE6C3C21CC31F4500DB3429 /* Mapbox.h in Headers */,
@@ -1141,6 +1148,7 @@
DAE6C38A1CC31E2A00DB3429 /* MGLMultiPoint.mm in Sources */,
DAE6C3961CC31E2A00DB3429 /* MGLTypes.m in Sources */,
DA35A2A61CC9EB2700E826B2 /* MGLCoordinateFormatter.m in Sources */,
+ 30E5781C1DAA85820050F07E /* NSImage+MGLAdditions.mm in Sources */,
352742821D4C243B00A1ECE6 /* MGLSource.mm in Sources */,
DAE6C3881CC31E2A00DB3429 /* MGLMapCamera.mm in Sources */,
DA6408D81DA4E5DA00908C90 /* MGLVectorStyleLayer.m in Sources */,
diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm
index 96f49b7ca8..2031173509 100644
--- a/platform/macos/src/MGLMapView.mm
+++ b/platform/macos/src/MGLMapView.mm
@@ -42,6 +42,7 @@
#import "NSString+MGLAdditions.h"
#import "NSURL+MGLAdditions.h"
#import "NSColor+MGLAdditions.h"
+#import "NSImage+MGLAdditions.h"
#import <QuartzCore/QuartzCore.h>
@@ -157,7 +158,7 @@ public:
/// Cross-platform map view controller.
mbgl::Map *_mbglMap;
MGLMapViewImpl *_mbglView;
-
+
NSPanGestureRecognizer *_panGestureRecognizer;
NSMagnificationGestureRecognizer *_magnificationGestureRecognizer;
NSRotationGestureRecognizer *_rotationGestureRecognizer;
@@ -165,7 +166,7 @@ public:
CLLocationDirection _directionAtBeginningOfGesture;
CGFloat _pitchAtBeginningOfGesture;
BOOL _didHideCursorDuringGesture;
-
+
MGLAnnotationContextMap _annotationContextsByAnnotationTag;
MGLAnnotationTag _selectedAnnotationTag;
MGLAnnotationTag _lastSelectedAnnotationTag;
@@ -176,22 +177,22 @@ public:
BOOL _wantsToolTipRects;
/// True if any annotation images that have custom cursors have been installed.
BOOL _wantsCursorRects;
-
+
// Cached checks for delegate method implementations that may be called from
// MGLMultiPointDelegate methods.
-
+
BOOL _delegateHasAlphasForShapeAnnotations;
BOOL _delegateHasStrokeColorsForShapeAnnotations;
BOOL _delegateHasFillColorsForShapeAnnotations;
BOOL _delegateHasLineWidthsForShapeAnnotations;
-
+
/// True if the current process is the Interface Builder designable
/// renderer. When drawing the designable, the map is paused, so any call to
/// it may hang the process.
BOOL _isTargetingInterfaceBuilder;
CLLocationDegrees _pendingLatitude;
CLLocationDegrees _pendingLongitude;
-
+
/// True if the view is currently printing itself.
BOOL _isPrinting;
}
@@ -231,7 +232,7 @@ public:
- (void)awakeFromNib {
[super awakeFromNib];
-
+
self.styleURL = nil;
}
@@ -243,10 +244,10 @@ public:
MGLinitializeRunLoop();
_isTargetingInterfaceBuilder = NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent;
-
+
// Set up cross-platform controllers and resources.
_mbglView = new MGLMapViewImpl(self, [NSScreen mainScreen].backingScaleFactor);
-
+
// Delete the pre-offline ambient cache at
// ~/Library/Caches/com.mapbox.sdk.ios/cache.db.
NSURL *cachesDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory
@@ -258,36 +259,36 @@ public:
[NSBundle mgl_frameworkBundle].bundleIdentifier];
NSURL *legacyCacheURL = [cachesDirectoryURL URLByAppendingPathComponent:@"cache.db"];
[[NSFileManager defaultManager] removeItemAtURL:legacyCacheURL error:NULL];
-
+
mbgl::DefaultFileSource *mbglFileSource = [MGLOfflineStorage sharedOfflineStorage].mbglFileSource;
_mbglMap = new mbgl::Map(*_mbglView, *mbglFileSource, mbgl::MapMode::Continuous, mbgl::GLContextMode::Unique, mbgl::ConstrainMode::None, mbgl::ViewportMode::Default);
[self validateTileCacheSize];
-
+
// Install the OpenGL layer. Interface Builder’s synchronous drawing means
// we can’t display a map, so don’t even bother to have a map layer.
self.layer = _isTargetingInterfaceBuilder ? [CALayer layer] : [MGLOpenGLLayer layer];
-
+
// Notify map object when network reachability status changes.
MGLReachability *reachability = [MGLReachability reachabilityForInternetConnection];
reachability.reachableBlock = ^(MGLReachability *) {
mbgl::NetworkStatus::Reachable();
};
[reachability startNotifier];
-
+
// Install ornaments and gesture recognizers.
[self installZoomControls];
[self installCompass];
[self installLogoView];
[self installAttributionView];
[self installGestureRecognizers];
-
+
// Set up annotation management and selection state.
_annotationImagesByIdentifier = [NSMutableDictionary dictionary];
_annotationContextsByAnnotationTag = {};
_selectedAnnotationTag = MGLAnnotationTagNotFound;
_lastSelectedAnnotationTag = MGLAnnotationTagNotFound;
_annotationsNearbyLastClick = {};
-
+
// Jump to Null Island initially.
self.automaticallyAdjustsContentInsets = YES;
mbgl::CameraOptions options;
@@ -353,26 +354,26 @@ public:
- (void)installAttributionView {
_attributionView = [[NSView alloc] initWithFrame:NSZeroRect];
_attributionView.wantsLayer = YES;
-
+
// Make the background and foreground translucent to be unobtrusive.
_attributionView.layer.opacity = 0.6;
-
+
// Blur the background to prevent text underneath the view from running into
// the text in the view, rendering it illegible.
CIFilter *attributionBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
[attributionBlurFilter setDefaults];
-
+
// Brighten the background. This is similar to applying a translucent white
// background on the view, but the effect is a bit more subtle and works
// well with the blur above.
CIFilter *attributionColorFilter = [CIFilter filterWithName:@"CIColorControls"];
[attributionColorFilter setDefaults];
[attributionColorFilter setValue:@(0.1) forKey:kCIInputBrightnessKey];
-
+
// Apply the background effects and a standard button corner radius.
_attributionView.backgroundFilters = @[attributionColorFilter, attributionBlurFilter];
_attributionView.layer.cornerRadius = 4;
-
+
_attributionView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_attributionView];
[self updateAttributionView];
@@ -384,27 +385,27 @@ public:
_zoomEnabled = YES;
_rotateEnabled = YES;
_pitchEnabled = YES;
-
+
_panGestureRecognizer = [[NSPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
_panGestureRecognizer.delaysKeyEvents = YES;
[self addGestureRecognizer:_panGestureRecognizer];
-
+
NSClickGestureRecognizer *clickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleClickGesture:)];
clickGestureRecognizer.delaysPrimaryMouseButtonEvents = NO;
[self addGestureRecognizer:clickGestureRecognizer];
-
+
NSClickGestureRecognizer *rightClickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleRightClickGesture:)];
rightClickGestureRecognizer.buttonMask = 0x2;
[self addGestureRecognizer:rightClickGestureRecognizer];
-
+
NSClickGestureRecognizer *doubleClickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleClickGesture:)];
doubleClickGestureRecognizer.numberOfClicksRequired = 2;
doubleClickGestureRecognizer.delaysPrimaryMouseButtonEvents = NO;
[self addGestureRecognizer:doubleClickGestureRecognizer];
-
+
_magnificationGestureRecognizer = [[NSMagnificationGestureRecognizer alloc] initWithTarget:self action:@selector(handleMagnificationGesture:)];
[self addGestureRecognizer:_magnificationGestureRecognizer];
-
+
_rotationGestureRecognizer = [[NSRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotationGesture:)];
[self addGestureRecognizer:_rotationGestureRecognizer];
}
@@ -413,7 +414,7 @@ public:
/// hard-coded to the standard Mapbox and OpenStreetMap attribution.
- (void)updateAttributionView {
self.attributionView.subviews = @[];
-
+
for (NSUInteger i = 0; i < sizeof(MGLAttributions) / sizeof(MGLAttributions[0]); i++) {
// For each attribution, add a borderless button that responds to clicks
// and feels like a hyperlink.
@@ -421,7 +422,7 @@ public:
NSButton *button = [[MGLAttributionButton alloc] initWithTitle:MGLAttributions[i].title URL:url];
button.controlSize = NSMiniControlSize;
button.translatesAutoresizingMaskIntoConstraints = NO;
-
+
// Set the new button flush with the buttom of the container and to the
// right of the previous button, with standard spacing. If there is no
// previous button, align to the container instead.
@@ -449,14 +450,14 @@ public:
- (void)dealloc {
[self.window removeObserver:self forKeyPath:@"contentLayoutRect"];
[self.window removeObserver:self forKeyPath:@"titlebarAppearsTransparent"];
-
+
// Close any annotation callout immediately.
[self.calloutForSelectedAnnotation close];
self.calloutForSelectedAnnotation = nil;
-
+
// Removing the annotations unregisters any outstanding KVO observers.
[self removeAnnotations:self.annotations];
-
+
if (_mbglMap) {
delete _mbglMap;
_mbglMap = nullptr;
@@ -518,14 +519,14 @@ public:
- (void)setDelegate:(id<MGLMapViewDelegate>)delegate {
_delegate = delegate;
-
+
// Cache checks for delegate method implementations that may be called in a
// hot loop, namely the annotation style methods.
_delegateHasAlphasForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)];
_delegateHasStrokeColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)];
_delegateHasFillColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)];
_delegateHasLineWidthsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:lineWidthForPolylineAnnotation:)];
-
+
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) {
@@ -557,7 +558,7 @@ public:
if (_isTargetingInterfaceBuilder) {
return;
}
-
+
// Default to Streets.
if (!styleURL) {
// An access token is required to load any default style, including
@@ -567,7 +568,7 @@ public:
}
styleURL = [MGLStyle streetsStyleURLWithVersion:MGLStyleDefaultVersion];
}
-
+
styleURL = styleURL.mgl_URLByStandardizingScheme;
_mbglMap->setStyleURL(styleURL.absoluteString.UTF8String);
}
@@ -585,7 +586,7 @@ public:
if (!self.dormant && !newWindow) {
self.dormant = YES;
}
-
+
[self.window removeObserver:self forKeyPath:@"contentLayoutRect"];
[self.window removeObserver:self forKeyPath:@"titlebarAppearsTransparent"];
}
@@ -595,11 +596,11 @@ public:
if (self.dormant && window) {
self.dormant = NO;
}
-
+
if (window && _mbglMap->getConstrainMode() == mbgl::ConstrainMode::None) {
_mbglMap->setConstrainMode(mbgl::ConstrainMode::HeightOnly);
}
-
+
[window addObserver:self
forKeyPath:@"contentLayoutRect"
options:NSKeyValueObservingOptionInitial
@@ -648,7 +649,7 @@ public:
attribute:NSLayoutAttributeTrailing
multiplier:1
constant:MGLOrnamentPadding]];
-
+
// Center the compass above the zoom controls, assuming that the compass is
// narrower than the zoom controls.
[self addConstraint:
@@ -667,7 +668,7 @@ public:
attribute:NSLayoutAttributeBottom
multiplier:1
constant:8]];
-
+
// Place the logo view in the lower-left corner of the view, accounting for
// the logo’s alignment rect.
[self addConstraint:
@@ -686,7 +687,7 @@ public:
attribute:NSLayoutAttributeLeading
multiplier:1
constant:MGLOrnamentPadding - _logoView.image.alignmentRect.origin.x]];
-
+
// Place the attribution view to the right of the logo view and size it to
// fit the buttons inside.
[self addConstraint:[NSLayoutConstraint constraintWithItem:_logoView
@@ -717,7 +718,7 @@ public:
attribute:NSLayoutAttributeTrailing
multiplier:1
constant:8]];
-
+
[super updateConstraints];
}
@@ -755,20 +756,20 @@ public:
if (!_mbglMap) {
return;
}
-
+
CGFloat zoomFactor = self.maximumZoomLevel - self.minimumZoomLevel + 1;
CGFloat cpuFactor = [NSProcessInfo processInfo].processorCount;
CGFloat memoryFactor = (CGFloat)[NSProcessInfo processInfo].physicalMemory / 1000 / 1000 / 1000;
CGFloat sizeFactor = (NSWidth(self.bounds) / mbgl::util::tileSize) * (NSHeight(self.bounds) / mbgl::util::tileSize);
-
+
NSUInteger cacheSize = zoomFactor * cpuFactor * memoryFactor * sizeFactor * 0.5;
-
+
_mbglMap->setSourceTileCacheSize(cacheSize);
}
- (void)invalidate {
MGLAssertIsMainThread();
-
+
[self.layer setNeedsDisplay];
}
@@ -777,7 +778,7 @@ public:
if (!_mbglMap) {
return;
}
-
+
switch (change) {
case mbgl::MapChangeRegionWillChange:
case mbgl::MapChangeRegionWillChangeAnimated:
@@ -794,7 +795,7 @@ public:
// while animating.
[self updateCompass];
[self updateAnnotationCallouts];
-
+
if ([self.delegate respondsToSelector:@selector(mapViewCameraIsChanging:)]) {
[self.delegate mapViewCameraIsChanging:self];
}
@@ -812,7 +813,7 @@ public:
[self updateCompass];
[self updateAnnotationCallouts];
[self updateAnnotationTrackingAreas];
-
+
if ([self.delegate respondsToSelector:@selector(mapView:cameraDidChangeAnimated:)]) {
BOOL animated = change == mbgl::MapChangeRegionDidChangeAnimated;
[self.delegate mapView:self cameraDidChangeAnimated:animated];
@@ -894,7 +895,7 @@ public:
- (void)printWithImage:(NSImage *)image {
NSImageView *imageView = [[NSImageView alloc] initWithFrame:self.bounds];
imageView.image = image;
-
+
NSPrintOperation *op = [NSPrintOperation printOperationWithView:imageView];
[op runOperation];
}
@@ -1061,7 +1062,7 @@ public:
if ([self.camera isEqual:camera]) {
return;
}
-
+
mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera];
mbgl::AnimationOptions animationOptions;
if (duration > 0) {
@@ -1078,7 +1079,7 @@ public:
});
};
}
-
+
[self willChangeValueForKey:@"camera"];
_mbglMap->easeTo(cameraOptions, animationOptions);
[self didChangeValueForKey:@"camera"];
@@ -1097,7 +1098,7 @@ public:
if ([self.camera isEqual:camera]) {
return;
}
-
+
mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera];
mbgl::AnimationOptions animationOptions;
if (duration >= 0) {
@@ -1119,7 +1120,7 @@ public:
});
};
}
-
+
[self willChangeValueForKey:@"camera"];
_mbglMap->flyTo(cameraOptions, animationOptions);
[self didChangeValueForKey:@"camera"];
@@ -1161,7 +1162,7 @@ public:
- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated {
_mbglMap->cancelTransitions();
-
+
mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets);
padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets);
mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngBounds(MGLLatLngBoundsFromCoordinateBounds(bounds), padding);
@@ -1169,7 +1170,7 @@ public:
if (animated) {
animationOptions.duration = MGLDurationInSeconds(MGLAnimationDuration);
}
-
+
[self willChangeValueForKey:@"visibleCoordinateBounds"];
animationOptions.transitionFinishFn = ^() {
[self didChangeValueForKey:@"visibleCoordinateBounds"];
@@ -1212,7 +1213,7 @@ public:
if (!_automaticallyAdjustsContentInsets) {
return;
}
-
+
NSEdgeInsets contentInsets = self.contentInsets;
if ((self.window.styleMask & NSFullSizeContentViewWindowMask)
&& !self.window.titlebarAppearsTransparent) {
@@ -1226,7 +1227,7 @@ public:
} else {
contentInsets = NSEdgeInsetsZero;
}
-
+
self.contentInsets = contentInsets;
}
@@ -1238,7 +1239,7 @@ public:
if (NSEdgeInsetsEqual(contentInsets, self.contentInsets)) {
return;
}
-
+
// After adjusting the content insets, move the center coordinate from the
// old frame of reference to the new one represented by the newly set
// content insets.
@@ -1259,12 +1260,12 @@ public:
NSPoint delta = [gestureRecognizer translationInView:self];
NSPoint endPoint = [gestureRecognizer locationInView:self];
NSPoint startPoint = NSMakePoint(endPoint.x - delta.x, endPoint.y - delta.y);
-
+
NSEventModifierFlags flags = [NSApp currentEvent].modifierFlags;
if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
[self.window invalidateCursorRectsForView:self];
_mbglMap->setGestureInProgress(true);
-
+
if (![self isPanningWithGesture]) {
// Hide the cursor except when panning.
CGDisplayHideCursor(kCGDirectMainDisplay);
@@ -1274,7 +1275,7 @@ public:
|| gestureRecognizer.state == NSGestureRecognizerStateCancelled) {
_mbglMap->setGestureInProgress(false);
[self.window invalidateCursorRectsForView:self];
-
+
if (_didHideCursorDuringGesture) {
_didHideCursorDuringGesture = NO;
// Move the cursor back to the start point and show it again, creating
@@ -1286,15 +1287,15 @@ public:
CGDisplayShowCursor(kCGDirectMainDisplay);
}
}
-
+
if (flags & NSShiftKeyMask) {
// Shift-drag to zoom.
if (!self.zoomEnabled) {
return;
}
-
+
_mbglMap->cancelTransitions();
-
+
if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
_scaleAtBeginningOfGesture = _mbglMap->getScale();
} else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
@@ -1304,7 +1305,7 @@ public:
} else if (flags & NSAlternateKeyMask) {
// Option-drag to rotate and/or tilt.
_mbglMap->cancelTransitions();
-
+
if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
_directionAtBeginningOfGesture = self.direction;
_pitchAtBeginningOfGesture = _mbglMap->getPitch();
@@ -1323,7 +1324,7 @@ public:
} else if (self.scrollEnabled) {
// Otherwise, drag to pan.
_mbglMap->cancelTransitions();
-
+
if (gestureRecognizer.state == NSGestureRecognizerStateChanged) {
delta.y *= -1;
[self offsetCenterCoordinateBy:delta animated:NO];
@@ -1345,9 +1346,9 @@ public:
if (!self.zoomEnabled) {
return;
}
-
+
_mbglMap->cancelTransitions();
-
+
if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
_mbglMap->setGestureInProgress(true);
_scaleAtBeginningOfGesture = _mbglMap->getScale();
@@ -1373,7 +1374,7 @@ public:
|| [self subviewContainingGesture:gestureRecognizer]) {
return;
}
-
+
NSPoint gesturePoint = [gestureRecognizer locationInView:self];
MGLAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:gesturePoint persistingResults:YES];
if (hitAnnotationTag != MGLAnnotationTagNotFound) {
@@ -1401,9 +1402,9 @@ public:
|| [self subviewContainingGesture:gestureRecognizer]) {
return;
}
-
+
_mbglMap->cancelTransitions();
-
+
NSPoint gesturePoint = [gestureRecognizer locationInView:self];
[self scaleBy:2 atPoint:gesturePoint animated:YES];
}
@@ -1412,9 +1413,9 @@ public:
if (!self.zoomEnabled) {
return;
}
-
+
_mbglMap->cancelTransitions();
-
+
// Tap with two fingers (“right-click”) to zoom out on mice but not trackpads.
NSPoint gesturePoint = [self convertPoint:event.locationInWindow fromView:nil];
[self scaleBy:0.5 atPoint:gesturePoint animated:YES];
@@ -1425,9 +1426,9 @@ public:
if (!self.rotateEnabled) {
return;
}
-
+
_mbglMap->cancelTransitions();
-
+
if (gestureRecognizer.state == NSGestureRecognizerStateBegan) {
_mbglMap->setGestureInProgress(true);
_directionAtBeginningOfGesture = self.direction;
@@ -1471,13 +1472,13 @@ public:
&& _rotationGestureRecognizer.state == NSGestureRecognizerStatePossible) {
// Scroll to pan.
_mbglMap->cancelTransitions();
-
+
CGFloat x = event.scrollingDeltaX;
CGFloat y = event.scrollingDeltaY;
if (x || y) {
[self offsetCenterCoordinateBy:NSMakePoint(x, y) animated:NO];
}
-
+
// Drift pan.
if (event.momentumPhase != NSEventPhaseNone) {
[self offsetCenterCoordinateBy:NSMakePoint(x, y) animated:NO];
@@ -1589,7 +1590,7 @@ public:
if (_annotationContextsByAnnotationTag.empty()) {
return nil;
}
-
+
// Map all the annotation tags to the annotations themselves.
std::vector<id <MGLAnnotation>> annotations;
std::transform(_annotationContextsByAnnotationTag.begin(),
@@ -1606,7 +1607,7 @@ public:
if (!_annotationContextsByAnnotationTag.count(tag)) {
return nil;
}
-
+
MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag[tag];
return annotationContext.annotation;
}
@@ -1616,7 +1617,7 @@ public:
if (!annotation) {
return MGLAnnotationTagNotFound;
}
-
+
for (auto &pair : _annotationContextsByAnnotationTag) {
if (pair.second.annotation == annotation) {
return pair.first;
@@ -1635,21 +1636,21 @@ public:
if (!annotations) {
return;
}
-
+
[self willChangeValueForKey:@"annotations"];
-
+
BOOL delegateHasImagesForAnnotations = [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)];
-
+
for (id <MGLAnnotation> annotation in annotations) {
NSAssert([annotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation");
-
+
if ([annotation isKindOfClass:[MGLMultiPoint class]]) {
// Actual multipoints aren’t supported as annotations.
if ([annotation isMemberOfClass:[MGLMultiPoint class]]
|| [annotation isMemberOfClass:[MGLMultiPointFeature class]]) {
continue;
}
-
+
// The multipoint knows how to style itself (with the map view’s help).
MGLMultiPoint *multiPoint = (MGLMultiPoint *)annotation;
if (!multiPoint.pointCount) {
@@ -1675,13 +1676,13 @@ public:
if (!annotationImage) {
annotationImage = self.defaultAnnotationImage;
}
-
+
NSString *symbolName = annotationImage.styleIconIdentifier;
if (!symbolName) {
symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier];
annotationImage.styleIconIdentifier = symbolName;
}
-
+
if (!self.annotationImagesByIdentifier[annotationImage.reuseIdentifier]) {
self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage;
[self installAnnotationImage:annotationImage];
@@ -1696,7 +1697,7 @@ public:
context.annotation = annotation;
context.imageReuseIdentifier = annotationImage.reuseIdentifier;
_annotationContextsByAnnotationTag[annotationTag] = context;
-
+
if ([annotation isKindOfClass:[NSObject class]]) {
NSAssert(![annotation isKindOfClass:[MGLMultiPoint class]], @"Point annotation should not be MGLMultiPoint.");
[(NSObject *)annotation addObserver:self forKeyPath:@"coordinate" options:0 context:(void *)(NSUInteger)annotationTag];
@@ -1710,7 +1711,7 @@ public:
}
[self didChangeValueForKey:@"annotations"];
-
+
[self updateAnnotationTrackingAreas];
}
@@ -1732,36 +1733,23 @@ public:
- (void)installAnnotationImage:(MGLAnnotationImage *)annotationImage {
NSString *iconIdentifier = annotationImage.styleIconIdentifier;
self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage;
-
+
NSImage *image = annotationImage.image;
NSSize size = image.size;
if (size.width == 0 || size.height == 0 || !image.valid) {
// Can’t create an empty sprite. An image that hasn’t loaded is also useless.
return;
}
-
- // Create a bitmap image representation from the image, respecting backing
- // scale factor and any resizing done on the image at runtime.
- // http://www.cocoabuilder.com/archive/cocoa/82430-nsimage-getting-raw-bitmap-data.html#82431
- [image lockFocus];
- NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:{ NSZeroPoint, size }];
- [image unlockFocus];
-
- // Get the image’s raw pixel data as an RGBA buffer.
- std::string pixelString((const char *)rep.bitmapData, rep.pixelsWide * rep.pixelsHigh * 4 /* RGBA */);
-
- mbgl::PremultipliedImage cPremultipliedImage(rep.pixelsWide, rep.pixelsHigh);
- std::copy(rep.bitmapData, rep.bitmapData + cPremultipliedImage.size(), cPremultipliedImage.data.get());
- auto cSpriteImage = std::make_shared<mbgl::SpriteImage>(std::move(cPremultipliedImage),
- (float)(rep.pixelsWide / size.width));
- _mbglMap->addAnnotationIcon(iconIdentifier.UTF8String, cSpriteImage);
-
+
+ std::shared_ptr<mbgl::SpriteImage> sprite(annotationImage.image.mgl_spriteImage);
+ _mbglMap->addAnnotationIcon(iconIdentifier.UTF8String, sprite);
+
// Create a slop area with a “radius” equal to the annotation image’s entire
// size, allowing the eventual click to be on any point within this image.
// Union this slop area with any existing slop areas.
_unionedAnnotationImageSize = NSMakeSize(MAX(_unionedAnnotationImageSize.width, size.width),
MAX(_unionedAnnotationImageSize.height, size.height));
-
+
// Opt into potentially expensive cursor tracking areas.
if (annotationImage.cursor) {
_wantsCursorRects = YES;
@@ -1778,12 +1766,12 @@ public:
if (!annotations) {
return;
}
-
+
[self willChangeValueForKey:@"annotations"];
for (id <MGLAnnotation> annotation in annotations) {
NSAssert([annotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation");
-
+
MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation];
NSAssert(annotationTag != MGLAnnotationTagNotFound, @"No ID for annotation %@", annotation);
@@ -1793,9 +1781,9 @@ public:
if (annotationTag == _lastSelectedAnnotationTag) {
_lastSelectedAnnotationTag = MGLAnnotationTagNotFound;
}
-
+
_annotationContextsByAnnotationTag.erase(annotationTag);
-
+
if ([annotation isKindOfClass:[NSObject class]] &&
![annotation isKindOfClass:[MGLMultiPoint class]]) {
[(NSObject *)annotation removeObserver:self forKeyPath:@"coordinate" context:(void *)(NSUInteger)annotationTag];
@@ -1805,9 +1793,9 @@ public:
_mbglMap->removeAnnotation(annotationTag);
}
-
+
[self didChangeValueForKey:@"annotations"];
-
+
[self updateAnnotationTrackingAreas];
}
@@ -1821,11 +1809,11 @@ public:
/**
Returns the tag of the annotation at the given point in the view.
-
+
This is more involved than it sounds: if multiple point annotations overlap
near the point, this method cycles through them so that each of them is
accessible to the user at some point.
-
+
@param persist True to remember the cycleable set of annotations, so that a
different annotation is returned the next time this method is called
with the same point. Setting this parameter to false is useful for
@@ -1841,13 +1829,13 @@ public:
queryRect = NSInsetRect(queryRect, -MGLAnnotationImagePaddingForHitTest,
-MGLAnnotationImagePaddingForHitTest);
std::vector<MGLAnnotationTag> nearbyAnnotations = [self annotationTagsInRect:queryRect];
-
+
if (nearbyAnnotations.size()) {
// Assume that the user is fat-fingering an annotation.
NSRect hitRect = NSInsetRect({ point, NSZeroSize },
-MGLAnnotationImagePaddingForHitTest,
-MGLAnnotationImagePaddingForHitTest);
-
+
// Filter out any annotation whose image is unselectable or for which
// hit testing fails.
auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MGLAnnotationTag annotationTag) {
@@ -1856,12 +1844,12 @@ public:
if (!annotation) {
return true;
}
-
+
MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag];
if (!annotationImage.selectable) {
return true;
}
-
+
// Filter out the annotation if the fattened finger didn’t land on a
// translucent or opaque pixel in the image.
NSRect annotationRect = [self frameOfImage:annotationImage.image
@@ -1871,13 +1859,13 @@ public:
});
nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end));
}
-
+
MGLAnnotationTag hitAnnotationTag = MGLAnnotationTagNotFound;
if (nearbyAnnotations.size()) {
// The annotation tags need to be stable in order to compare them with
// the remembered tags.
std::sort(nearbyAnnotations.begin(), nearbyAnnotations.end());
-
+
if (nearbyAnnotations == _annotationsNearbyLastClick) {
// The first selection in the cycle should be the one nearest to the
// click.
@@ -1891,7 +1879,7 @@ public:
coordinateB.longitude - currentCoordinate.longitude);
return distanceA < distanceB;
});
-
+
// The last time we persisted a set of annotations, we had the same
// set of annotations as we do now. Cycle through them.
if (_lastSelectedAnnotationTag == MGLAnnotationTagNotFound
@@ -1920,14 +1908,14 @@ public:
if (persist) {
_annotationsNearbyLastClick = nearbyAnnotations;
}
-
+
// Choose the first nearby annotation.
if (nearbyAnnotations.size()) {
hitAnnotationTag = nearbyAnnotations.front();
}
}
}
-
+
return hitAnnotationTag;
}
@@ -1966,13 +1954,13 @@ public:
if (!selectedAnnotations.count) {
return;
}
-
+
id <MGLAnnotation> firstAnnotation = selectedAnnotations[0];
NSAssert([firstAnnotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation");
if ([firstAnnotation isKindOfClass:[MGLMultiPoint class]]) {
return;
}
-
+
// Select the annotation if it’s visible.
if (MGLCoordinateInCoordinateBounds(firstAnnotation.coordinate, self.visibleCoordinateBounds)) {
[self selectAnnotation:firstAnnotation];
@@ -1985,29 +1973,29 @@ public:
if (!annotation || [annotation isKindOfClass:[MGLMultiPoint class]]) {
return;
}
-
+
id <MGLAnnotation> selectedAnnotation = self.selectedAnnotation;
if (annotation == selectedAnnotation) {
return;
}
-
+
// Deselect the annotation before reselecting it.
[self deselectAnnotation:selectedAnnotation];
-
+
// Add the annotation to the map if it hasn’t been added yet.
MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation];
if (annotationTag == MGLAnnotationTagNotFound) {
[self addAnnotation:annotation];
}
-
+
// The annotation can’t be selected if no part of it is hittable.
NSRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag];
if (NSIsEmptyRect(NSIntersectionRect(positioningRect, self.bounds))) {
return;
}
-
+
self.selectedAnnotation = annotation;
-
+
// For the callout to be shown, the annotation must have a title, its
// callout must not already be shown, and the annotation must be able to
// show a callout according to the delegate.
@@ -2017,7 +2005,7 @@ public:
&& [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)]
&& [self.delegate mapView:self annotationCanShowCallout:annotation]) {
NSPopover *callout = [self calloutForAnnotation:annotation];
-
+
// Hang the callout off the right edge of the annotation image’s
// alignment rect, or off the left edge in a right-to-left UI.
callout.delegate = self;
@@ -2065,7 +2053,7 @@ public:
- (NSPopover *)calloutForAnnotation:(id <MGLAnnotation>)annotation {
NSPopover *callout = [[NSPopover alloc] init];
callout.behavior = NSPopoverBehaviorTransient;
-
+
NSViewController *viewController;
if ([self.delegate respondsToSelector:@selector(mapView:calloutViewControllerForAnnotation:)]) {
NSViewController *viewControllerFromDelegate = [self.delegate mapView:self
@@ -2082,7 +2070,7 @@ public:
// annotation.
viewController.representedObject = annotation;
callout.contentViewController = viewController;
-
+
return callout;
}
@@ -2110,7 +2098,7 @@ public:
if (!image) {
return NSZeroRect;
}
-
+
NSRect positioningRect = [self frameOfImage:image centeredAtCoordinate:annotation.coordinate];
positioningRect = NSOffsetRect(image.alignmentRect, positioningRect.origin.x, positioningRect.origin.y);
return NSInsetRect(positioningRect, -MGLAnnotationImagePaddingForCallout,
@@ -2130,10 +2118,10 @@ public:
|| _annotationContextsByAnnotationTag.count(annotationTag) == 0) {
return nil;
}
-
+
NSString *customSymbol = _annotationContextsByAnnotationTag.at(annotationTag).imageReuseIdentifier;
NSString *symbolName = customSymbol.length ? customSymbol : MGLDefaultStyleMarkerSymbolName;
-
+
return [self dequeueReusableAnnotationImageWithIdentifier:symbolName];
}
@@ -2141,11 +2129,11 @@ public:
if (!annotation || self.selectedAnnotation != annotation) {
return;
}
-
+
// Close the callout popover gracefully.
NSPopover *callout = self.calloutForSelectedAnnotation;
[callout performClose:self];
-
+
self.selectedAnnotation = nil;
}
@@ -2217,7 +2205,7 @@ public:
id <MGLAnnotation> annotation = self.calloutForSelectedAnnotation.contentViewController.representedObject;
self.calloutForSelectedAnnotation = nil;
self.selectedAnnotation = nil;
-
+
if ([self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotation:)]) {
[self.delegate mapView:self didDeselectAnnotation:annotation];
}
@@ -2274,7 +2262,7 @@ public:
}
}
}
-
+
// Blow away any cursor tracking areas and rebuild them. That’s the
// potentially expensive part.
if (_wantsCursorRects) {
@@ -2295,13 +2283,13 @@ public:
[self addCursorRect:self.bounds cursor:[NSCursor closedHandCursor]];
return;
}
-
+
// The rest of this method can be expensive, so bail if no annotations have
// ever had custom cursors.
if (!_wantsCursorRects) {
return;
}
-
+
std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:self.bounds];
for (MGLAnnotationTag annotationTag : annotationTags) {
id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag];
@@ -2327,7 +2315,7 @@ public:
- (NS_ARRAY_OF(id <MGLFeature>) *)visibleFeaturesAtPoint:(NSPoint)point inStyleLayersWithIdentifiers:(NS_SET_OF(NSString *) *)styleLayerIdentifiers {
// Cocoa origin is at the lower-left corner.
mbgl::ScreenCoordinate screenCoordinate = { point.x, NSHeight(self.bounds) - point.y };
-
+
mbgl::optional<std::vector<std::string>> optionalLayerIDs;
if (styleLayerIdentifiers) {
__block std::vector<std::string> layerIDs;
@@ -2337,7 +2325,7 @@ public:
}];
optionalLayerIDs = layerIDs;
}
-
+
std::vector<mbgl::Feature> features = _mbglMap->queryRenderedFeatures(screenCoordinate, optionalLayerIDs);
return MGLFeaturesFromMBGLFeatures(features);
}
@@ -2352,7 +2340,7 @@ public:
{ NSMinX(rect), NSHeight(self.bounds) - NSMaxY(rect) },
{ NSMaxX(rect), NSHeight(self.bounds) - NSMinY(rect) },
};
-
+
mbgl::optional<std::vector<std::string>> optionalLayerIDs;
if (styleLayerIdentifiers) {
__block std::vector<std::string> layerIDs;
@@ -2362,7 +2350,7 @@ public:
}];
optionalLayerIDs = layerIDs;
}
-
+
std::vector<mbgl::Feature> features = _mbglMap->queryRenderedFeatures(screenBox, optionalLayerIDs);
return MGLFeaturesFromMBGLFeatures(features);
}
@@ -2371,7 +2359,7 @@ public:
- (void)prepareForInterfaceBuilder {
[super prepareForInterfaceBuilder];
-
+
// Color the background a glorious Mapbox teal.
self.layer.borderColor = [NSColor colorWithRed:59/255.
green:178/255.
@@ -2382,7 +2370,7 @@ public:
green:178/255.
blue:208/255.
alpha:0.6].CGColor;
-
+
// Place a playful marker right smack dab in the middle.
self.layer.contents = MGLDefaultMarkerImage();
self.layer.contentsGravity = kCAGravityCenter;
@@ -2441,7 +2429,7 @@ public:
bounds.extend([self convertPoint:{ NSMaxX(rect), NSMinY(rect) } toLatLngFromView:view]);
bounds.extend([self convertPoint:{ NSMaxX(rect), NSMaxY(rect) } toLatLngFromView:view]);
bounds.extend([self convertPoint:{ NSMinX(rect), NSMaxY(rect) } toLatLngFromView:view]);
-
+
// The world is wrapping if a point just outside the bounds is also within
// the rect.
mbgl::LatLng outsideLatLng;
@@ -2456,13 +2444,13 @@ public:
bounds.east() + 1,
};
}
-
+
// If the world is wrapping, extend the bounds to cover all longitudes.
if (NSPointInRect([self convertLatLng:outsideLatLng toPointToView:view], rect)) {
bounds.extend(mbgl::LatLng(bounds.south(), -180));
bounds.extend(mbgl::LatLng(bounds.south(), 180));
}
-
+
return bounds;
}
@@ -2570,7 +2558,7 @@ public:
mbgl::PremultipliedImage image { size[0], size[1] };
MBGL_CHECK_ERROR(glReadPixels(0, 0, size[0], size[1], GL_RGBA, GL_UNSIGNED_BYTE, image.data.get()));
-
+
const size_t stride = image.stride();
auto tmp = std::make_unique<uint8_t[]>(stride);
uint8_t *rgba = image.data.get();
@@ -2579,14 +2567,14 @@ public:
std::memcpy(rgba + i * stride, rgba + j * stride, stride);
std::memcpy(rgba + j * stride, tmp.get(), stride);
}
-
+
return image;
}
-
+
private:
/// Cocoa map view that this adapter bridges to.
__weak MGLMapView *nativeView = nullptr;
-
+
/// Backing scale factor of the view.
const float scaleFactor;
};
diff --git a/platform/macos/src/NSImage+MGLAdditions.h b/platform/macos/src/NSImage+MGLAdditions.h
new file mode 100644
index 0000000000..a2144e96a2
--- /dev/null
+++ b/platform/macos/src/NSImage+MGLAdditions.h
@@ -0,0 +1,9 @@
+#import <Cocoa/Cocoa.h>
+
+#include <mbgl/sprite/sprite_image.hpp>
+
+@interface NSImage (MGLAdditions)
+
+- (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage;
+
+@end
diff --git a/platform/macos/src/NSImage+MGLAdditions.mm b/platform/macos/src/NSImage+MGLAdditions.mm
new file mode 100644
index 0000000000..7931c0b2a5
--- /dev/null
+++ b/platform/macos/src/NSImage+MGLAdditions.mm
@@ -0,0 +1,23 @@
+#import "NSImage+MGLAdditions.h"
+
+@implementation NSImage (MGLAdditions)
+
+- (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage
+{
+ // Create a bitmap image representation from the image, respecting backing
+ // scale factor and any resizing done on the image at runtime.
+ // http://www.cocoabuilder.com/archive/cocoa/82430-nsimage-getting-raw-bitmap-data.html#82431
+ [image lockFocus];
+ NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:{ NSZeroPoint, self.size }];
+ [image unlockFocus];
+
+ // Get the image’s raw pixel data as an RGBA buffer.
+ std::string pixelString((const char *)rep.bitmapData, rep.pixelsWide * rep.pixelsHigh * 4 /* RGBA */);
+
+ mbgl::PremultipliedImage cPremultipliedImage(rep.pixelsWide, rep.pixelsHigh);
+ std::copy(rep.bitmapData, rep.bitmapData + cPremultipliedImage.size(), cPremultipliedImage.data.get());
+ return std::make_unique<mbgl::SpriteImage>(std::move(cPremultipliedImage),
+ (float)(rep.pixelsWide / self.size.width));
+}
+
+@end