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

Commit

Permalink
Install aftermarket expression functions (#11472)
Browse files Browse the repository at this point in the history
* [ios, macos] Introduced more ergonomic expression concatenation syntax

* [ios, macos] Fixed mgl_join:

* [ios, macos] Simplified expression document headings

* [ios, macos] Documented mgl_join:

* [ios, macos] Convert length operator to length: for strings

Only works for literal strings.

* [ios, macos] Implemented type conversion using CAST()

* [ios, macos] Aftermarket let expressions

* [ios, macos] Refactored aftermarket expression functions

Refactored the installation of aftermarket expression functions to use macros. It is no longer necessary to handwrite the type encoding of a function. Also added aftermarket functions for interpolating and stepping.

* [ios, macos] Updated documentation, tests, demo apps

* [ios, macos] Added generic expression function

* [ios, macos] Add MGL_MATCH function placeholder.

* [ios, macos] Add MGL_SWITCH expression operator.

* [ios, macos] Add mgl_coalesce: as expression function.

* [ios, macos] Update style documentation.

* [ios, macos] Add conventional custom function support.

* [ios, macos] Updated example code

* [ios, macos] Add mgl_coalesce conventional custom function support.

* [ios, macos] Add aftermarket function to 'has' operator.

* [ios, macos] Add documentation for lookup and feature operators.

* [ios, macos] Documented simple lookup

* [ios, macos] Renamed, reversed has expression

Renamed mgl_hasProperty:properties: to mgl_does:have: for better readability and consistency with the conventional mgl_has: function. Documented both forms of mgl_has:.

* [ios, macos] Restored OEM conditionals where available

This is the preferred syntax for simple conditionals on iOS 9 and above, because you can inline the predicate instead of wrapping it in a constant value expression, which means you can write a conditional in a single format string.

* [ios, macos] Update style docs.

Co-authored-by: Fabian Guerra <[email protected]>
  • Loading branch information
1ec5 and fabian-guerra authored Mar 29, 2018
1 parent e5a3a7a commit 8c5eb6c
Show file tree
Hide file tree
Showing 30 changed files with 990 additions and 571 deletions.
36 changes: 22 additions & 14 deletions platform/darwin/docs/guides/For Style Authors.md.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,11 @@ iOS.
<% } -%>
### Expression operators
Many expression operators defined in the style specification have corresponding
symbols to be used with the `+[NSExpression expressionWithFormat:]`,
`+[NSExpression expressionForFunction:arguments:]`, or
`+[NSExpression expressionForFunction:selectorName:arguments:]` method:
In style specification | Method, function, or predicate type | Format string syntax
-----------------------|-------------------------------------|---------------------
`array` | |
Expand All @@ -330,15 +335,15 @@ In style specification | Method, function, or predicate type | Format string syn
`string` | |
`to-boolean` | `boolValue` |
`to-color` | |
`to-number` | `mgl_numberWithFallbackValues:` |
`to-string` | `stringValue` |
`to-number` | `mgl_numberWithFallbackValues:` | `CAST(zipCode, 'NSNumber')`
`to-string` | `stringValue` | `CAST(ele, 'NSString')`
`typeof` | |
`geometry-type` | |
`id` | |
`properties` | |
`at` | |
`geometry-type` | |`$mgl_geometryType`
`id` | |`$mgl_featureIdentifier`
`properties` | |`$mgl_featureProperties`
`at` | `objectFrom:withIndex:` | `array[n]`
`get` | `+[NSExpression expressionForKeyPath:]` | Key path
`has` | |
`has` | `mgl_does:have:` | `mgl_does:have:(self, 'key')`
`length` | `count:` | `count({1, 2, 2, 3, 4, 7, 9})`
`!` | `NSNotPredicateType` | `NOT (p0 OR … OR pn)`
`!=` | `NSNotEqualToPredicateOperatorType` | `key != value`
Expand All @@ -349,14 +354,14 @@ In style specification | Method, function, or predicate type | Format string syn
`>=` | `NSGreaterThanOrEqualToPredicateOperatorType` | `key >= value`
`all` | `NSAndPredicateType` | `p0 AND … AND pn`
`any` | `NSOrPredicateType` | `p0 OR … OR pn`
`case` | `+[NSExpression expressionForConditional:trueExpression:falseExpression:]` | `TERNARY(condition, trueExpression, falseExpression)`
`coalesce` | |
`match` | |
`interpolate` | `mgl_interpolateWithCurveType:parameters:stops:` |
`step` | `mgl_stepWithMinimum:stops:` |
`let` | `mgl_expressionWithContext:` |
`case` | `+[NSExpression expressionForConditional:trueExpression:falseExpression:]` or `MGL_IF` | `TERNARY(1 = 2, YES, NO)` or `MGL_IF(1 = 2, YES, 2 = 2, YES, NO)`
`coalesce` | `mgl_coalesce:` | `mgl_coalesce({x, y, z})`
`match` | `MGL_MATCH` | `MGL_MATCH(x, 0, 'zero match', 1, 'one match', 'two match', 'default')`
`interpolate` | `mgl_interpolate:withCurveType:parameters:stops:` |
`step` | `mgl_step:withMinimum:stops:` |
`let` | `mgl_expressionWithContext:` | `MGL_LET('ios', 11, 'macos', 10.13, $ios + $macos)`
`var` | `+[NSExpression expressionForVariable:]` | `$variable`
`concat` | `stringByAppendingString:` |
`concat` | `mgl_join:` | `mgl_join({'Old', ' ', 'MacDonald'})`
`downcase` | `lowercase:` | `lowercase('DOWNTOWN')`
`upcase` | `uppercase:` | `uppercase('Elysian Fields')`
<% if (macOS) { -%>
Expand Down Expand Up @@ -391,6 +396,9 @@ In style specification | Method, function, or predicate type | Format string syn
`zoom` | | `$zoom`
`heatmap-density` | | `$heatmapDensity`
For operators that have no corresponding `NSExpression` symbol, use the
`MGL_FUNCTION()` format string syntax.
## Filtering sources
You can filter a shape or vector source by setting the
Expand Down
52 changes: 40 additions & 12 deletions platform/darwin/docs/guides/Predicates and Expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,17 +123,15 @@ style attributes and also `hyphen-minus` and `tag:subtag`. However, you must use

## Using expressions to configure layout and paint attributes

### Functions

#### Key paths
### Key paths

A key path expression refers to an attribute of the `MGLFeature` object being
evaluated for display. For example, if a polygon’s `MGLFeature.attributes`
dictionary contains the `floorCount` key, then the key path `floorCount` refers
to the value of the `floorCount` attribute when evaluating that particular
polygon.

#### Predefined functions
### Predefined functions

Of the
[functions predefined by the `+[NSExpression expressionForFunction:arguments:]` method](https://developer.apple.com/documentation/foundation/nsexpression/1413747-init#discussion),
Expand Down Expand Up @@ -163,6 +161,8 @@ Initializer parameter | Format string syntax
`uppercase:` | `uppercase('Elysian Fields')`
`lowercase:` | `lowercase('DOWNTOWN')`
`noindex:` | `noindex(0 + 2 + c)`
`length:` | `length('Wapakoneta')`
`castObject:toType:` | `CAST(ele, 'NSString')`<br>`CAST(ele, 'NSNumber')`

The following predefined functions are not supported:

Expand All @@ -182,13 +182,28 @@ Initializer parameter | Format string syntax
`onesComplement:` | `onesComplement(255)`
`distanceToLocation:fromLocation:` | `distanceToLocation:fromLocation:(there, here)`

#### Mapbox-specific functions
### Mapbox-specific functions

For compatibility with the Mapbox Style Specification, the following functions
are defined by this SDK for use with style layers. Because these functions are
not predefined by `NSExpression`, you must use the
are defined by this SDK. When setting a style layer property, you can call these
functions just like the predefined functions above, using either the
`+[NSExpression expressionForFunction:arguments:]` method or a convenient format
string syntax:

Initializer parameter | Format string syntax | Description
----------------------|----------------------|------------
`mgl_does:have:` | `mgl_does:have:(SELF, 'key')` or `mgl_does:have:(%@, 'key')` | Returns a Boolean value indicating whether the dictionary has a value for the key or whether the evaluated object (`SELF`) has a value for the feature attribute. Compared to the `mgl_has:` custom function, that function’s target is instead passed in as the first argument to this function. Both functions are equivalent to the syntax `key != NIL` or `%@[key] != NIL` but can be used outside of a predicate.
`mgl_interpolate:withCurveType:parameters:stops:` | `mgl_interpolate:withCurveType:parameters:stops:(x, 'linear', nil, %@)` | Produces continuous, smooth results by interpolating between pairs of input and output values (“stops”). Compared to the `mgl_interpolateWithCurveType:parameters:stops:` custom function, the input expression (that function’s target) is instead passed in as the first argument to this function.
`mgl_step:from:stops:` | `mgl_step:from:stops:(x, 11, %@)` | Produces discrete, stepped results by evaluating a piecewise-constant function defined by pairs of input and output values ("stops"). Compared to the `mgl_stepWithMinimum:stops:` custom function, the input expression (that function’s target) is instead passed in as the first argument to this function.
`mgl_join:` | `mgl_join({'Old', 'MacDonald'})` | Returns the result of concatenating together all the elements of an array in order. Compared to the `stringByAppendingString:` custom function, this function takes only one argument, which is an aggregate expression containing the strings to concatenate.
`mgl_coalesce:` | `mgl_coalesce({x, y, z})` | Returns the first non-`nil` value from an array of expressions.
`MGL_LET` | `MGL_LET('age', uppercase('old'), 'name', uppercase('MacDonald'), mgl_join({$age, $name}))` | Any number of variable names interspersed with their assigned `NSExpression` values, followed by an `NSExpression` that may contain references to those variables. Compared to the `mgl_expressionWithContext:` custom function, this function takes the variable names and values inline before the expression that contains references to those variables.
`MGL_MATCH` | `MGL_MATCH(x, 0, 'zero match', 1, 'one match', 'two match', 'default')` | Evaluates the first expression and returns the value that matches the initial condition. After the first expression condition a pair of matching/return value should be added and a default value.
`MGL_IF` | `MGL_IF(1 = 2, YES, 2 = 2, YES, NO)` | Returns the first value that meets the condition otherwise a default value. The expression conditions should be added in pairs of conditional/return value. Unlike `+[NSExpression expressionForConditional:trueExpression:falseExpression:]` or the `TERNARY()` syntax, this function can accept multiple “if else” conditions and is supported on iOS 8._x_ and macOS 10.10._x_; however, each conditional passed into this function must be wrapped in a constant expression.

The following custom functions are also available with the
`+[NSExpression expressionForFunction:selectorName:arguments:]` method or the
`FUNCTION()` format string syntax instead.
`FUNCTION()` format string syntax:

<table>
<thead>
Expand All @@ -206,6 +221,17 @@ not predefined by `NSExpression`, you must use the
empty string, 0, `FALSE`, `NIL`, or NaN, otherwise `TRUE`.
</td>
</tr>
<tr>
<td><code>mgl_has:</code></td>
<td>
An `NSExpression` that evaluates to an <code>NSDictionary</code> or the evaluated object (<code>SELF</code>).
</td>
<td>
An `NSExpression` that evaluates to an <code>NSString</code> representing the key to look up in the dictionary or the feature attribute to look up in the evaluated object (see <code>MGLFeature.attributes</code>).
</td>
<td>
`true` if the dictionary has a value for the key or if the evaluated object has a value for the feature attribute.
</td>
<tr>
<td><code>mgl_expressionWithContext:</code></td>
<td>
Expand Down Expand Up @@ -366,6 +392,10 @@ classes, but you should not call them directly outside the context of an
expression, because the result may differ from the evaluated expression’s result
or may result in undefined behavior.

The Mapbox Style Specification defines some operators for which no custom
function is available. To use these operators in an `NSExpression`, call the
`MGL_FUNCTION()` function with the same arguments that the operator expects.

### Variables

The following variables are defined by this SDK for use with style layers:
Expand Down Expand Up @@ -404,11 +434,9 @@ of a [Mapbox-specific function](#mapbox-specific-functions) that takes an
`NSDictionary` as an argument:

```objc
[NSExpression expressionWithFormat:@"FUNCTION($floorCount + 1, 'mgl_expressionWithContext:', %@)",
{@"floorCount": @2}];
[NSExpression expressionWithFormat:@"MGL_LET('floorCount', 2, $floorCount + 1)"];
```
```swift
NSExpression(format: "FUNCTION($floorCount + 1, 'mgl_expressionWithContext:', %@)",
["floorCount": 2])
NSExpression(format: "MGL_LET(floorCount, 2, $floorCount + 1)")
```
2 changes: 1 addition & 1 deletion platform/darwin/src/MGLCircleStyleLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ typedef NS_ENUM(NSUInteger, MGLCircleTranslationAnchor) {
let layer = MGLCircleStyleLayer(identifier: "circles", source: population)
layer.sourceLayerIdentifier = "population"
layer.circleColor = NSExpression(forConstantValue: UIColor.green)
layer.circleRadius = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'exponential', 1.75, %@)",
layer.circleRadius = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'exponential', 1.75, %@)",
[12: 2,
22: 180])
layer.circleOpacity = NSExpression(forConstantValue: 0.7)
Expand Down
4 changes: 2 additions & 2 deletions platform/darwin/src/MGLHeatmapStyleLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ NS_ASSUME_NONNULL_BEGIN
```swift
let layer = MGLHeatmapStyleLayer(identifier: "earthquake-heat", source: earthquakes)
layer.heatmapWeight = NSExpression(format: "FUNCTION(magnitude, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)",
layer.heatmapWeight = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:(magnitude, 'linear', nil, %@)",
[0: 0,
6: 1])
layer.heatmapIntensity = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)",
layer.heatmapIntensity = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)",
[0: 1,
9: 3])
mapView.style?.addLayer(layer)
Expand Down
2 changes: 1 addition & 1 deletion platform/darwin/src/MGLLineStyleLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ typedef NS_ENUM(NSUInteger, MGLLineTranslationAnchor) {
```swift
let layer = MGLLineStyleLayer(identifier: "trails-path", source: trails)
layer.sourceLayerIdentifier = "trails"
layer.lineWidth = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'exponential', 1.5, %@)",
layer.lineWidth = NSExpression(format: "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'exponential', 1.5, %@)",
[14: 2,
18: 20])
layer.lineColor = NSExpression(forConstantValue: UIColor.brown)
Expand Down
14 changes: 7 additions & 7 deletions platform/darwin/src/MGLStyleValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ NS_ASSUME_NONNULL_BEGIN

typedef NSString *MGLStyleFunctionOption NS_STRING_ENUM NS_UNAVAILABLE;

extern MGL_EXPORT const MGLStyleFunctionOption MGLStyleFunctionOptionInterpolationBase __attribute__((unavailable("Use NSExpression instead, applying the mgl_interpolateWithCurveType:parameters:stops: function with a curve type of “exponential” and a non-nil parameter.")));
extern MGL_EXPORT const MGLStyleFunctionOption MGLStyleFunctionOptionInterpolationBase __attribute__((unavailable("Use NSExpression instead, applying the mgl_interpolate:withCurveType:parameters:stops: function with a curve type of “exponential” and a non-nil parameter.")));

extern MGL_EXPORT const MGLStyleFunctionOption MGLStyleFunctionOptionDefaultValue __attribute__((unavailable("Use +[NSExpression expressionForConditional:trueExpression:falseExpression:] instead.")));

typedef NS_ENUM(NSUInteger, MGLInterpolationMode) {
MGLInterpolationModeExponential __attribute__((unavailable("Use NSExpression instead, applying the mgl_interpolateWithCurveType:parameters:stops: function with a curve type of “exponential”."))) = 0,
MGLInterpolationModeInterval __attribute__((unavailable("Use NSExpression instead, calling the mgl_stepWithMinimum:stops: function."))),
MGLInterpolationModeExponential __attribute__((unavailable("Use NSExpression instead, applying the mgl_interpolate:withCurveType:parameters:stops: function with a curve type of “exponential”."))) = 0,
MGLInterpolationModeInterval __attribute__((unavailable("Use NSExpression instead, calling the mgl_step:from:stops: function."))),
MGLInterpolationModeCategorical __attribute__((unavailable("Use NSExpression instead."))),
MGLInterpolationModeIdentity __attribute__((unavailable("Use +[NSExpression expressionForKeyPath:] instead.")))
} __attribute__((unavailable("Use NSExpression instead.")));
Expand All @@ -29,19 +29,19 @@ MGL_EXPORT __attribute__((unavailable("Use +[NSExpression expressionForConstantV

@compatibility_alias MGLStyleConstantValue MGLConstantStyleValue;

MGL_EXPORT __attribute__((unavailable("Use NSExpression instead, calling the mgl_stepWithMinimum:stops: or mgl_interpolateWithCurveType:parameters:stops: function.")))
MGL_EXPORT __attribute__((unavailable("Use NSExpression instead, calling the mgl_step:from:stops: or mgl_interpolate:withCurveType:parameters:stops: function.")))
@interface MGLStyleFunction<T> : MGLStyleValue<T>
@end

MGL_EXPORT __attribute__((unavailable("Use NSExpression instead, applying the mgl_stepWithMinimum:stops: or mgl_interpolateWithCurveType:parameters:stops: function to the $zoomLevel variable.")))
MGL_EXPORT __attribute__((unavailable("Use NSExpression instead, applying the mgl_step:from:stops: or mgl_interpolate:withCurveType:parameters:stops: function to the $zoomLevel variable.")))
@interface MGLCameraStyleFunction<T> : MGLStyleFunction<T>
@end

MGL_EXPORT __attribute__((unavailable("Use NSExpression instead, applying the mgl_stepWithMinimum:stops: or mgl_interpolateWithCurveType:parameters:stops: function to a key path expression.")))
MGL_EXPORT __attribute__((unavailable("Use NSExpression instead, applying the mgl_step:from:stops: or mgl_interpolate:withCurveType:parameters:stops: function to a key path expression.")))
@interface MGLSourceStyleFunction<T> : MGLStyleFunction<T>
@end

MGL_EXPORT __attribute__((unavailable("Use a NSExpression instead with nested mgl_stepWithMinimum:stops: or mgl_interpolateWithCurveType:parameters:stops: function calls.")))
MGL_EXPORT __attribute__((unavailable("Use a NSExpression instead with nested mgl_step:from:stops: or mgl_interpolate:withCurveType:parameters:stops: function calls.")))
@interface MGLCompositeStyleFunction<T> : MGLStyleFunction<T>
@end

Expand Down
Loading

0 comments on commit 8c5eb6c

Please sign in to comment.