diff options
-rw-r--r-- | include/mbgl/style/conversion/function.hpp | 2 | ||||
m--------- | mapbox-gl-js | 0 | ||||
-rw-r--r-- | platform/darwin/src/MGLConversion.h | 135 | ||||
-rw-r--r-- | platform/darwin/src/MGLStyleValue_Private.h | 256 | ||||
-rw-r--r-- | platform/darwin/test/MGLStyleValueTests.swift | 280 | ||||
-rw-r--r-- | platform/ios/ios.xcodeproj/project.pbxproj | 6 | ||||
-rw-r--r-- | platform/macos/macos.xcodeproj/project.pbxproj | 4 |
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 */, |