Platform: iOS
Mapbox SDK version: v4.0.0
The format string "valueForKeyPath:" seems to resolve to a nil value. When I use it to set a circle layer's color property, the layer appears black.
let stops = [
"earthquake": UIColor.orange,
"explosion": UIColor.red,
"quarry blast": UIColor.yellow
]
let layer = MGLCircleStyleLayer(identifier: "circles", source: source)
layer.circleColor = NSExpression(format: "FUNCTION(%@, 'valueForKeyPath:', type)", stops)
mapView.style?.addLayer(layer)
This behavior continues when a ternary function is used to check for a nil value.
layer.circleColor = NSExpression(format: "TERNARY(FUNCTION(%@, 'valueForKeyPath:', type) != nil, FUNCTION(%@, 'valueForKeyPath:', type), %@)", stops, stops, UIColor.blue)
cc @1ec5 @captainbarbosa
This seems to occur when the attribute has a value that is not included in the stops dictionary. The above layer renders as black because some features account for values of "ice quakes" or "Volcanic Eruption" for type.
Edit: Never mind, changing the dictionary still resolves to a black layer.
let stops : [String : UIColor] = ["earthquake" : UIColor.orange,
"explosion" : UIColor.red,
"quarry blast" : UIColor.yellow,
"ice quake": UIColor.blue,
"Volcanic Eruption": UIColor.purple,
"volcanic eruption": UIColor.gray]
Noting that the layer is also black if I use a predicate to try to filter out values not accounted for:
layer.predicate = NSPredicate(format: "type IN { 'earthquake', 'explosion', 'quarry blast', 'volcanic eruption', 'ice quake' }")
@jmkiley We are also facing the same issue. Please, fix this as soon as possible.
The expression鈥檚 mgl_jsonExpressionObject is:
["get",["get","type"],["literal",{"explosion":["rgb",255,0,0],"earthquake":["rgb",255,127.5,0],"quarry blast":["rgb",255,255,0]}]]
The problem is that the style specification only allows a get expression鈥檚 first argument to be a literal string, not an expression.
/cc @anandthakker
The problem is that the style specification only allows a get expression鈥檚 first argument to be a literal string, not an expression.
No, expressions are permitted there. The problem is that "literal" expressions are entirely literal. Values in the object, such as ["rgb",255,0,0], are not evaluated, so the result of this expression is a heterogeneous four element array, not a color.
Ah, good call. So the valueForKeyPath: approach to specifying variable-length stop dictionaries would only work for properties that accept simpler types like numbers or strings, not complex types like colors that require the use of an expression.
If you have a stop dictionary, there is still a way to turn it into a valid match expression. It鈥檚 a bit more involved than specifying an MGL_MATCH expression in a format string, so you鈥檇 only want to do this if you have a lot of stops or don鈥檛 know in advance what the stops will be:
let stops = [
"earthquake": UIColor.orange,
"explosion": UIColor.red,
"quarry blast": UIColor.yellow,
]
// MGL_MATCH(type, 'earthquake', <orange>, 'explosion', <red>, 'quarry blast', <yellow>, <blue>)
let arguments = [NSExpression(forKeyPath: "type")] + stops.flatMap {
[NSExpression(forConstantValue: $0.0), NSExpression(forConstantValue: $0.1)]
} + [NSExpression(forConstantValue: UIColor.blue)]
layer.circleColor = NSExpression(forFunction: "MGL_MATCH", arguments: arguments)
NSDictionary<NSString *, UIColor *> *stops = @{
@"earthquake": [UIColor orangeColor],
@"explosion": [UIColor redColor],
@"quarry blast": [UIColor yellowColor],
};
// MGL_MATCH(type, 'earthquake', <orange>, 'explosion', <red>, 'quarry blast', <yellow>, <blue>)
NSMutableArray *arguments = [NSMutableArray arrayWithObject:[NSExpression expressionForKeyPath:@"type"]];
[stops enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSColor * _Nonnull value, BOOL * _Nonnull stop) {
[arguments addObject:[NSExpression expressionForConstantValue:key]];
[arguments addObject:[NSExpression expressionForConstantValue:value]];
}];
[arguments addObject:[NSExpression expressionForConstantValue:[UIColor blueColor]]];
[NSExpression expressionForFunction:@"MGL_MATCH" arguments:arguments];
We tried the above example code in our app, but the app is crashing,
Console log,
2018-07-04 17:42:06.292261+0530 OurApp[5101:1919118] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unrecognized expression function MGL_MATCH:.'
* First throw call stack:
(0x18111ed8c 0x1802d85ec 0x18111ec6c 0x1094d1538 0x1093ef3c8 0x1093ef1ec 0x104989af8 0x104468f60 0x10a49d260 0x10a49d220 0x10a4a1db0 0x1810c7070 0x1810c4bc8 0x180fe4da8 0x182fc7020 0x18afc578c 0x104a70510 0x180a75fc0)
libc++abi.dylib: terminating with uncaught exception of type NSException
Code,
let arguments = [NSExpression(forKeyPath: attributeName)] + stops.flatMap {[$0.0, $0.1] } + [UIColor.red]
layer.circleColor = NSExpression(forFunction: "MGL_MATCH:", arguments: arguments)
layer.circleStrokeColor = NSExpression(forFunction: "MGL_MATCH:", arguments: arguments)
Platform: iOS
Mapbox SDK version: v4.1.1
My bad, that should be MGL_MATCH, without the colon. (There鈥檚 an alternative syntax mgl_match:, with a colon and a different argument order.) I鈥檝e updated the example above.
馃暫Finally, it's worked for us. Thanks a lot @1ec5.
Great to hear it!
The problem is that "literal" expressions are entirely literal. Values in the object, such as
["rgb",255,0,0], are not evaluated, so the result of this expression is a heterogeneous four element array, not a color.
This problem is being tracked in mapbox/mapbox-gl-js#6155, also for expressions within arrays.
Most helpful comment
Ah, good call. So the
valueForKeyPath:approach to specifying variable-length stop dictionaries would only work for properties that accept simpler types like numbers or strings, not complex types like colors that require the use of an expression.If you have a stop dictionary, there is still a way to turn it into a valid match expression. It鈥檚 a bit more involved than specifying an
MGL_MATCHexpression in a format string, so you鈥檇 only want to do this if you have a lot of stops or don鈥檛 know in advance what the stops will be: