Refactor: use conversion system to convert MGLStyleFunction to mbgl core types (#8026)

This leverages the work already happening in `mbgl::style::conversion` to convert style functions from style-spec definitions into `mbgl::style::{Camera,Source,Composite}Function`s.  In particular, this allows the conversions system to handle the differing typing requirements based on whether the output type of the style function is interpolatable or non-interpolatable.
This commit is contained in:
Anand Thakker
2017-02-15 17:16:54 -05:00
committed by GitHub
parent eb8a1ef7aa
commit 685f237d02
7 changed files with 398 additions and 287 deletions

View File

@@ -200,7 +200,7 @@ struct Converter<CameraFunction<T>> {
template <class T, class V>
Result<optional<T>> convertDefaultValue(const V& value) {
auto defaultValueValue = objectMember(value, "defaultValue");
auto defaultValueValue = objectMember(value, "default");
if (!defaultValueValue) {
return {};
}

View File

@@ -0,0 +1,135 @@
#import <Foundation/Foundation.h>
#include <mbgl/util/logging.hpp>
#include <mbgl/style/conversion.hpp>
#include <mbgl/util/feature.hpp>
#include <mbgl/util/optional.hpp>
NS_ASSUME_NONNULL_BEGIN
namespace mbgl {
namespace style {
namespace conversion {
/**
A minimal wrapper class conforming to the requirements for `objectMember(v, name)` (see mbgl/style/conversion.hpp)
This is necessary because using `NSObject*` as the value type in `optional<NSObject*>` causes problems for the ARC,
due to things like `optional(const value_type& __v)`
*/
class OptionalNSObjectValue {
public:
OptionalNSObjectValue(NSObject * _Nullable _value) : value(_value) {}
explicit operator bool() const {
return value;
}
NSObject * _Nullable operator*() {
NSCAssert(this, @"Expected non-null value.");
return value;
}
private:
NSObject * _Nullable value;
};
inline bool isUndefined(const id value) {
return !value || value == [NSNull null];
}
inline bool isArray(const id value) {
return [value isKindOfClass:[NSArray class]];
}
inline bool isObject(const id value) {
return [value isKindOfClass:[NSDictionary class]];
}
inline std::size_t arrayLength(const id value) {
NSCAssert([value isKindOfClass:[NSArray class]], @"Value must be an NSArray for getLength().");
NSArray *array = value;
auto length = [array count];
NSCAssert(length <= std::numeric_limits<size_t>::max(), @"Array length out of bounds.");
return length;
}
inline NSObject *arrayMember(const id value, std::size_t i) {
NSCAssert([value isKindOfClass:[NSArray class]], @"Value must be an NSArray for get(int).");
NSCAssert(i < NSUIntegerMax, @"Index must be less than NSUIntegerMax");
return [value objectAtIndex: i];
}
inline OptionalNSObjectValue objectMember(const id value, const char *key) {
NSCAssert([value isKindOfClass:[NSDictionary class]], @"Value must be an NSDictionary for get(string).");
NSObject *member = [value objectForKey: @(key)];
if (member && member != [NSNull null]) {
return { member };
} else {
return { nullptr };
}
}
// Not implemented (unneeded for MGLStyleFunction conversion):
// optional<Error> eachMember(const NSObject*, Fn&&)
inline bool _isBool(const id value) {
if (![value isKindOfClass:[NSNumber class]]) return false;
// char: 32-bit boolean
// BOOL: 64-bit boolean
NSNumber *number = value;
return ((strcmp([number objCType], @encode(char)) == 0) ||
(strcmp([number objCType], @encode(BOOL)) == 0));
}
inline bool _isNumber(const id value) {
return [value isKindOfClass:[NSNumber class]] && !_isBool(value);
}
inline bool _isString(const id value) {
return [value isKindOfClass:[NSString class]];
}
inline optional<bool> toBool(const id value) {
if (_isBool(value)) {
return ((NSNumber *)value).boolValue;
} else {
return {};
}
}
inline optional<float> toNumber(const id value) {
if (_isNumber(value)) {
return ((NSNumber *)value).floatValue;
} else {
return {};
}
}
inline optional<std::string> toString(const id value) {
if (_isString(value)) {
return std::string(static_cast<const char *>([value UTF8String]));
} else {
return {};
}
}
inline optional<mbgl::Value> toValue(const id value) {
if (isUndefined(value)) {
return {};
} else if (_isBool(value)) {
return { *toBool(value) };
} else if ( _isString(value)) {
return { *toString(value) };
} else if (_isNumber(value)) {
// Need to cast to a double here as the float is otherwise considered a bool...
return { static_cast<double>(*toNumber(value)) };
} else {
return {};
}
}
} // namespace conversion
} // namespace style
} // namespace mbgl
NS_ASSUME_NONNULL_END

View File

@@ -5,6 +5,10 @@
#import "NSValue+MGLStyleAttributeAdditions.h"
#import "MGLTypes.h"
#import "MGLConversion.h"
#include <mbgl/style/conversion/data_driven_property_value.hpp>
#include <mbgl/style/conversion.hpp>
#import <mbgl/util/enum.hpp>
#include <mbgl/style/data_driven_property_value.hpp>
@@ -117,157 +121,11 @@ public:
mbgl::style::DataDrivenPropertyValue<MBGLType> toDataDrivenPropertyValue(MGLStyleValue<ObjCType> *value) {
if ([value isKindOfClass:[MGLStyleConstantValue class]]) {
return toMBGLConstantValue((MGLStyleConstantValue<ObjCType> *)value);
} else if ([value isKindOfClass:[MGLCameraStyleFunction class]]) {
MGLCameraStyleFunction<ObjCType> *cameraStyleFunction = (MGLCameraStyleFunction<ObjCType> *)value;
switch (cameraStyleFunction.interpolationMode) {
case MGLInterpolationModeExponential:
return toMBGLExponentialCameraFunction(cameraStyleFunction);
break;
case MGLInterpolationModeInterval:
return toMBGLIntervalCameraFunction(cameraStyleFunction);
break;
default:
[NSException raise:NSInvalidArgumentException
format:@"A camera function must use either exponential or interval stops."];
return {};
}
} else if ([value isKindOfClass:[MGLSourceStyleFunction class]]) {
MGLSourceStyleFunction<ObjCType> *sourceStyleFunction = (MGLSourceStyleFunction<ObjCType> *)value;
switch (sourceStyleFunction.interpolationMode) {
case MGLInterpolationModeExponential:
return toMBGLExponentialSourceFunction(sourceStyleFunction);
break;
case MGLInterpolationModeInterval:
return toMBGLIntervalSourceFunction(sourceStyleFunction);
break;
case MGLInterpolationModeCategorical:
return toMBGLCategoricalSourceFunction(sourceStyleFunction);
break;
case MGLInterpolationModeIdentity:
return toMBGLIdentitySourceFunction(sourceStyleFunction);
break;
default:
[NSException raise:NSInvalidArgumentException
format:@"A camera function must use exponential, interval, categorical, or identity stops."];
return {};
}
} else if ([value isKindOfClass:[MGLCompositeStyleFunction class]]) {
MGLCompositeStyleFunction<ObjCType> *compositeStyleFunction = (MGLCompositeStyleFunction<ObjCType> *)value;
switch (compositeStyleFunction.interpolationMode) {
case MGLInterpolationModeExponential: {
__block std::map<float, std::map<float, MBGLType>> outerStops = {};
[compositeStyleFunction.stops enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull zoomKey, NSDictionary * _Nonnull stopValue, BOOL * _Nonnull stop) {
std::map<float, MBGLType> stops = {};
for (NSNumber *key in stopValue.allKeys) {
NSCAssert([key isKindOfClass:[NSNumber class]], @"Stop keys should be NSNumbers");
MGLStyleValue<ObjCType> *value = stopValue[key];
NSCAssert([value isKindOfClass:[MGLStyleValue class]], @"Stops should be MGLStyleValues");
auto mbglStopValue = toPropertyValue(value);
NSCAssert(mbglStopValue.isConstant(), @"Stops must be constant");
stops[key.floatValue] = mbglStopValue.asConstant();
}
outerStops[zoomKey.floatValue] = stops;
}];
mbgl::style::CompositeFunction<MBGLType> compositeFunction {
compositeStyleFunction.attributeName.UTF8String,
mbgl::style::CompositeExponentialStops<MBGLType> { outerStops, (float)compositeStyleFunction.interpolationBase }
};
if (compositeStyleFunction.defaultValue) {
NSCAssert([compositeStyleFunction.defaultValue isKindOfClass:[MGLStyleConstantValue class]], @"Default value must be constant");
MBGLType mbglValue;
id mglValue = [(MGLStyleConstantValue<ObjCType> *)compositeStyleFunction.defaultValue rawValue];
getMBGLValue(mglValue, mbglValue);
compositeFunction.defaultValue = mbglValue;
}
return compositeFunction;
}
break;
case MGLInterpolationModeInterval: {
__block std::map<float, std::map<float, MBGLType>> outerStops = {};
[compositeStyleFunction.stops enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull zoomKey, NSDictionary * _Nonnull stopValue, BOOL * _Nonnull stop) {
std::map<float, MBGLType> stops = {};
for (NSNumber *key in stopValue.allKeys) {
NSCAssert([key isKindOfClass:[NSNumber class]], @"Stop keys should be NSNumbers");
MGLStyleValue<ObjCType> *value = stopValue[key];
NSCAssert([value isKindOfClass:[MGLStyleValue class]], @"Stops should be MGLStyleValues");
auto mbglStopValue = toPropertyValue(value);
NSCAssert(mbglStopValue.isConstant(), @"Stops must be constant");
stops[key.floatValue] = mbglStopValue.asConstant();
}
outerStops[zoomKey.floatValue] = stops;
}];
mbgl::style::CompositeFunction<MBGLType> compositeFunction {
compositeStyleFunction.attributeName.UTF8String,
mbgl::style::CompositeIntervalStops<MBGLType> { outerStops }
};
if (compositeStyleFunction.defaultValue) {
NSCAssert([compositeStyleFunction.defaultValue isKindOfClass:[MGLStyleConstantValue class]], @"Default value must be constant");
MBGLType mbglValue;
id mglValue = [(MGLStyleConstantValue<ObjCType> *)compositeStyleFunction.defaultValue rawValue];
getMBGLValue(mglValue, mbglValue);
compositeFunction.defaultValue = mbglValue;
}
return compositeFunction;
}
break;
case MGLInterpolationModeCategorical: {
__block std::map<float, std::map<mbgl::style::CategoricalValue, MBGLType>> outerStops = {};
[compositeStyleFunction.stops enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull zoomKey, NSDictionary * _Nonnull stopValue, BOOL * _Nonnull stop) {
__block std::map<mbgl::style::CategoricalValue, MBGLType> stops = {};
[stopValue enumerateKeysAndObjectsUsingBlock:^(id _Nonnull categoryKey, MGLStyleValue<ObjCType> * _Nonnull innerValue, BOOL * _Nonnull stop) {
NSCAssert([innerValue isKindOfClass:[MGLStyleValue class]], @"Stops should be MGLStyleValues");
auto mbglStopValue = toPropertyValue(innerValue);
NSCAssert(mbglStopValue.isConstant(), @"Stops must be constant");
if ([categoryKey isKindOfClass:[NSString class]]) {
const std::string& convertedValueKey = [((NSString *)categoryKey) UTF8String];
stops[mbgl::style::CategoricalValue(convertedValueKey)] = mbglStopValue.asConstant();
} else if ([categoryKey isKindOfClass:[NSNumber class]]) {
NSNumber *key = (NSNumber *)categoryKey;
if ((strcmp([key objCType], @encode(char)) == 0) ||
(strcmp([key objCType], @encode(BOOL)) == 0)) {
stops[mbgl::style::CategoricalValue((bool)[key boolValue])] = mbglStopValue.asConstant();
} else if (strcmp([key objCType], @encode(double)) == 0 ||
strcmp([key objCType], @encode(float)) == 0) {
NSCAssert(mbglStopValue.isConstant(), @"Categorical stop keys must be strings, booleans, or integers");
} else if ([key compare:@(0)] == NSOrderedDescending ||
[key compare:@(0)] == NSOrderedSame ||
[key compare:@(0)] == NSOrderedAscending) {
stops[mbgl::style::CategoricalValue((int64_t)[key integerValue])] = mbglStopValue.asConstant();
}
}
}];
outerStops[zoomKey.floatValue] = stops;
}];
mbgl::style::CompositeFunction<MBGLType> compositeFunction {
compositeStyleFunction.attributeName.UTF8String,
mbgl::style::CompositeCategoricalStops<MBGLType> { outerStops }
};
if (compositeStyleFunction.defaultValue) {
NSCAssert([compositeStyleFunction.defaultValue isKindOfClass:[MGLStyleConstantValue class]], @"Default value must be constant");
MBGLType mbglValue;
id mglValue = [(MGLStyleConstantValue<ObjCType> *)compositeStyleFunction.defaultValue rawValue];
getMBGLValue(mglValue, mbglValue);
compositeFunction.defaultValue = mbglValue;
}
return compositeFunction;
}
break;
default:
[NSException raise:NSInvalidArgumentException
format:@"A composite function must use exponential, interval, or categorical stops."];
return {};
}
return {};
} else if ([value isMemberOfClass:[MGLStyleFunction class]]) {
MGLStyleFunction<ObjCType> *styleFunction = (MGLStyleFunction<ObjCType> *)value;
return toMBGLExponentialCameraFunction(styleFunction);
} else if (value) {
[NSException raise:@"MGLAbstractClassException" format:
@"The style value %@ cannot be applied to the style. "
@"Make sure the style value was created as a member of a concrete subclass of MGLStyleValue.",
NSStringFromClass([value class])];
return {};
} else if ([value isKindOfClass:[MGLStyleFunction class]]) {
auto rawValue = toRawStyleSpecValue((MGLStyleFunction<ObjCType> *) value);
auto result = mbgl::style::conversion::convert<mbgl::style::DataDrivenPropertyValue<MBGLType>>(rawValue);
NSCAssert(result, @(result.error().message.c_str()));
return *result;
} else {
return {};
}
@@ -322,6 +180,102 @@ private: // Private utilities for converting from mgl to mbgl values
getMBGLValue(value.rawValue, mbglValue);
return mbglValue;
}
NSObject* toRawStyleSpecValue(MGLColor *color) {
return @(color.mgl_color.stringify().c_str());
}
NSObject* toRawStyleSpecValue(NSObject *rawMGLValue) {
if ([rawMGLValue isKindOfClass:[NSValue class]]) {
const auto rawNSValue = (NSValue *)rawMGLValue;
if (strcmp([rawNSValue objCType], @encode(CGVector)) == 0) {
// offset [x, y]
std::array<float, 2> mglValue = rawNSValue.mgl_offsetArrayValue;
return [NSArray arrayWithObjects:@(mglValue[0]), @(mglValue[1]), nil];
}
}
// noop pass-through plain NSObject-based items
return rawMGLValue;
}
NSObject* toRawStyleSpecValue(MGLStyleFunction<ObjCType>* styleFunction) {
NSMutableDictionary * rawFunction = [NSMutableDictionary new];
// interpolationMode => type
switch (styleFunction.interpolationMode) {
case MGLInterpolationModeExponential:
rawFunction[@"type"] = @"exponential";
break;
case MGLInterpolationModeInterval:
rawFunction[@"type"] = @"interval";
break;
case MGLInterpolationModeCategorical:
rawFunction[@"type"] = @"categorical";
break;
case MGLInterpolationModeIdentity:
rawFunction[@"type"] = @"identity";
break;
}
// interpolationBase => base
if (styleFunction.interpolationBase) {
rawFunction[@"base"] = @(styleFunction.interpolationBase);
}
// stops and default value
if ([styleFunction isKindOfClass:[MGLCameraStyleFunction class]]) {
// zoom-only function (no default value)
__block NSMutableArray *stops = [[NSMutableArray alloc] init];
[styleFunction.stops enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull zoomKey, MGLStyleConstantValue<ObjCType> * _Nonnull outputValue, BOOL * _Nonnull stop) {
NSArray *rawStop = @[zoomKey, toRawStyleSpecValue([outputValue rawValue])];
[stops addObject:rawStop];
}];
rawFunction[@"stops"] = stops;
} else if ([styleFunction isKindOfClass:[MGLSourceStyleFunction class]]) {
auto sourceStyleFunction = (MGLSourceStyleFunction<ObjCType> *)styleFunction;
rawFunction[@"property"] = sourceStyleFunction.attributeName;
// property-only function
__block NSMutableArray *stops = [[NSMutableArray alloc] init];
[styleFunction.stops enumerateKeysAndObjectsUsingBlock:^(NSObject * _Nonnull propertyKey, MGLStyleConstantValue<ObjCType> * _Nonnull outputValue, BOOL * _Nonnull stop) {
NSArray *rawStop = @[propertyKey, toRawStyleSpecValue([outputValue rawValue])];
[stops addObject:rawStop];
}];
rawFunction[@"stops"] = stops;
// defaultValue => default
if (sourceStyleFunction.defaultValue) {
NSCAssert([sourceStyleFunction.defaultValue isKindOfClass:[MGLStyleConstantValue class]], @"Default value must be constant");
rawFunction[@"default"] = toRawStyleSpecValue([(MGLStyleConstantValue<ObjCType> *)sourceStyleFunction.defaultValue rawValue]);
}
} else if ([styleFunction isKindOfClass:[MGLCompositeStyleFunction class]]) {
// zoom-and-property function
auto compositeStyleFunction = (MGLCompositeStyleFunction<ObjCType> *)styleFunction;
rawFunction[@"property"] = compositeStyleFunction.attributeName;
__block NSMutableArray *stops = [[NSMutableArray alloc] init];
[compositeStyleFunction.stops enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull zoomKey, NSDictionary * _Nonnull stopValue, BOOL * _Nonnull stop) {
for (NSObject *valueKey in stopValue.allKeys) {
NSDictionary *stopKey = @{
@"zoom": zoomKey,
@"value": valueKey
};
MGLStyleConstantValue<ObjCType> *outputValue = stopValue[valueKey];
NSCAssert([outputValue isKindOfClass:[MGLStyleConstantValue<ObjCType> class]], @"Stop outputs should be MGLStyleConstantValues");
NSArray *rawStop = @[stopKey, toRawStyleSpecValue([outputValue rawValue])];
[stops addObject:rawStop];
}
}];
rawFunction[@"stops"] = stops;
// defaultValue => default
if (compositeStyleFunction.defaultValue) {
NSCAssert([compositeStyleFunction.defaultValue isKindOfClass:[MGLStyleConstantValue class]], @"Default value must be constant");
rawFunction[@"default"] = toRawStyleSpecValue([(MGLStyleConstantValue<ObjCType> *)compositeStyleFunction.defaultValue rawValue]);
}
}
return rawFunction;
}
mbgl::style::CameraFunction<MBGLType> toMBGLExponentialCameraFunction(MGLStyleFunction<ObjCType> *styleFunction) {
__block std::map<float, MBGLType> stops = {};

View File

@@ -1,8 +1,88 @@
import XCTest
import Mapbox
#if os(iOS) || os(watchOS) || os(tvOS)
typealias MGLColor = UIColor
#elseif os(macOS)
typealias MGLColor = NSColor
#endif
extension MGLStyleValueTests {
func assertColorsEqualWithAccuracy(_ actual: MGLColor, _ expected: MGLColor, accuracy: Float = 1/255) {
var actualComponents : [CGFloat] = [0, 0, 0, 0]
var expectedComponents : [CGFloat] = [0, 0, 0, 0]
actual.getRed(&(actualComponents[0]), green: &(actualComponents[1]), blue: &(actualComponents[2]), alpha: &(actualComponents[3]))
expected.getRed(&(expectedComponents[0]), green: &(expectedComponents[1]), blue: &(expectedComponents[2]), alpha: &(expectedComponents[3]))
for (ac, ec) in zip(actualComponents, expectedComponents) {
XCTAssertEqualWithAccuracy(Float(ac), Float(ec), accuracy: accuracy)
}
}
func assertColorValuesEqual(_ actual: MGLStyleValue<MGLColor>, _ expected: MGLStyleValue<MGLColor>) {
guard type(of: actual) == type(of: expected) else {
XCTFail("Expected \(type(of: expected)), but found \(type(of: actual)) instead.")
return
}
if let actualConstant = actual as? MGLStyleConstantValue<MGLColor> {
assertColorsEqualWithAccuracy(actualConstant.rawValue, (expected as! MGLStyleConstantValue<MGLColor>).rawValue)
} else if let actualFunction = actual as? MGLStyleFunction<MGLColor>,
let expectedFunction = expected as? MGLStyleFunction<MGLColor> {
// unless we have stops, there's no need for a custom comparison - default to plain == assertion
guard let actualStops = actualFunction.stops, let expectedStops = expectedFunction.stops else {
XCTAssertEqual(actualFunction, expectedFunction)
return
}
guard expectedStops is [String: Any] || expectedStops is [Float:Any] else {
XCTFail("Stop levels must be String or Float.")
return
}
XCTAssertEqual(actualFunction.interpolationBase, expectedFunction.interpolationBase)
XCTAssertEqual(actualFunction.interpolationMode, expectedFunction.interpolationMode)
if let actualFunction = actualFunction as? MGLSourceStyleFunction<MGLColor>,
let expectedFunction = expectedFunction as? MGLSourceStyleFunction<MGLColor> {
XCTAssertEqual(actualFunction.defaultValue, expectedFunction.defaultValue)
} else if let actualFunction = actualFunction as? MGLCompositeStyleFunction<MGLColor>,
let expectedFunction = expectedFunction as? MGLCompositeStyleFunction<MGLColor> {
XCTAssertEqual(actualFunction.defaultValue, expectedFunction.defaultValue)
}
func assertStopEqual (_ actualValue: Any?, _ expectedValue: Any?) {
guard type(of: actualValue) == type(of: expectedValue) else {
XCTFail("Expected stop value of type \(type(of: expectedValue)), but found \(type(of: actualValue)) instead.")
return
}
if let actualValue = actualValue as? MGLStyleConstantValue<MGLColor>,
let expectedValue = expectedValue as? MGLStyleConstantValue<MGLColor> {
assertColorsEqualWithAccuracy(actualValue.rawValue, expectedValue.rawValue)
} else if let actualValue = actualValue as? MGLStyleConstantValue<AnyObject>,
let expectedValue = expectedValue as? MGLStyleConstantValue<AnyObject> {
XCTAssertEqual(actualValue, expectedValue)
} else {
XCTFail("Unsupported stop value type \(type(of: actualValue)).")
}
}
XCTAssertEqual(actualStops.count, expectedStops.count)
if let actualStops = actualStops as? [String:Any], let expectedStops = expectedStops as? [String: Any] {
for (key, value) in actualStops {
assertStopEqual(value, expectedStops[key])
}
} else if let actualStops = actualStops as? [Float:Any], let expectedStops = expectedStops as? [Float:Any] {
for (key, value) in actualStops {
assertStopEqual(value, expectedStops[key])
}
} else {
XCTFail("Expected stops of type \(type(of: Array(expectedStops.keys)).Index.self), but found \(type(of: Array(actualStops.keys)).Index.self) instead.")
return
}
} else {
XCTFail("MGLStyleValue<MGLColor> must be either a constant or a style function.")
}
}
func testConstantValues() {
let shapeSource = MGLShapeSource(identifier: "source", shape: nil, options: nil)
@@ -44,7 +124,7 @@ extension MGLStyleValueTests {
var circleTranslationTwo = CGVector(dx: 0, dy: 0)
let circleTranslationValueTwo = NSValue(bytes: &circleTranslationTwo, objCType: "{CGVector=dd}")
let circleTranslationStops = [
let circleTranslationStops : [Float:MGLStyleValue<NSValue>] = [
0: MGLStyleValue<NSValue>(rawValue: circleTranslationValueOne),
10: MGLStyleValue<NSValue>(rawValue: circleTranslationValueTwo)
]
@@ -59,7 +139,7 @@ extension MGLStyleValueTests {
XCTAssertEqual(circleStyleLayer.circleTranslation, expectedCircleTranslationValue)
// non-data-driven (enumeration property value), camera function with MGLCircleScaleAlignment enum (NSValue) stop values
let scaleAlignmentStops = [
let scaleAlignmentStops : [Float:MGLStyleValue<NSValue>] = [
0: MGLStyleValue(rawValue: NSValue(mglCircleScaleAlignment: .map)),
10: MGLStyleValue(rawValue: NSValue(mglCircleScaleAlignment: .viewport))
]
@@ -75,135 +155,67 @@ extension MGLStyleValueTests {
func testFunctionsWithDataDrivenProperties() {
let shapeSource = MGLShapeSource(identifier: "test", shape: nil, options: nil)
let circleStyleLayer = MGLCircleStyleLayer(identifier: "circleLayer", source: shapeSource)
#if os(iOS) || os(watchOS) || os(tvOS)
// data-driven, camera function with exponential color stop values
let redGreenStops = [
0: MGLStyleValue<UIColor>(rawValue: .red),
10: MGLStyleValue<UIColor>(rawValue: .red),
15: MGLStyleValue<UIColor>(rawValue: .green)
]
let expectedCircleColorValue = MGLStyleValue<UIColor>(
interpolationMode: .exponential,
cameraStops: redGreenStops,
options: [.interpolationBase: 10.0]
)
circleStyleLayer.circleColor = expectedCircleColorValue
XCTAssertEqual(circleStyleLayer.circleColor, expectedCircleColorValue)
// data-driven, source function with categorical color stop values with string attribute keys
let redOnlyStops = [
"red": MGLStyleValue<UIColor>(rawValue: .red)
]
let expectedRedCategoricalValue = MGLStyleValue<UIColor>(
interpolationMode: .categorical,
sourceStops: redOnlyStops,
attributeName: "red",
options: [.defaultValue: MGLStyleValue<UIColor>(rawValue: .cyan)]
)
circleStyleLayer.circleColor = expectedRedCategoricalValue
XCTAssertEqual(circleStyleLayer.circleColor, expectedRedCategoricalValue)
// data-driven, source function with categorical color stop values with integer attribute keys
let greenOrangeStops = [
0: MGLStyleValue<UIColor>(rawValue: .green),
100: MGLStyleValue<UIColor>(rawValue: .orange)
]
let expectedGreenOrangeCategoricalValue = MGLStyleValue<UIColor>(
interpolationMode: .categorical,
sourceStops: greenOrangeStops,
attributeName: "temp",
options: [.defaultValue: MGLStyleValue<UIColor>(rawValue: .red)]
)
circleStyleLayer.circleColor = expectedGreenOrangeCategoricalValue
XCTAssertEqual(circleStyleLayer.circleColor, expectedGreenOrangeCategoricalValue)
// data-driven, source function with exponential color stop values
let expectedRedGreenSourceExponentialValue = MGLStyleValue<UIColor>(
interpolationMode: .exponential,
sourceStops: redGreenStops,
attributeName: "temp",
options: nil
)
circleStyleLayer.circleColor = expectedRedGreenSourceExponentialValue
XCTAssertEqual(circleStyleLayer.circleColor, expectedRedGreenSourceExponentialValue)
// data-driven, identity source function
let expectedSourceIdentityValue = MGLStyleValue<UIColor>(
interpolationMode: .identity,
sourceStops: nil,
attributeName: "size",
options: [.defaultValue: MGLStyleValue<UIColor>(rawValue: .green)]
)
circleStyleLayer.circleColor = expectedSourceIdentityValue
XCTAssertEqual(circleStyleLayer.circleColor, expectedSourceIdentityValue)
#elseif os(macOS)
// data-driven, camera function with exponential color stop values
let redGreenStops = [
0: MGLStyleValue<NSColor>(rawValue: .red),
10: MGLStyleValue<NSColor>(rawValue: .red),
15: MGLStyleValue<NSColor>(rawValue: .green)
]
let expectedCircleColorValue = MGLStyleValue<NSColor>(
interpolationMode: .exponential,
cameraStops: redGreenStops,
options: [.interpolationBase: 10.0]
)
circleStyleLayer.circleColor = expectedCircleColorValue
XCTAssertEqual(circleStyleLayer.circleColor, expectedCircleColorValue)
// data-driven, source function with categorical color stop values with string typed keys
let redOnlyStops = [
"red": MGLStyleValue<NSColor>(rawValue: .red)
]
let expectedRedCategoricalValue = MGLStyleValue<NSColor>(
interpolationMode: .categorical,
sourceStops: redOnlyStops,
attributeName: "red",
options: [.defaultValue: MGLStyleValue<NSColor>(rawValue: .cyan)]
)
circleStyleLayer.circleColor = expectedRedCategoricalValue
XCTAssertEqual(circleStyleLayer.circleColor, expectedRedCategoricalValue)
// data-driven, source function with categorical color stop values with integer attribute keys
let greenOrangeStops = [
0: MGLStyleValue<NSColor>(rawValue: .green),
100: MGLStyleValue<NSColor>(rawValue: .orange)
]
let expectedGreenOrangeCategoricalValue = MGLStyleValue<NSColor>(
interpolationMode: .categorical,
sourceStops: greenOrangeStops,
attributeName: "temp",
options: [.defaultValue: MGLStyleValue<NSColor>(rawValue: .red)]
)
circleStyleLayer.circleColor = expectedGreenOrangeCategoricalValue
XCTAssertEqual(circleStyleLayer.circleColor, expectedGreenOrangeCategoricalValue)
// data-driven, source function with exponential color stop values
let expectedRedGreenSourceExponentialValue = MGLStyleValue<NSColor>(
interpolationMode: .exponential,
sourceStops: redGreenStops,
attributeName: "temp",
options: nil
)
circleStyleLayer.circleColor = expectedRedGreenSourceExponentialValue
XCTAssertEqual(circleStyleLayer.circleColor, expectedRedGreenSourceExponentialValue)
// data-driven, identity source function
let expectedSourceIdentityValue = MGLStyleValue<NSColor>(
interpolationMode: .identity,
sourceStops: nil,
attributeName: "size",
options: nil
)
circleStyleLayer.circleColor = expectedSourceIdentityValue
XCTAssertEqual(circleStyleLayer.circleColor, expectedSourceIdentityValue)
#endif
// data-driven, camera function with exponential color stop values
let redGreenStops : [Float:MGLStyleValue<MGLColor>] = [
0: MGLStyleValue<MGLColor>(rawValue: .red),
10: MGLStyleValue<MGLColor>(rawValue: .red),
15: MGLStyleValue<MGLColor>(rawValue: .green)
]
let expectedCircleColorValue = MGLStyleValue<MGLColor>(
interpolationMode: .exponential,
cameraStops: redGreenStops,
options: [.interpolationBase: 10.0]
)
circleStyleLayer.circleColor = expectedCircleColorValue
assertColorValuesEqual(circleStyleLayer.circleColor as! MGLStyleFunction<MGLColor>, expectedCircleColorValue as! MGLStyleFunction<MGLColor>)
// data-driven, source function with categorical color stop values with string attribute keys
let redOnlyStops = [
"red": MGLStyleValue<MGLColor>(rawValue: .red)
]
let expectedRedCategoricalValue = MGLStyleValue<MGLColor>(
interpolationMode: .categorical,
sourceStops: redOnlyStops,
attributeName: "red",
options: [.defaultValue: MGLStyleValue<MGLColor>(rawValue: .cyan)]
)
circleStyleLayer.circleColor = expectedRedCategoricalValue
assertColorValuesEqual(circleStyleLayer.circleColor, expectedRedCategoricalValue)
// data-driven, source function with categorical color stop values with integer attribute keys
let greenOrangeStops : [Float:MGLStyleValue<MGLColor>] = [
0: MGLStyleValue<MGLColor>(rawValue: .green),
100: MGLStyleValue<MGLColor>(rawValue: .orange)
]
let expectedGreenOrangeCategoricalValue = MGLStyleValue<MGLColor>(
interpolationMode: .categorical,
sourceStops: greenOrangeStops,
attributeName: "temp",
options: [.defaultValue: MGLStyleValue<MGLColor>(rawValue: .red)]
)
circleStyleLayer.circleColor = expectedGreenOrangeCategoricalValue
assertColorValuesEqual(circleStyleLayer.circleColor, expectedGreenOrangeCategoricalValue)
// data-driven, source function with exponential color stop values
let expectedRedGreenSourceExponentialValue = MGLStyleValue<MGLColor>(
interpolationMode: .exponential,
sourceStops: redGreenStops,
attributeName: "temp",
options: nil
)
circleStyleLayer.circleColor = expectedRedGreenSourceExponentialValue
assertColorValuesEqual(circleStyleLayer.circleColor, expectedRedGreenSourceExponentialValue)
// data-driven, identity source function
let expectedSourceIdentityValue = MGLStyleValue<MGLColor>(
interpolationMode: .identity,
sourceStops: nil,
attributeName: "size",
options: [.defaultValue: MGLStyleValue<MGLColor>(rawValue: .green)]
)
circleStyleLayer.circleColor = expectedSourceIdentityValue
assertColorValuesEqual(circleStyleLayer.circleColor, expectedSourceIdentityValue)
// data-driven, source function with categorical color stop values with boolean attribute keys
let booleanCategoricalStops = [
@@ -223,7 +235,7 @@ extension MGLStyleValueTests {
let smallRadius = MGLStyleValue<NSNumber>(rawValue: 5)
let mediumRadius = MGLStyleValue<NSNumber>(rawValue: 10)
let largeRadius = MGLStyleValue<NSNumber>(rawValue: 20)
let radiusCompositeCategoricalStops: [NSNumber: [String: MGLStyleValue<NSNumber>]] = [
let radiusCompositeCategoricalStops: [Float: [String: MGLStyleValue<NSNumber>]] = [
0: ["green": smallRadius],
10: ["green": smallRadius],
15: ["green": largeRadius],
@@ -240,7 +252,7 @@ extension MGLStyleValueTests {
XCTAssertEqual(circleStyleLayer.circleRadius, expectedCompositeCategoricalValue)
// data-driven, composite function with inner exponential color stop values nested in outer camera stops
let radiusCompositeExponentialOrIntervalStops: [NSNumber: [NSNumber: MGLStyleValue<NSNumber>]] = [
let radiusCompositeExponentialOrIntervalStops: [Float: [Float: MGLStyleValue<NSNumber>]] = [
0: [0: smallRadius],
10: [200: smallRadius],
20: [200: largeRadius]

View File

@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
1753ED421E53CE6F00A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */; };
1753ED431E53CE6F00A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */; };
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 */; };
@@ -518,6 +520,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1753ED411E53CE6F00A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = "<group>"; };
20DABE861DF78148007AC5FF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Foundation.strings"; sourceTree = "<group>"; };
20DABE881DF78148007AC5FF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
20DABE8A1DF78149007AC5FF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Root.strings"; sourceTree = "<group>"; };
@@ -947,6 +950,7 @@
35599DA21D4682B60048254D /* Styling */ = {
isa = PBXGroup;
children = (
1753ED411E53CE6F00A9FD90 /* MGLConversion.h */,
35599DB81D46AD7F0048254D /* Categories */,
353933F01D3FB6BA003F57D7 /* Layers */,
35136D491D4277EA00C20EFD /* Sources */,
@@ -1574,6 +1578,7 @@
DAD1656C1CF41981001FF4B9 /* MGLFeature.h in Headers */,
40EDA1C01CFE0E0200D9EA68 /* MGLAnnotationContainerView.h in Headers */,
DA88484F1CBAFB9800AB86E3 /* MGLAnnotationImage_Private.h in Headers */,
1753ED421E53CE6F00A9FD90 /* MGLConversion.h in Headers */,
DA8847F21CBAFA5100AB86E3 /* MGLMapCamera.h in Headers */,
3538AA1D1D542239008EC33D /* MGLForegroundStyleLayer.h in Headers */,
DA8847F51CBAFA5100AB86E3 /* MGLOfflineRegion.h in Headers */,
@@ -1659,6 +1664,7 @@
DAF0D8141DFE0EC500B28378 /* MGLVectorSource_Private.h in Headers */,
DABFB8731CBE9A9900D62B32 /* Mapbox.h in Headers */,
357FE2DE1E02D2B20068B753 /* NSCoder+MGLAdditions.h in Headers */,
1753ED431E53CE6F00A9FD90 /* MGLConversion.h in Headers */,
354B83971D2E873E005D9406 /* MGLUserLocationAnnotationView.h in Headers */,
DAF0D8111DFE0EA000B28378 /* MGLRasterSource_Private.h in Headers */,
DABFB86B1CBE99E500D62B32 /* MGLTilePyramidOfflineRegion.h in Headers */,

View File

@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
1753ED401E53CE6100A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED3F1E53CE5200A9FD90 /* MGLConversion.h */; };
30E5781B1DAA857E0050F07E /* NSImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578141DAA7D920050F07E /* NSImage+MGLAdditions.h */; };
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 */; };
@@ -258,6 +259,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1753ED3F1E53CE5200A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = "<group>"; };
30E578141DAA7D920050F07E /* NSImage+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSImage+MGLAdditions.h"; path = "src/NSImage+MGLAdditions.h"; 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>"; };
@@ -613,6 +615,7 @@
3527429B1D4C259500A1ECE6 /* Styling */ = {
isa = PBXGroup;
children = (
1753ED3F1E53CE5200A9FD90 /* MGLConversion.h */,
352742791D4C235C00A1ECE6 /* Categories */,
35136D471D42295400C20EFD /* Layers */,
3527427E1D4C242B00A1ECE6 /* Sources */,
@@ -1007,6 +1010,7 @@
3508EC641D749D39009B0EE4 /* NSExpression+MGLAdditions.h in Headers */,
DAE6C38D1CC31E2A00DB3429 /* MGLOfflineRegion_Private.h in Headers */,
DA7DC9831DED647F0027472F /* MGLRasterSource_Private.h in Headers */,
1753ED401E53CE6100A9FD90 /* MGLConversion.h in Headers */,
408AA8651DAEEE3400022900 /* MGLPolygon+MGLAdditions.h in Headers */,
DA8F259C1D51CB000010E6B5 /* MGLStyleValue_Private.h in Headers */,
DAE6C35B1CC31E0400DB3429 /* MGLAnnotation.h in Headers */,