From 06a8ed79598beae923b795dea711834b4775f6b0 Mon Sep 17 00:00:00 2001 From: Jesse Bounds Date: Wed, 8 Feb 2017 12:27:12 -0800 Subject: Add back MGLStyleFunction and re-document MGLStyleValue (#7943) [ios, macos] Add back MGLStyleFunction and re-document MGLStyleValue --- platform/darwin/src/MGLStyleValue.h | 322 +++++++++++++++++++++----- platform/darwin/src/MGLStyleValue.mm | 104 ++++++--- platform/darwin/src/MGLStyleValue_Private.h | 19 +- platform/darwin/test/MGLStyleValueTests.swift | 70 ++++++ 4 files changed, 428 insertions(+), 87 deletions(-) diff --git a/platform/darwin/src/MGLStyleValue.h b/platform/darwin/src/MGLStyleValue.h index c8ec08b6a0..1ae8a67861 100644 --- a/platform/darwin/src/MGLStyleValue.h +++ b/platform/darwin/src/MGLStyleValue.h @@ -30,9 +30,10 @@ typedef NS_ENUM(NSUInteger, MGLInterpolationMode) { The `MGLStyleValue` class itself represents a class cluster. Under the hood, a particular `MGLStyleValue` object may be either an `MGLStyleConstantValue` to - represent a constant value or an `MGLStyleFunction` to represent a value - function. Do not initialize an `MGLStyleValue` object directly; instead, use - one of the class factory methods to create an `MGLStyleValue` object. + represent a constant value or one of the concrete subclasses of + `MGLStyleFunction` to represent a value function. Do not initialize an + `MGLStyleValue` object directly; instead, use one of the class factory methods + to create an `MGLStyleValue` object. The `MGLStyleValue` class takes a generic parameter `T` that indicates the Foundation class being wrapped by this class. Common values for `T` include: @@ -62,32 +63,68 @@ MGL_EXPORT #pragma mark Function values /** - Creates and returns an `MGLStyleFunction` object representing a linear zoom - level function with any number of stops. + Creates and returns an `MGLCameraStyleFunction` object representing a linear camera + function with one or more stops. @param stops A dictionary associating zoom levels with style values. - @return An `MGLStyleFunction` object with the given stops. + @return An `MGLCameraStyleFunction` object with the given stops. */ -+ (instancetype)valueWithStops:(NSDictionary *> *)stops __attribute__((deprecated("Use +[MGLStyleValue valueWithInterpolationMode:cameraStops:options:]"))); ++ (instancetype)valueWithStops:(NS_DICTIONARY_OF(NSNumber *, MGLStyleValue *) *)stops __attribute__((deprecated("Use +[MGLStyleValue valueWithInterpolationMode:cameraStops:options:]"))); /** - Creates and returns an `MGLStyleFunction` object representing a zoom level - function with an exponential interpolation base and any number of stops. + Creates and returns an `MGLCameraStyleFunction` object representing a camera + function with an exponential interpolation base and one or more stops. @param interpolationBase The exponential base of the interpolation curve. @param stops A dictionary associating zoom levels with style values. - @return An `MGLStyleFunction` object with the given interpolation base and stops. + @return An `MGLCameraStyleFunction` object with the given interpolation base and stops. */ -+ (instancetype)valueWithInterpolationBase:(CGFloat)interpolationBase stops:(NSDictionary *> *)stops __attribute__((deprecated("Use +[MGLStyleValue valueWithInterpolationMode:cameraStops:options:]"))); ++ (instancetype)valueWithInterpolationBase:(CGFloat)interpolationBase stops:(NS_DICTIONARY_OF(NSNumber *, MGLStyleValue *) *)stops __attribute__((deprecated("Use +[MGLStyleValue valueWithInterpolationMode:cameraStops:options:]"))); -// TODO: API docs -+ (instancetype)valueWithInterpolationMode:(MGLInterpolationMode)interpolationMode cameraStops:(NS_DICTIONARY_OF(id, MGLStyleValue *) *)stops options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options; +/** + Creates and returns an `MGLCameraStyleFunction` object representing a camera function + with one or more stops. + + @param interpolationMode The mode used to interpolate property values between + map zoom level changes. + @param cameraStops A dictionary associating zoom levels with style values. + @param options A dictionary containing `MGLStyleFunctionOption` values that + specify how a function is applied. + @return An `MGLCameraStyleFunction` object with the given interpolation mode, + camera stops, and options. + */ ++ (instancetype)valueWithInterpolationMode:(MGLInterpolationMode)interpolationMode cameraStops:(NS_DICTIONARY_OF(id, MGLStyleValue *) *)cameraStops options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options; -// TODO: API docs -+ (instancetype)valueWithInterpolationMode:(MGLInterpolationMode)interpolationMode sourceStops:(nullable NS_DICTIONARY_OF(id, MGLStyleValue *) *)stops attributeName:(NSString *)attributeName options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options; +/** + Creates and returns an `MGLSourceStyleFunction` object representing a source function. + + @param interpolationMode The mode used to interpolate property values over a + range of feature attribute values. + @param sourceStops A dictionary associating feature attributes with style values. + @param attributeName Specifies the feature attribute to take as the function + input. + @param options A dictionary containing `MGLStyleFunctionOption` values that + specify how a function is applied. + @return An `MGLSourceStyleFunction` object with the given interpolation mode, + source stops, attribute name, and options. + */ ++ (instancetype)valueWithInterpolationMode:(MGLInterpolationMode)interpolationMode sourceStops:(nullable NS_DICTIONARY_OF(id, MGLStyleValue *) *)sourceStops attributeName:(NSString *)attributeName options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options; -// TODO: API docs -+ (instancetype)valueWithInterpolationMode:(MGLInterpolationMode)interpolationMode compositeStops:(NS_DICTIONARY_OF(id, NS_DICTIONARY_OF(id, MGLStyleValue *) *) *)stops attributeName:(NSString *)attributeName options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options; +/** + Creates and returns an `MGLCompositeStyleFunction` object representing a composite + function. + + @param interpolationMode The mode used to interpolate property values over a + range of feature attribute values for each outer zoom level. + @param sourceStops A dictionary associating feature attributes with style values. + @param attributeName Specifies the feature attribute to take as the function + input. + @param options A dictionary containing `MGLStyleFunctionOption` values that + specify how a function is applied. + @return An `MGLCompositeStyleFunction` object with the given interpolation mode, + composite stops, attribute name, and options. + */ ++ (instancetype)valueWithInterpolationMode:(MGLInterpolationMode)interpolationMode compositeStops:(NS_DICTIONARY_OF(id, NS_DICTIONARY_OF(id, MGLStyleValue *) *) *)compositeStops attributeName:(NSString *)attributeName options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options; @end @@ -135,77 +172,258 @@ MGL_EXPORT @end +/** + An `MGLStyleFunction` is a is an abstract superclass for functions that are + defined by an `MGLCameraStyleFunction`, `MGLSourceStyleFunction`, or + `MGLCompositeStyleFunction` object. + + Do not create instances of this class directly, and do not create your own + subclasses of this class. Instead, use one of the class factory methods in + `MGLStyleValue` to create instances of the following concrete subclasses: + `MGLCameraStyleFunction`, `MGLSourceStyleFunction`, and + `MGLCompositeStyleFunction`. + + The `MGLStyleFunction` class takes a generic parameter `T` that indicates the + Foundation class being wrapped by this class. + */ MGL_EXPORT -@interface MGLCameraStyleFunction : MGLStyleValue +@interface MGLStyleFunction : MGLStyleValue -// TODO: API docs -+ (instancetype)functionWithInterpolationMode:(MGLInterpolationMode)interpolationMode stops:(NS_DICTIONARY_OF(id, MGLStyleValue *) *)stops options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options; +#pragma mark Creating a Style Function -// TODO: API docs -- (instancetype)initWithInterpolationMode:(MGLInterpolationMode)interpolationMode stops:(NS_DICTIONARY_OF(id, MGLStyleValue *) *)stops options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options NS_DESIGNATED_INITIALIZER; +/** + Creates and returns an `MGLCameraStyleFunction` object representing a camera + function with a linear interpolation curve. -// TODO: API docs + @note Do not create function instances using this method unless it is required + for backwards compatiblity with your application code. + + @param stops A dictionary associating zoom levels with style values. + @return An `MGLCameraStyleFunction` object with the given stops. + */ ++ (instancetype)functionWithStops:(NS_DICTIONARY_OF(NSNumber *, MGLStyleValue *) *)stops __attribute__((deprecated("Use +[MGLStyleValue valueWithInterpolationMode:cameraStops:options:]"))); + +/** + Creates and returns an `MGLCameraStyleFunction` object representing a camera + function with an interpolation curve controlled by the provided interpolation + base. + + @note Do not create function instances using this method unless it is required + for backwards compatiblity with your application code. + + @param interpolationBase The exponential base of the interpolation curve. + @param stops A dictionary associating zoom levels with style values. + @return An `MGLCameraStyleFunction` object with the given interpolation base and stops. + */ ++ (instancetype)functionWithInterpolationBase:(CGFloat)interpolationBase stops:(NS_DICTIONARY_OF(NSNumber *, MGLStyleValue *) *)stops __attribute__((deprecated("Use +[MGLStyleValue valueWithInterpolationMode:cameraStops:options:]"))); + +#pragma mark Initializing a Style Function + +/** + Returns an `MGLStyleFunction` object representing a camera function. If the + function is set as a style layer property value, it will be interpreted + as a camera function with an interpolation curve controlled by the provided + interpolation base. + + @note Do not create instances of `MGLStyleFunction` unless it is required for + backwards compatiblity with your application code. You should create and use + instances of `MGLCameraStyleFunction` to specify how properties will + be visualized at different zoom levels. + + @param interpolationBase The exponential base of the interpolation curve. + @param stops A dictionary associating zoom levels with style values. + @return An `MGLStyleFunction` object with the given interpolation base and stops. + */ +- (instancetype)initWithInterpolationBase:(CGFloat)interpolationBase stops:(NS_DICTIONARY_OF(NSNumber *, MGLStyleValue *) *)stops __attribute__((deprecated("Use +[MGLStyleValue valueWithInterpolationMode:cameraStops:options:]"))); + +#pragma mark Accessing the Parameters of a Function + +/** + The modes used to interpolate property values between map zoom level changes or + over a range of feature attribute values. + */ @property (nonatomic) MGLInterpolationMode interpolationMode; -// TODO: API docs -@property (nonatomic, copy) NS_DICTIONARY_OF(id, MGLStyleValue *) *stops; +/** + A dictionary associating zoom levels with style values. + */ +@property (nonatomic, copy, nullable) NSDictionary *stops; -// TODO: API docs +/** + The exponential interpolation base of the function’s interpolation curve. + + @note This property specifies the exponential base of the interpolation curve + of `MGLCameraStyleFunction` and `MGLSourceStyleFunction` functions that use + a `MGLInterpolationModeExponential` `interpolationMode`. Otherwise, it is + ignored. + */ @property (nonatomic) CGFloat interpolationBase; @end -@compatibility_alias MGLStyleFunction MGLCameraStyleFunction; +/** + An `MGLCameraStyleFunction` is a value function defining a style value that changes + as the zoom level changes. The layout and paint attribute properties of an + `MGLStyleLayer` object can be set to `MGLCameraStyleFunction` objects. Use a camera + function to create the illusion of depth and control data density. + The `MGLCameraStyleFunction` class takes a generic parameter `T` that indicates the + Foundation class being wrapped by this class. + */ MGL_EXPORT -@interface MGLSourceStyleFunction : MGLStyleValue +@interface MGLCameraStyleFunction : MGLStyleFunction -// TODO: API docs -+ (instancetype)functionWithInterpolationMode:(MGLInterpolationMode)interpolationMode stops:(nullable NS_DICTIONARY_OF(id, MGLStyleValue *) *)stops attributeName:(NSString *)attributeName options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options; +#pragma mark Creating a Camera Function -// TODO: API docs -- (instancetype)initWithInterpolationMode:(MGLInterpolationMode)interpolationMode stops:(nullable NS_DICTIONARY_OF(id, MGLStyleValue *) *)stops attributeName:(NSString *)attributeName options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options NS_DESIGNATED_INITIALIZER; +/** + Creates and returns an `MGLCameraStyleFunction` object representing a camera + function with one or more stops. -// TODO: API docs -@property (nonatomic) MGLInterpolationMode interpolationMode; + @param interpolationMode The mode used to interpolate property values between + map zoom level changes. + @param stops A dictionary associating zoom levels with style values. + @param options A dictionary containing `MGLStyleFunctionOption` values that + specify how a function is applied. + @return An `MGLCameraStyleFunction` object with the given interpolation mode, + camera stops, and options. + */ ++ (instancetype)functionWithInterpolationMode:(MGLInterpolationMode)interpolationMode stops:(NS_DICTIONARY_OF(id, MGLStyleValue *) *)stops options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options; -// TODO: API docs +#pragma mark Accessing the Parameters of a Camera Function + +/** + A dictionary associating zoom levels with style values. + + Each of the function’s stops is represented by one key-value pair in the + dictionary. Each key in the dictionary is an `NSNumber` object containing a + floating-point zoom level. Each value in the dictionary is an `MGLStyleValue` + object containing the value of the style attribute when the map is at the + associated zoom level. An `MGLStyleFunction` object may not be used recursively + as a stop value. + */ +@property (nonatomic, copy) NS_DICTIONARY_OF(id, MGLStyleValue *) *stops; + +@end + +/** + An `MGLSourceStyleFunction` is a value function defining a style value that + changes with its properties. The layout and paint attribute properties of an + `MGLStyleLayer` object can be set to `MGLSourceStyleFunction` objects. + Use source functions to visually differentate types of features within the same + layer or create data visualizations. + + The `MGLSourceStyleFunction` class takes a generic parameter `T` that indicates the + Foundation class being wrapped by this class. + */ +MGL_EXPORT +@interface MGLSourceStyleFunction : MGLStyleFunction + +#pragma mark Creating a Source Function + +/** + Creates and returns an `MGLSourceStyleFunction` object representing a source + function. + + @param interpolationMode The mode used to interpolate property values over a + range of feature attribute values. + @param sourceStops A dictionary associating feature attributes with style values. + @param attributeName Specifies the feature attribute to take as the function + input. + @param options A dictionary containing `MGLStyleFunctionOption` values that + specify how a function is applied. + @return An `MGLSourceStyleFunction` object with the given interpolation mode, + source stops, attribute name, and options. +*/ ++ (instancetype)functionWithInterpolationMode:(MGLInterpolationMode)interpolationMode stops:(nullable NS_DICTIONARY_OF(id, MGLStyleValue *) *)stops attributeName:(NSString *)attributeName options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options; + +#pragma mark Accessing the Parameters of a Source Function + +/** + A string that specifies the feature attribute key whose value be used as the function + input. +*/ @property (nonatomic, copy) NSString *attributeName; -// TODO: API docs +/** + A dictionary associating attribute values with style values. + + Each of the function’s stops is represented by one key-value pair in the + dictionary. Each key in the dictionary is an object representing a feature + attribute key or interpolation stop. Each value in the dictionary is an + `MGLStyleValue` object containing the value to use when the function is given + the associated attribute key. An `MGLStyleFunction` object may not be used + recursively as a stop value. + */ @property (nonatomic, copy, nullable) NS_DICTIONARY_OF(id, MGLStyleValue *) *stops; -// TODO: API docs +/** + An `MGLStyleValue` object containing the default value to use when there is + no input to provide to the function. + */ @property (nonatomic, nullable) MGLStyleValue *defaultValue; -// TODO: API docs -@property (nonatomic) CGFloat interpolationBase; - @end +/** + An `MGLCompositeStyleFunction` is a value function defining a style value that + changes with the feature attributes at each map zoom level. The layout and paint + attribute properties of an `MGLStyleLayer` object can be set to + `MGLCompositeStyleFunction` objects. Use composite functions to allow the + appearance of a map feature to change with both its attributes and the map zoom + level. + + The `MGLCompositeStyleFunction` class takes a generic parameter `T` that indicates the + Foundation class being wrapped by this class. + */ MGL_EXPORT -@interface MGLCompositeStyleFunction : MGLStyleValue +@interface MGLCompositeStyleFunction : MGLStyleFunction -// TODO: API docs -+ (instancetype)functionWithInterpolationMode:(MGLInterpolationMode)interpolationMode stops:(NS_DICTIONARY_OF(id, NS_DICTIONARY_OF(id, MGLStyleValue *) *) *)stops attributeName:(NSString *)attributeName options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options; +#pragma mark Creating a Composite Function -- (instancetype)initWithInterpolationMode:(MGLInterpolationMode)interpolationMode stops:(NS_DICTIONARY_OF(id, NS_DICTIONARY_OF(id, MGLStyleValue *) *) *)stops attributeName:(NSString *)attributeName options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options NS_DESIGNATED_INITIALIZER; +/** + Creates and returns an `MGLCompositeStyleFunction` object representing a composite + function. + + @param interpolationMode The mode used to interpolate property values over a + range of feature attribute values for each outer zoom level. + @param sourceStops A dictionary associating feature attributes with style values. + @param attributeName Specifies the feature attribute to take as the function + input. + @param options A dictionary containing `MGLStyleFunctionOption` values that + specify how a function is applied. + @return An `MGLCompositeStyleFunction` object with the given interpolation mode, + composite stops, attribute name, and options. + */ ++ (instancetype)functionWithInterpolationMode:(MGLInterpolationMode)interpolationMode stops:(NS_DICTIONARY_OF(id, NS_DICTIONARY_OF(id, MGLStyleValue *) *) *)stops attributeName:(NSString *)attributeName options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options; -// TODO: API docs -@property (nonatomic) MGLInterpolationMode interpolationMode; +#pragma mark Accessing the Parameters of a Composite Function -// TODO: API docs +/** + A string that specifies the feature attribute key whose value be used as the function + input. + */ @property (nonatomic, copy) NSString *attributeName; -// TODO: API docs +/** + A dictionary associating attribute values with style values. + + Each of the function’s stops is represented by one key-value pair in the + dictionary. Each key in the dictionary is an `NSNumber` object containing a + floating-point zoom level. Each value in the dictionary is an inner nested + dictionary. Each key in the nested dictionary is an object representing a feature + attribute key or interpolation stop. Each value in the nested dictionary is an + `MGLStyleValue` object containing the value to use when the function is given + the associated attribute key. An `MGLStyleFunction` object may not be used + recursively as a value inside the nested dictionary. + */ @property (nonatomic, copy) NS_DICTIONARY_OF(id, NS_DICTIONARY_OF(id, MGLStyleValue *) *) *stops; -// TODO: API docs +/** + An `MGLStyleValue` object containing the default value to use when there is + no input to provide to the function. + */ @property (nonatomic, nullable) MGLStyleValue *defaultValue; -// TODO: API docs -@property (nonatomic) CGFloat interpolationBase; - @end NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLStyleValue.mm b/platform/darwin/src/MGLStyleValue.mm index 1a8e6edeab..020dc27d6a 100644 --- a/platform/darwin/src/MGLStyleValue.mm +++ b/platform/darwin/src/MGLStyleValue.mm @@ -17,16 +17,16 @@ const MGLStyleFunctionOption MGLStyleFunctionOptionDefaultValue = @"MGLStyleFunc return [MGLCameraStyleFunction functionWithInterpolationMode:MGLInterpolationModeExponential stops:stops options:nil]; } -+ (instancetype)valueWithInterpolationMode:(MGLInterpolationMode)interpolationMode cameraStops:(NSDictionary *)stops options:(NSDictionary *)options { - return [MGLCameraStyleFunction functionWithInterpolationMode:interpolationMode stops:stops options:options]; ++ (instancetype)valueWithInterpolationMode:(MGLInterpolationMode)interpolationMode cameraStops:(NSDictionary *)cameraStops options:(NSDictionary *)options { + return [MGLCameraStyleFunction functionWithInterpolationMode:interpolationMode stops:cameraStops options:options]; } -+ (instancetype)valueWithInterpolationMode:(MGLInterpolationMode)interpolationMode sourceStops:(NSDictionary *)stops attributeName:(NSString *)attributeName options:(NSDictionary *)options { - return [MGLSourceStyleFunction functionWithInterpolationMode:interpolationMode stops:stops attributeName:attributeName options:options]; ++ (instancetype)valueWithInterpolationMode:(MGLInterpolationMode)interpolationMode sourceStops:(NSDictionary *)sourceStops attributeName:(NSString *)attributeName options:(NSDictionary *)options { + return [MGLSourceStyleFunction functionWithInterpolationMode:interpolationMode stops:sourceStops attributeName:attributeName options:options]; } -+ (instancetype)valueWithInterpolationMode:(MGLInterpolationMode)interpolationMode compositeStops:(NSDictionary *)stops attributeName:(NSString *)attributeName options:(NSDictionary *)options { - return [MGLCompositeStyleFunction functionWithInterpolationMode:interpolationMode stops:stops attributeName:attributeName options:options]; ++ (instancetype)valueWithInterpolationMode:(MGLInterpolationMode)interpolationMode compositeStops:(NSDictionary *)compositeStops attributeName:(NSString *)attributeName options:(NSDictionary *)options { + return [MGLCompositeStyleFunction functionWithInterpolationMode:interpolationMode stops:compositeStops attributeName:attributeName options:options]; } @end @@ -62,16 +62,63 @@ const MGLStyleFunctionOption MGLStyleFunctionOptionDefaultValue = @"MGLStyleFunc @end +@implementation MGLStyleFunction + ++ (instancetype)functionWithStops:(NSDictionary *)stops { + return [MGLCameraStyleFunction functionWithInterpolationMode:MGLInterpolationModeExponential stops:stops options:nil]; +} + ++ (instancetype)functionWithInterpolationBase:(CGFloat)interpolationBase stops:(NSDictionary *)stops { + return [MGLCameraStyleFunction functionWithInterpolationMode:MGLInterpolationModeExponential stops:stops options:@{MGLStyleFunctionOptionInterpolationBase: @(interpolationBase)}]; +} + +- (instancetype)init { + if (self = [super init]) { + self.interpolationBase = 1.0; + self.stops = @{}; + } + return self; +} + +- (instancetype)initWithInterpolationBase:(CGFloat)interpolationBase stops:(NSDictionary *)stops { + if (self = [super init]) { + self.interpolationBase = interpolationBase; + self.stops = stops; + } + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p, \ + stops = %@, \ + interpolationBase = %f>", + NSStringFromClass([self class]), (void *)self, + self.stops, + self.interpolationBase]; +} + +- (BOOL)isEqual:(MGLStyleFunction *)other { + return ([other isKindOfClass:[self class]] + && [other.stops isEqualToDictionary:self.stops] + && other.interpolationBase == self.interpolationBase); +} + +- (NSUInteger)hash { + return self.stops.hash + @(self.interpolationBase).hash; +} + +@end + @implementation MGLCameraStyleFunction -@synthesize interpolationMode = _interpolationMode; +@dynamic stops; + (instancetype)functionWithInterpolationMode:(MGLInterpolationMode)interpolationMode stops:(NSDictionary *)stops options:(NSDictionary *)options { return [[self alloc] initWithInterpolationMode:interpolationMode stops:stops options:options]; } -- (instancetype)init { - return [self initWithInterpolationMode:MGLInterpolationModeExponential stops:@{} options:nil]; +- (instancetype)initWithInterpolationBase:(CGFloat)interpolationBase stops:(NSDictionary *)stops { + return [self initWithInterpolationMode:MGLInterpolationModeExponential stops:stops options:@{MGLStyleFunctionOptionInterpolationBase: @(interpolationBase)}]; } - (instancetype)initWithInterpolationMode:(MGLInterpolationMode)interpolationMode stops:(NSDictionary *)stops options:(NSDictionary *)options { @@ -82,14 +129,13 @@ const MGLStyleFunctionOption MGLStyleFunctionOptionDefaultValue = @"MGLStyleFunc } if (self == [super init]) { - _interpolationMode = interpolationMode; - _stops = stops; - _interpolationBase = 1.0; + self.interpolationMode = interpolationMode; + self.stops = stops; if ([options.allKeys containsObject:MGLStyleFunctionOptionInterpolationBase]) { if ([options[MGLStyleFunctionOptionInterpolationBase] isKindOfClass:[NSNumber class]]) { NSNumber *value = (NSNumber *)options[MGLStyleFunctionOptionInterpolationBase]; - _interpolationBase = [value floatValue]; + self.interpolationBase = [value floatValue]; } else { [NSException raise:NSInvalidArgumentException format:@"Interpolation base must be an NSNumber that represents a CGFloat."]; } @@ -117,29 +163,28 @@ const MGLStyleFunctionOption MGLStyleFunctionOptionDefaultValue = @"MGLStyleFunc } - (NSUInteger)hash { - return self.interpolationMode + self.stops.hash + self.interpolationBase; + return @(self.interpolationMode).hash + self.stops.hash + @(self.interpolationBase).hash; } @end @implementation MGLSourceStyleFunction -@synthesize interpolationMode = _interpolationMode; +@dynamic stops; + (instancetype)functionWithInterpolationMode:(MGLInterpolationMode)interpolationMode stops:(NSDictionary *)stops attributeName:(NSString *)attributeName options:(NSDictionary *)options { return [[self alloc] initWithInterpolationMode:interpolationMode stops:stops attributeName:attributeName options:options]; } -- (instancetype)init { - return [self initWithInterpolationMode:MGLInterpolationModeExponential stops:nil attributeName:@"" options:nil]; +- (instancetype)initWithInterpolationBase:(CGFloat)interpolationBase stops:(NSDictionary *)stops { + return [self initWithInterpolationMode:MGLInterpolationModeExponential stops:stops attributeName:@"" options:@{MGLStyleFunctionOptionInterpolationBase: @(interpolationBase)}]; } - (instancetype)initWithInterpolationMode:(MGLInterpolationMode)interpolationMode stops:(NSDictionary *)stops attributeName:(NSString *)attributeName options:(NSDictionary *)options { if (self == [super init]) { - _interpolationMode = interpolationMode; - _stops = stops; + self.interpolationMode = interpolationMode; + self.stops = stops; _attributeName = attributeName; - _interpolationBase = 1.0; if ([options.allKeys containsObject:MGLStyleFunctionOptionDefaultValue]) { if ([options[MGLStyleFunctionOptionDefaultValue] isKindOfClass:[MGLStyleValue class]]) { @@ -153,7 +198,7 @@ const MGLStyleFunctionOption MGLStyleFunctionOptionDefaultValue = @"MGLStyleFunc if ([options.allKeys containsObject:MGLStyleFunctionOptionInterpolationBase]) { if ([options[MGLStyleFunctionOptionInterpolationBase] isKindOfClass:[NSNumber class]]) { NSNumber *value = (NSNumber *)options[MGLStyleFunctionOptionInterpolationBase]; - _interpolationBase = [value floatValue]; + self.interpolationBase = [value floatValue]; } else { [NSException raise:NSInvalidArgumentException format:@"Interpolation base must be an NSNumber that represents a CGFloat."]; } @@ -188,29 +233,28 @@ const MGLStyleFunctionOption MGLStyleFunctionOptionDefaultValue = @"MGLStyleFunc } - (NSUInteger)hash { - return self.interpolationMode + self.stops.hash + self.attributeName.hash + self.defaultValue.hash + self.interpolationBase; + return @(self.interpolationMode).hash + self.stops.hash + self.attributeName.hash + self.defaultValue.hash + @(self.interpolationBase).hash; } @end @implementation MGLCompositeStyleFunction -@synthesize interpolationMode = _interpolationMode; +@dynamic stops; + (instancetype)functionWithInterpolationMode:(MGLInterpolationMode)interpolationMode stops:(NSDictionary *)stops attributeName:(NSString *)attributeName options:(NSDictionary *)options { return [[self alloc] initWithInterpolationMode:interpolationMode stops:stops attributeName:attributeName options:options]; } -- (instancetype)init { - return [self initWithInterpolationMode:MGLInterpolationModeExponential stops:@{} attributeName:@"" options:nil]; +- (instancetype)initWithInterpolationBase:(CGFloat)interpolationBase stops:(NSDictionary *)stops { + return [self initWithInterpolationMode:MGLInterpolationModeExponential stops:stops attributeName:@"" options:@{MGLStyleFunctionOptionInterpolationBase: @(interpolationBase)}]; } - (instancetype)initWithInterpolationMode:(MGLInterpolationMode)interpolationMode stops:(NSDictionary *)stops attributeName:(NSString *)attributeName options:(NSDictionary *)options { if (self == [super init]) { - _interpolationMode = interpolationMode; - _stops = stops; + self.interpolationMode = interpolationMode; + self.stops = stops; _attributeName = attributeName; - _interpolationBase = 1.0; if ([options.allKeys containsObject:MGLStyleFunctionOptionDefaultValue]) { if ([options[MGLStyleFunctionOptionDefaultValue] isKindOfClass:[MGLStyleValue class]]) { @@ -224,7 +268,7 @@ const MGLStyleFunctionOption MGLStyleFunctionOptionDefaultValue = @"MGLStyleFunc if ([options.allKeys containsObject:MGLStyleFunctionOptionInterpolationBase]) { if ([options[MGLStyleFunctionOptionInterpolationBase] isKindOfClass:[NSNumber class]]) { NSNumber *value = (NSNumber *)options[MGLStyleFunctionOptionInterpolationBase]; - _interpolationBase = [value floatValue]; + self.interpolationBase = [value floatValue]; } else { [NSException raise:NSInvalidArgumentException format:@"Interpolation base must be an NSNumber that represents a CGFloat."]; } @@ -258,7 +302,7 @@ const MGLStyleFunctionOption MGLStyleFunctionOptionDefaultValue = @"MGLStyleFunc } - (NSUInteger)hash { - return self.interpolationMode + self.stops.hash + self.attributeName.hash + self.interpolationBase; + return @(self.interpolationMode).hash + self.stops.hash + self.attributeName.hash + @(self.interpolationBase).hash; } @end diff --git a/platform/darwin/src/MGLStyleValue_Private.h b/platform/darwin/src/MGLStyleValue_Private.h index b4354d7d16..03a8f242cf 100644 --- a/platform/darwin/src/MGLStyleValue_Private.h +++ b/platform/darwin/src/MGLStyleValue_Private.h @@ -60,6 +60,9 @@ public: // a deprecated MGLStyleValue method (that used to create an MGLStyleFunction) to create a function // for properties that are piecewise-constant (i.e. enum, bool, string) return toMBGLIntervalCameraFunction(cameraStyleFunction); + } else if ([value isMemberOfClass:[MGLStyleFunction class]]) { + MGLStyleFunction *styleFunction = (MGLStyleFunction *)value; + return toMBGLIntervalCameraFunction(styleFunction); } else if (value) { [NSException raise:@"MGLAbstractClassException" format: @"The style value %@ cannot be applied to the style. " @@ -81,6 +84,9 @@ public: if ([value isKindOfClass:[MGLStyleConstantValue class]]) { return toMBGLConstantValue((MGLStyleConstantValue *)value); + } else if ([value isMemberOfClass:[MGLStyleFunction class]]) { + MGLStyleFunction *styleFunction = (MGLStyleFunction *)value; + return toMBGLExponentialCameraFunction(styleFunction); } else if ([value isKindOfClass:[MGLCameraStyleFunction class]]) { MGLCameraStyleFunction *cameraStyleFunction = (MGLCameraStyleFunction *)value; switch (cameraStyleFunction.interpolationMode) { @@ -247,6 +253,9 @@ public: return {}; } return {}; + } else if ([value isMemberOfClass:[MGLStyleFunction class]]) { + MGLStyleFunction *styleFunction = (MGLStyleFunction *)value; + return toMBGLExponentialCameraFunction(styleFunction); } else if (value) { [NSException raise:@"MGLAbstractClassException" format: @"The style value %@ cannot be applied to the style. " @@ -308,9 +317,9 @@ private: // Private utilities for converting from mgl to mbgl values return mbglValue; } - mbgl::style::CameraFunction toMBGLExponentialCameraFunction(MGLCameraStyleFunction *cameraStyleFunction) { + mbgl::style::CameraFunction toMBGLExponentialCameraFunction(MGLStyleFunction *styleFunction) { __block std::map stops = {}; - [cameraStyleFunction.stops enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull zoomKey, MGLStyleValue * _Nonnull stopValue, BOOL * _Nonnull stop) { + [styleFunction.stops enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull zoomKey, MGLStyleValue * _Nonnull stopValue, BOOL * _Nonnull stop) { NSCAssert([stopValue isKindOfClass:[MGLStyleValue class]], @"Stops should be MGLStyleValues"); auto mbglStopValue = toPropertyValue(stopValue); NSCAssert(mbglStopValue.isConstant(), @"Stops must be constant"); @@ -318,15 +327,15 @@ private: // Private utilities for converting from mgl to mbgl values }]; // Camera function with Exponential stops - mbgl::style::ExponentialStops exponentialStops = {stops, (float)cameraStyleFunction.interpolationBase}; + mbgl::style::ExponentialStops exponentialStops = {stops, (float)styleFunction.interpolationBase}; mbgl::style::CameraFunction cameraFunction = {exponentialStops}; return cameraFunction; } - mbgl::style::CameraFunction toMBGLIntervalCameraFunction(MGLCameraStyleFunction *cameraStyleFunction) { + mbgl::style::CameraFunction toMBGLIntervalCameraFunction(MGLStyleFunction *styleFunction) { __block std::map stops = {}; - [cameraStyleFunction.stops enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull zoomKey, MGLStyleValue * _Nonnull stopValue, BOOL * _Nonnull stop) { + [styleFunction.stops enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull zoomKey, MGLStyleValue * _Nonnull stopValue, BOOL * _Nonnull stop) { NSCAssert([stopValue isKindOfClass:[MGLStyleValue class]], @"Stops should be MGLStyleValues"); auto mbglStopValue = toPropertyValue(stopValue); NSCAssert(mbglStopValue.isConstant(), @"Stops must be constant"); diff --git a/platform/darwin/test/MGLStyleValueTests.swift b/platform/darwin/test/MGLStyleValueTests.swift index 066232329f..afdc2ef9a3 100644 --- a/platform/darwin/test/MGLStyleValueTests.swift +++ b/platform/darwin/test/MGLStyleValueTests.swift @@ -38,6 +38,7 @@ extension MGLStyleValueTests { func testDeprecatedFunctions() { let shapeSource = MGLShapeSource(identifier: "test", shape: nil, options: nil) let symbolStyleLayer = MGLSymbolStyleLayer(identifier: "symbolLayer", source: shapeSource) + let circleStyleLayer = MGLCircleStyleLayer(identifier: "circleLayer", source: shapeSource) // deprecated function, stops with float values let iconHaloBlurStyleValue = MGLStyleValue(interpolationBase: 1.0, stops: [1: MGLStyleValue(rawValue: 0), @@ -63,6 +64,66 @@ extension MGLStyleValueTests { options: nil ) XCTAssertEqual(symbolStyleLayer.iconAllowsOverlap, expectedIconAllowsOverlapStyleValue) + + /// + // creating and using MGLStyleFunctions directly + /// + + let circleRadiusStops: [NSNumber: MGLStyleValue] = [ + 0: MGLStyleValue(rawValue: 10), + 20: MGLStyleValue(rawValue: 5) + ] + let circleRadiusFunction = MGLStyleFunction( + interpolationBase: 1.0, + stops: circleRadiusStops + ) + circleStyleLayer.circleRadius = circleRadiusFunction + let expectedCircleRadiusFunction = MGLStyleValue( + interpolationMode: .exponential, + cameraStops: + circleRadiusStops, + options: nil + ) + // setting a data driven property to an MGLStyleFunction should return an exponential camera function + XCTAssertEqual(circleStyleLayer.circleRadius, expectedCircleRadiusFunction) + + var circleTranslationOne = CGVector(dx: 100, dy: 0) + let circleTranslationValueOne = NSValue(bytes: &circleTranslationOne, objCType: "{CGVector=dd}") + var circleTranslationTwo = CGVector(dx: 0, dy: 0) + let circleTranslationValueTwo = NSValue(bytes: &circleTranslationTwo, objCType: "{CGVector=dd}") + + let circleTranslationStops: [NSNumber: MGLStyleValue] = [ + 0: MGLStyleValue(rawValue: circleTranslationValueOne), + 10: MGLStyleValue(rawValue: circleTranslationValueTwo) + ] + let circleTranslationFunction = MGLStyleFunction( + interpolationBase: 1.0, + stops: circleTranslationStops + ) + circleStyleLayer.circleTranslation = circleTranslationFunction + let expectedCircleTranslationFunction = MGLStyleValue( + interpolationMode: .exponential, + cameraStops: circleTranslationStops, + options: nil + ) + // setting a non-data driven, interpolatable property to an MGLStyleFunction should return an exponential camera function + XCTAssertEqual(circleStyleLayer.circleTranslation, expectedCircleTranslationFunction) + + let iconOptionalStops: [NSNumber: MGLStyleValue] = [ + 0: MGLStyleValue(rawValue: false), + 20: MGLStyleValue(rawValue: true) + ] + let iconOptionalFunction = MGLStyleFunction( + interpolationBase: 1.0, + stops: iconOptionalStops + ) + symbolStyleLayer.iconOptional = iconOptionalFunction + let expectedIconOptionalFunction = MGLStyleValue( + interpolationMode: .interval, + cameraStops: iconOptionalStops, + options: nil + ) + XCTAssertEqual(symbolStyleLayer.iconOptional, expectedIconOptionalFunction) } func testFunctionsWithNonDataDrivenProperties() { @@ -293,6 +354,15 @@ extension MGLStyleValueTests { } } + // get value back as base class + if let returnedCircleRadius = circleStyleLayer.circleRadius as? MGLStyleFunction { + if let returnedStops = returnedCircleRadius.stops as NSDictionary? as? [NSNumber: [NSNumber: MGLStyleValue]] { + let lhs: MGLStyleValue = returnedStops[0]!.values.first! + let rhs: MGLStyleValue = radiusCompositeExponentialOrIntervalStops[0]!.values.first! + XCTAssertEqual(lhs, rhs) + } + } + // data-driven, composite function with inner interval color stop values nested in outer camera stops let expectedCompositeIntervalValue = MGLStyleValue( interpolationMode: .interval, -- cgit v1.2.1