summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnand Thakker <anandthakker@users.noreply.github.com>2017-02-15 17:16:54 -0500
committerGitHub <noreply@github.com>2017-02-15 17:16:54 -0500
commit685f237d0286ef13aac52cbbab93fed82a0b31b8 (patch)
tree78116f66e4212abbac7ea49359cabcc4124dad70
parenteb8a1ef7aa43582202c8b385b9ee0665cc6c838c (diff)
downloadqtlocation-mapboxgl-685f237d0286ef13aac52cbbab93fed82a0b31b8.tar.gz
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.
-rw-r--r--include/mbgl/style/conversion/function.hpp2
m---------mapbox-gl-js0
-rw-r--r--platform/darwin/src/MGLConversion.h135
-rw-r--r--platform/darwin/src/MGLStyleValue_Private.h256
-rw-r--r--platform/darwin/test/MGLStyleValueTests.swift280
-rw-r--r--platform/ios/ios.xcodeproj/project.pbxproj6
-rw-r--r--platform/macos/macos.xcodeproj/project.pbxproj4
7 files changed, 397 insertions, 286 deletions
diff --git a/include/mbgl/style/conversion/function.hpp b/include/mbgl/style/conversion/function.hpp
index 1f7a3fe778..a5979e6799 100644
--- a/include/mbgl/style/conversion/function.hpp
+++ b/include/mbgl/style/conversion/function.hpp
@@ -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 {};
}
diff --git a/mapbox-gl-js b/mapbox-gl-js
-Subproject dee3911a7b5a1e8b2333d133a162af388673d1b
+Subproject abdd41a90f8d9f6985bcadf0d363d84149f6fa4
diff --git a/platform/darwin/src/MGLConversion.h b/platform/darwin/src/MGLConversion.h
new file mode 100644
index 0000000000..d51ebd775c
--- /dev/null
+++ b/platform/darwin/src/MGLConversion.h
@@ -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
+
diff --git a/platform/darwin/src/MGLStyleValue_Private.h b/platform/darwin/src/MGLStyleValue_Private.h
index 759c96c6bd..44d6f505ee 100644
--- a/platform/darwin/src/MGLStyleValue_Private.h
+++ b/platform/darwin/src/MGLStyleValue_Private.h
@@ -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 = {};
diff --git a/platform/darwin/test/MGLStyleValueTests.swift b/platform/darwin/test/MGLStyleValueTests.swift
index a8c4aaf71a..f965c31e40 100644
--- a/platform/darwin/test/MGLStyleValueTests.swift
+++ b/platform/darwin/test/MGLStyleValueTests.swift
@@ -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]
diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj
index db078d0274..d08c52fcb3 100644
--- a/platform/ios/ios.xcodeproj/project.pbxproj
+++ b/platform/ios/ios.xcodeproj/project.pbxproj
@@ -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 */,
diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj
index 7059897135..2619283712 100644
--- a/platform/macos/macos.xcodeproj/project.pbxproj
+++ b/platform/macos/macos.xcodeproj/project.pbxproj
@@ -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 */,