Search code examples
iosswiftmapboxnsexpressionmapbox-expressions

Building a conditional expression using the Mapbox iOS SDK


I'm updating an existing iOS app that uses the Mapbox v6 SDK. The app defines a shape source using an array of point features and specifies that the points should be clustered based on a particular radius. Here's the code to do this:

let source = MGLShapeSource(identifier: sourceIdentifier, features: features, options: [.clustered: true, .clusterRadius: 50.0, .maximumZoomLevelForClustering: 14.0])

I also have a circle style layer defined to show the clusters on the map:

let circularClusterLayer = MGLCircleStyleLayer(identifier: "clusteredMarkers", source: source)
circularClusterLayer.predicate = NSPredicate(format: "cluster == YES")

This is working as expected but now I need to define the circle color based on an attribute of all the features located in the cluster. Specifically, each of my features has a boolean property in its attributes dictionary called .needsHelp. I'm trying to define the circle color for the cluster marker based on this logic:

if any of the features in the cluster have .needsHelp == true {
    // set the circle color = UIColor.red
    circularClusterLayer.circleColor = NSExpression(forConstantValue: UIColor.red)
} else {
    // set the circle color = UIColor.gray
    circularClusterLayer.circleColor = NSExpression(forConstantValue: UIColor.gray)
}

I'm pretty sure that I should be able to build an expression that returns true if any of the features in the cluster have .needsHelp == true or otherwise, returns false. And I believe this expression would be assigned to the .clusterProperties option for the source like this:

let expression1 = NSExpression(mglJSONObject: ["any",  ["==", ["boolean", ["get", "isInEmergency"]], true]])
let expression2 = NSExpression(forConstantValue: UIColor.red)
let clusterPropertyDictionary: NSDictionary = ["clusterContainsFeatureThatNeedsHelp" : [expression1, expression2]]        
let selectedContactsSource = MGLShapeSource(identifier: sourceIdentifier, features: features, options: [.clustered: true, .clusterRadius: 50.0, .maximumZoomLevelForClustering: 14.0, .clusterProperties: clusterPropertyDictionary])

I've been looking at the Mapbox expressions guide and I think I should be using the "any" decision expression to get a boolean value back indicating whether any of the features have .needsHelp == true or not. But so far I've been unable to translate the LISP-like expression syntax into an NSExpression that works.

I'm assuming that in addition to the expression above, I will also need another expression to check the value of "clusterContainsFeatureThatNeedsHelp" and then set the circle color accordingly. My best guess is that that would look something like this:

// TODO: check value of clusterContainsFeatureThatNeedsHelp to determine circle color

circularClusterLayer.circleColor = NSExpression(forConditional: NSPredicate(format: "%K == %@", "clusterContainsFeatureThatNeedsHelp", NSNumber(value: true)), trueExpression: NSExpression(forConstantValue: UIColor.red), falseExpression: NSExpression(forConstantValue: UIColor.gray))

Unfortunately, I'm not having any luck building either of these NSExpressions successfully. Has anyone done something like this before? If so, I'd appreciate any tips or suggestions!


Solution

  • I finally got this working. I'm now creating the clusterPropertyDictionary like this:

    let firstExpression = NSExpression(format: "max:({$featureAccumulated, clusterContainsFeatureThatNeedsHelp})")
    let secondExpression = NSExpression(format: "CAST(needsHelp, 'NSNumber')")
    let clusterPropertiesDictionary = ["clusterContainsFeatureThatNeedsHelp" : [firstExpression, secondExpression]]
    

    And the expression that actually sets the circle color looks like this:

    let expressionForClusterCircleColor = NSExpression(
        forConditional: NSPredicate(format: "clusterContainsFeatureThatNeedsHelp > 0"),
        trueExpression: NSExpression(forConstantValue: UIColor.red),
        falseExpression: NSExpression(forConstantValue: UIColor.gray)
    )
    circularClusterLayer.circleColor = expressionForClusterCircleColor