Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Convert NSColor/UIColor expressions to CSS color string values #8025

Merged
merged 1 commit into from
Feb 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions platform/darwin/src/MGLFeature.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,21 @@ NS_ASSUME_NONNULL_BEGIN
`MGLVectorStyleLayer.predicate` to an `NSPredicate` with the format
`importance > 50`.

You can also configure some attributes of an `MGLSymbolStyleLayer` object to
include the value of an attribute in this dictionary whenever it renders this
feature. For example, to label features in an `MGLShapeSource` object by their
names, you can assign a `name` attribute to each of the source’s features, then
set `MGLSymbolStyleLayer.textField` to an `MGLStyleValue` object containing the
string `{name}`.
You can also configure many layout and paint attributes of an `MGLStyleLayer`
object to match the value of an attribute in this dictionary whenever it
renders this feature. For example, if you display features in an
`MGLShapeSource` using an `MGLCircleStyleLayer`, you can assign a `halfway`
attribute to each of the source’s features, then set
`MGLCircleStyleLayer.circleRadius` to an `MGLStyleValue` object with an
interpolation mode of `MGLInterpolationModeIdentity` and an attribute name of
`halfway`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack, this didn't occur to me earlier, but would this be a good place to suggest referring to the DDS support tables to for those properties where ^ this approach is actually available?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MGLCircleStyleLayer.circleRadius will link automatically to the documentation for that property, which like all layout and paint properties will list the interpolation modes and function types that are supported. I would like to refer the developer to a guide on data-driven styling, once it’s written as part of #7924.


The `MGLSymbolStyleLayer.textField` and `MGLSymbolStyleLayer.iconImageName`
properties allow you to use attributes yet another way. For example, to label
features in an `MGLShapeSource` object by their names, you can assign a `name`
attribute to each of the source’s features, then set
`MGLSymbolStyleLayer.textField` to an `MGLStyleValue` object containing the
raw string value `{name}`.

In vector tiles loaded by `MGLVectorSource` objects, the keys and values of
each feature’s attribute dictionary are determined by the source. Each
Expand All @@ -115,6 +124,15 @@ NS_ASSUME_NONNULL_BEGIN
and
<a href="https://www.mapbox.com/vector-tiles/mapbox-terrain/">Mapbox Terrain</a>
layer references.

When adding a feature to an `MGLShapeSource`, use the same Foundation types
listed above for each attribute value. In addition to the Foundation types, you
may also set an attribute to an `NSColor` (macOS) or `UIColor` (iOS), which
will be converted into its
<a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#types-color">CSS string representation</a>
when the feature is added to an `MGLShapeSource`. This can be convenient when
using the attribute to supply a value for a color-typed layout or paint
attribute via the `MGLInterpolationModeIdentity` interpolation mode.

Note that while it is possible to change this value on feature
instances obtained from `-[MGLMapView visibleFeaturesAtPoint:]` and related
Expand Down
10 changes: 10 additions & 0 deletions platform/darwin/src/NSExpression+MGLAdditions.mm
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
#import "NSExpression+MGLAdditions.h"

#import "MGLTypes.h"
#if TARGET_OS_IPHONE
#import "UIColor+MGLAdditions.h"
#else
#import "NSColor+MGLAdditions.h"
#endif

@implementation NSExpression (MGLAdditions)

- (std::vector<mbgl::Value>)mgl_aggregateMBGLValue {
Expand Down Expand Up @@ -56,6 +63,9 @@ @implementation NSExpression (MGLAdditions)
// We use long long here to avoid any truncation.
return { (int64_t)number.longLongValue };
}
} else if ([value isKindOfClass:[MGLColor class]]) {
auto hexString = [(MGLColor *)value mgl_color].stringify();
return { hexString };
} else if (value && value != [NSNull null]) {
[NSException raise:NSInvalidArgumentException
format:@"Can’t convert %s:%@ to mbgl::Value", [value objCType], value];
Expand Down
247 changes: 78 additions & 169 deletions platform/darwin/test/MGLExpressionTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,29 @@

#import <string>

#import "MGLTypes.h"
#import "NSExpression+MGLAdditions.h"

#define MGLAssertEqualValues(actual, expected, ...) \
XCTAssertTrue(actual.is<__typeof__(expected)>()); \
if (actual.is<__typeof__(expected)>()) { \
XCTAssertEqual(actual.get<__typeof__(expected)>(), expected, __VA_ARGS__); \
}

#define MGLAssertEqualValuesWithAccuracy(actual, expected, accuracy, ...) \
XCTAssertTrue(actual.is<__typeof__(expected)>()); \
if (actual.is<__typeof__(expected)>()) { \
XCTAssertEqualWithAccuracy(actual.get<__typeof__(expected)>(), expected, accuracy, __VA_ARGS__); \
}

#define MGLAssertConstantEqualsValue(constant, value, ...) \
MGLAssertEqualValues([NSExpression expressionForConstantValue:constant].mgl_constantMBGLValue, value, __VA_ARGS__);

#define MGLAssertConstantEqualsValueWithAccuracy(constant, value, accuracy, ...) \
MGLAssertEqualValuesWithAccuracy([NSExpression expressionForConstantValue:constant].mgl_constantMBGLValue, value, accuracy, __VA_ARGS__);

using namespace std::string_literals;

@interface MGLExpressionTests : XCTestCase

@end
Expand All @@ -23,212 +44,100 @@ - (NSComparisonPredicate *)equalityComparisonPredicateWithRightConstantValue:(id
return predicate;
}

#pragma mark - String Tests

- (void)testExpressionConversionString
{
NSComparisonPredicate *predicate = [self equalityComparisonPredicateWithRightConstantValue:@"bar"];
mbgl::Value convertedValue = predicate.rightExpression.mgl_constantMBGLValue;
XCTAssertTrue(convertedValue.is<std::string>());
XCTAssertEqualObjects(@(convertedValue.get<std::string>().c_str()), @"bar");
}
#pragma mark - Valuation tests

- (void)testExpressionConversionStringWithUnicode
{
NSComparisonPredicate *predicate = [self equalityComparisonPredicateWithRightConstantValue:@"🆔🆗🇦🇶"];
mbgl::Value convertedValue = predicate.rightExpression.mgl_constantMBGLValue;
XCTAssertTrue(convertedValue.is<std::string>());
XCTAssertEqual(convertedValue.get<std::string>(), "🆔🆗🇦🇶");
- (void)testStringValuation {
MGLAssertConstantEqualsValue(@"bar", "bar"s, @"NSString should convert to std::string.");
MGLAssertConstantEqualsValue(@"🆔🆗🇦🇶", "🆔🆗🇦🇶"s, @"NSString with non-ASCII characters should convert losslessly to std::string.");
}

#pragma mark - Boolean Tests

- (void)testExpressionConversionBooleanTrue
{
NSComparisonPredicate *predicate = [self equalityComparisonPredicateWithRightConstantValue:@YES];
mbgl::Value convertedValue = predicate.rightExpression.mgl_constantMBGLValue;
XCTAssertTrue(convertedValue.is<bool>());
XCTAssertEqual(convertedValue.get<bool>(), true);
- (void)testColorValuation {
MGLAssertConstantEqualsValue([MGLColor redColor], "rgba(255,0,0,1)"s, @"MGLColor should convert to std::string containing CSS color string.");
}

- (void)testExpressionConversionBooleanFalse
{
NSComparisonPredicate *predicate = [self equalityComparisonPredicateWithRightConstantValue:@NO];
mbgl::Value convertedValue = predicate.rightExpression.mgl_constantMBGLValue;
XCTAssertTrue(convertedValue.is<bool>());
XCTAssertEqual(convertedValue.get<bool>(), false);
- (void)testBooleanValuation {
MGLAssertConstantEqualsValue(@NO, false, @"Boolean NSNumber should convert to bool.");
MGLAssertConstantEqualsValue(@YES, true, @"Boolean NSNumber should convert to bool.");
}

#pragma mark - Floating Point Tests

- (void)testExpressionConversionDouble
- (void)testDoubleValuation
{
NSComparisonPredicate *predicate;
mbgl::Value convertedValue;

predicate = [self equalityComparisonPredicateWithRightConstantValue:[NSNumber numberWithDouble:DBL_MIN]];
convertedValue = predicate.rightExpression.mgl_constantMBGLValue;
XCTAssertTrue(convertedValue.is<double>());
XCTAssertEqual(convertedValue.get<double>(), DBL_MIN);

predicate = [self equalityComparisonPredicateWithRightConstantValue:[NSNumber numberWithDouble:DBL_MAX]];
convertedValue = predicate.rightExpression.mgl_constantMBGLValue;
XCTAssertTrue(convertedValue.is<double>());
XCTAssertEqual(convertedValue.get<double>(), DBL_MAX);
MGLAssertConstantEqualsValue(@DBL_MIN, DBL_MIN, @"Double NSNumber should convert to double.");
MGLAssertConstantEqualsValue(@DBL_MAX, DBL_MAX, @"Double NSNumber should convert to double.");
}

- (void)testExpressionConversionFloat
{
- (void)testFloatValuation {
// Because we can't guarantee precision when using float, and because
// we warn the user to this effect in -[NSExpression mgl_constantMBGLValue],
// we just check that things are in the ballpark here with integer values
// and some lower-precision checks.

NSComparisonPredicate *predicate;
mbgl::Value convertedValue;

predicate = [self equalityComparisonPredicateWithRightConstantValue:[NSNumber numberWithFloat:-1]];
convertedValue = predicate.rightExpression.mgl_constantMBGLValue;
XCTAssertTrue(convertedValue.is<double>());
XCTAssertEqual(convertedValue.get<double>(), -1);

predicate = [self equalityComparisonPredicateWithRightConstantValue:[NSNumber numberWithFloat:1]];
convertedValue = predicate.rightExpression.mgl_constantMBGLValue;
XCTAssertTrue(convertedValue.is<double>());
XCTAssertEqual(convertedValue.get<double>(), 1);

predicate = [self equalityComparisonPredicateWithRightConstantValue:[NSNumber numberWithFloat:-23.232342]];
convertedValue = predicate.rightExpression.mgl_constantMBGLValue;
XCTAssertTrue(convertedValue.is<double>());
XCTAssertEqualWithAccuracy(convertedValue.get<double>(), -23.232342, 0.000001);

predicate = [self equalityComparisonPredicateWithRightConstantValue:[NSNumber numberWithFloat:23.232342]];
convertedValue = predicate.rightExpression.mgl_constantMBGLValue;
XCTAssertTrue(convertedValue.is<double>());
XCTAssertEqualWithAccuracy(convertedValue.get<double>(), 23.232342, 0.000001);

MGLAssertConstantEqualsValue(@-1.0f, -1.0, @"Float NSNumber should convert to double.");
MGLAssertConstantEqualsValue(@1.0f, 1.0, @"Float NSNumber should convert to double.");
MGLAssertConstantEqualsValueWithAccuracy(@-23.232342f, -23.232342, 0.000001, @"Float NSNumber should convert to double.");
MGLAssertConstantEqualsValueWithAccuracy(@23.232342f, 23.232342, 0.000001, @"Float NSNumber should convert to double.");
MGLAssertConstantEqualsValueWithAccuracy(@-FLT_MAX, static_cast<double>(-FLT_MAX), 0.000001, @"Float NSNumber should convert to double.");
MGLAssertConstantEqualsValueWithAccuracy(@FLT_MAX, static_cast<double>(FLT_MAX), 0.000001, @"Float NSNumber should convert to double.");
}

#pragma mark - Integer Tests

- (void)testExpressionNegativeIntegers
{
NSComparisonPredicate *predicate;
mbgl::Value convertedValue;

NSArray<NSNumber *> *minValues = @[
[NSNumber numberWithShort: SHRT_MIN],
[NSNumber numberWithInt: INT_MIN],
[NSNumber numberWithLong: LONG_MIN],
[NSNumber numberWithLongLong: LLONG_MIN],
[NSNumber numberWithInteger: NSIntegerMin]
];

NSArray<NSNumber *> *maxValues = @[
[NSNumber numberWithShort: SHRT_MAX],
[NSNumber numberWithInt: INT_MAX],
[NSNumber numberWithLong: LONG_MAX],
[NSNumber numberWithLongLong: LLONG_MAX],
[NSNumber numberWithInteger: NSIntegerMax]
];

- (void)testIntegerValuation {
// Negative integers should always come back as int64_t per mbgl::Value definition.
// We use the long long value because it can store the highest number on both 32-
// and 64-bit and won't overflow.

for (NSNumber *min in minValues)
{
predicate = [self equalityComparisonPredicateWithRightConstantValue:min];
convertedValue = predicate.rightExpression.mgl_constantMBGLValue;
XCTAssertTrue(convertedValue.is<int64_t>());
XCTAssertEqual(convertedValue.get<int64_t>(), min.longLongValue);
}
MGLAssertConstantEqualsValue(@SHRT_MIN, static_cast<int64_t>(SHRT_MIN), @"Negative short NSNumber should convert to int64_t.");
MGLAssertConstantEqualsValue(@INT_MIN, static_cast<int64_t>(INT_MIN), @"Negative int NSNumber should convert to int64_t.");
MGLAssertConstantEqualsValue(@LONG_MIN, static_cast<int64_t>(LONG_MIN), @"Negative long NSNumber should convert to int64_t.");
MGLAssertConstantEqualsValue(@LLONG_MIN, static_cast<int64_t>(LLONG_MIN), @"Negative long long NSNumber should convert to int64_t.");
MGLAssertConstantEqualsValue(@NSIntegerMin, static_cast<int64_t>(NSIntegerMin), @"Negative NSInteger NSNumber should convert to int64_t.");

// Positive integers should always come back as uint64_t per mbgl::Value definition.
// We use the unsigned long long value because it can store the highest number on
// both 32- and 64-bit and won't overflow.

for (NSNumber *max in maxValues)
{
predicate = [self equalityComparisonPredicateWithRightConstantValue:max];
convertedValue = predicate.rightExpression.mgl_constantMBGLValue;
XCTAssertTrue(convertedValue.is<uint64_t>());
XCTAssertEqual(convertedValue.get<uint64_t>(), max.unsignedLongLongValue);
}

MGLAssertConstantEqualsValue(@SHRT_MAX, static_cast<uint64_t>(SHRT_MAX), @"Positive short NSNumber should convert to uint64_t.");
MGLAssertConstantEqualsValue(@INT_MAX, static_cast<uint64_t>(INT_MAX), @"Positive int NSNumber should convert to uint64_t.");
MGLAssertConstantEqualsValue(@LONG_MAX, static_cast<uint64_t>(LONG_MAX), @"Positive long NSNumber should convert to uint64_t.");
MGLAssertConstantEqualsValue(@LLONG_MAX, static_cast<uint64_t>(LLONG_MAX), @"Positive long long NSNumber should convert to uint64_t.");
MGLAssertConstantEqualsValue(@NSIntegerMax, static_cast<uint64_t>(NSIntegerMax), @"Positive NSInteger NSNumber should convert to uint64_t.");
}

- (void)testExpressionPositiveAndZeroIntegers
{
NSComparisonPredicate *predicate;
mbgl::Value convertedValue;

NSArray<NSNumber *> *minValues = @[
[NSNumber numberWithUnsignedShort: 0],
[NSNumber numberWithUnsignedInt: 0],
[NSNumber numberWithUnsignedLong: 0],
[NSNumber numberWithUnsignedLongLong: 0],
[NSNumber numberWithUnsignedInteger: 0]
];

NSArray<NSNumber *> *maxValues = @[
[NSNumber numberWithUnsignedShort: USHRT_MAX],
[NSNumber numberWithUnsignedInt: UINT_MAX],
[NSNumber numberWithUnsignedLong: ULONG_MAX],
[NSNumber numberWithUnsignedLongLong: ULLONG_MAX],
[NSNumber numberWithUnsignedInteger: NSUIntegerMax]
];

- (void)testUnsignedIntegerValuation {
// Zero-value integers should always come back as uint64_t per mbgl::Value definition
// (using the interpretation that zero is not negative). We use the unsigned long long
// value just for parity with the positive integer test.

for (NSNumber *min in minValues)
{
predicate = [self equalityComparisonPredicateWithRightConstantValue:min];
convertedValue = predicate.rightExpression.mgl_constantMBGLValue;
XCTAssertTrue(convertedValue.is<uint64_t>());
XCTAssertEqual(convertedValue.get<uint64_t>(), min.unsignedLongLongValue);
}
MGLAssertConstantEqualsValue(@(static_cast<unsigned short>(0)), static_cast<uint64_t>(0), @"Unsigned short NSNumber should convert to uint64_t.");
MGLAssertConstantEqualsValue(@0u, static_cast<uint64_t>(0), @"Unsigned int NSNumber should convert to uint64_t.");
MGLAssertConstantEqualsValue(@0UL, static_cast<uint64_t>(0), @"Unsigned long NSNumber should convert to uint64_t.");
MGLAssertConstantEqualsValue(@0ULL, static_cast<uint64_t>(0), @"Unsigned long long NSNumber should convert to uint64_t.");
MGLAssertConstantEqualsValue(@(static_cast<NSUInteger>(0)), static_cast<uint64_t>(0), @"Unsigned NSUInteger NSNumber should convert to uint64_t.");

// Positive integers should always come back as uint64_t per mbgl::Value definition.
// We use the unsigned long long value because it can store the highest number on
// both 32- and 64-bit and won't overflow.

for (NSNumber *max in maxValues)
{
predicate = [self equalityComparisonPredicateWithRightConstantValue:max];
convertedValue = predicate.rightExpression.mgl_constantMBGLValue;
XCTAssertTrue(convertedValue.is<uint64_t>());
XCTAssertEqual(convertedValue.get<uint64_t>(), max.unsignedLongLongValue);
}
MGLAssertConstantEqualsValue(@USHRT_MAX, static_cast<uint64_t>(USHRT_MAX), @"Unsigned short NSNumber should convert to uint64_t.");
MGLAssertConstantEqualsValue(@UINT_MAX, static_cast<uint64_t>(UINT_MAX), @"Unsigned int NSNumber should convert to uint64_t.");
MGLAssertConstantEqualsValue(@ULONG_MAX, static_cast<uint64_t>(ULONG_MAX), @"Unsigned long NSNumber should convert to uint64_t.");
MGLAssertConstantEqualsValue(@ULLONG_MAX, static_cast<uint64_t>(ULLONG_MAX), @"Unsigned long long NSNumber should convert to uint64_t.");
MGLAssertConstantEqualsValue(@NSUIntegerMax, static_cast<uint64_t>(NSUIntegerMax), @"Unsigned NSUInteger NSNumber should convert to uint64_t.");
}

#pragma mark - Null Tests

- (void)testExpressionConversionNull
{
NSComparisonPredicate *predicate = [self equalityComparisonPredicateWithRightConstantValue:[NSNull null]];
mbgl::Value convertedValue = predicate.rightExpression.mgl_constantMBGLValue;
XCTAssertTrue(convertedValue.is<mbgl::NullValue>());
- (void)testNullValuation {
mbgl::NullValue nullValue;
MGLAssertConstantEqualsValue([NSNull null], nullValue, @"NSNull should convert to mbgl::NullValue.");
}

#pragma mark - Feature type tests

- (void)testFeatureType {
XCTAssertEqual([NSExpression expressionWithFormat:@"'Point'"].mgl_featureType, mbgl::FeatureType::Point);
XCTAssertEqual([NSExpression expressionWithFormat:@"'LineString'"].mgl_featureType, mbgl::FeatureType::LineString);
XCTAssertEqual([NSExpression expressionWithFormat:@"'Polygon'"].mgl_featureType, mbgl::FeatureType::Polygon);
XCTAssertEqual([NSExpression expressionWithFormat:@"'Unknown'"].mgl_featureType, mbgl::FeatureType::Unknown);
XCTAssertEqual([NSExpression expressionWithFormat:@"''"].mgl_featureType, mbgl::FeatureType::Unknown);
XCTAssertEqual([NSExpression expressionForConstantValue:@"Point"].mgl_featureType, mbgl::FeatureType::Point);
XCTAssertEqual([NSExpression expressionForConstantValue:@"LineString"].mgl_featureType, mbgl::FeatureType::LineString);
XCTAssertEqual([NSExpression expressionForConstantValue:@"Polygon"].mgl_featureType, mbgl::FeatureType::Polygon);
XCTAssertEqual([NSExpression expressionForConstantValue:@"Unknown"].mgl_featureType, mbgl::FeatureType::Unknown);
XCTAssertEqual([NSExpression expressionForConstantValue:@""].mgl_featureType, mbgl::FeatureType::Unknown);

XCTAssertEqual([NSExpression expressionWithFormat:@"1"].mgl_featureType, mbgl::FeatureType::Point);
XCTAssertEqual([NSExpression expressionWithFormat:@"2"].mgl_featureType, mbgl::FeatureType::LineString);
XCTAssertEqual([NSExpression expressionWithFormat:@"3"].mgl_featureType, mbgl::FeatureType::Polygon);
XCTAssertEqual([NSExpression expressionWithFormat:@"0"].mgl_featureType, mbgl::FeatureType::Unknown);
XCTAssertEqual([NSExpression expressionWithFormat:@"-1"].mgl_featureType, mbgl::FeatureType::Unknown);
XCTAssertEqual([NSExpression expressionWithFormat:@"4"].mgl_featureType, mbgl::FeatureType::Unknown);
XCTAssertEqual([NSExpression expressionForConstantValue:@1].mgl_featureType, mbgl::FeatureType::Point);
XCTAssertEqual([NSExpression expressionForConstantValue:@2].mgl_featureType, mbgl::FeatureType::LineString);
XCTAssertEqual([NSExpression expressionForConstantValue:@3].mgl_featureType, mbgl::FeatureType::Polygon);
XCTAssertEqual([NSExpression expressionForConstantValue:@0].mgl_featureType, mbgl::FeatureType::Unknown);
XCTAssertEqual([NSExpression expressionForConstantValue:@-1].mgl_featureType, mbgl::FeatureType::Unknown);
XCTAssertEqual([NSExpression expressionForConstantValue:@4].mgl_featureType, mbgl::FeatureType::Unknown);

XCTAssertEqual([NSExpression expressionWithFormat:@"nil"].mgl_featureType, mbgl::FeatureType::Unknown);
XCTAssertEqual([NSExpression expressionForConstantValue:nil].mgl_featureType, mbgl::FeatureType::Unknown);
}

@end
Loading