diff options
Diffstat (limited to 'platform/darwin/src')
-rw-r--r-- | platform/darwin/src/MGLShapeSource.h | 32 | ||||
-rw-r--r-- | platform/darwin/src/MGLShapeSource.mm | 53 | ||||
-rw-r--r-- | platform/darwin/src/MGLStyleValue.mm | 13 | ||||
-rw-r--r-- | platform/darwin/src/MGLStyleValue_Private.h | 5 | ||||
-rw-r--r-- | platform/darwin/src/NSExpression+MGLAdditions.h | 7 | ||||
-rw-r--r-- | platform/darwin/src/NSExpression+MGLAdditions.mm | 43 |
6 files changed, 146 insertions, 7 deletions
diff --git a/platform/darwin/src/MGLShapeSource.h b/platform/darwin/src/MGLShapeSource.h index a57b963c63..675c219300 100644 --- a/platform/darwin/src/MGLShapeSource.h +++ b/platform/darwin/src/MGLShapeSource.h @@ -42,6 +42,38 @@ FOUNDATION_EXTERN MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClus FOUNDATION_EXTERN MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClusterRadius; /** + An `NSDictionary` object where the key is an `NSString`. The dictionary key will + be the feature attribute key. The resulting attribute value is + aggregated from the clustered points. The dictionary value is an `NSArray` + consisting of two `NSExpression` objects. + + The first object determines how the attribute values are accumulated from the + cluster points. It is an `NSExpression` with an expression function that accepts + two or more arguments, such as `sum` or `max`. The arguments should be + `featureAccumulated` and the previously defined feature attribute key. The + resulting value is assigned to the specified attribute key. + + The second `NSExpression` in the array determines which + attribute values are accessed from individual features within a cluster. + + ```swift + let firstExpression = NSExpression(format: "sum:({$featureAccumulated, sumValue})") + let secondExpression = NSExpression(forKeyPath: "magnitude") + let clusterPropertiesDictionary = ["sumValue" : [firstExpression, secondExpression]] + + let options : [MGLShapeSourceOption : Any] = [.clustered : true, + .clusterProperties: clusterPropertiesDictionary] + ``` + + This option corresponds to the + <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources-geojson-clusterProperties"><code>clusterProperties</code></a> + source property in the Mapbox Style Specification. + + This option only affects point features within an `MGLShapeSource` object; it + is ignored when creating an `MGLComputedShapeSource` object. + */ +FOUNDATION_EXTERN MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClusterProperties; +/** An `NSNumber` object containing an integer; specifies the maximum zoom level at which to cluster points if clustering is enabled. Defaults to one zoom level less than the value of `MGLShapeSourceOptionMaximumZoomLevel` so that, at the diff --git a/platform/darwin/src/MGLShapeSource.mm b/platform/darwin/src/MGLShapeSource.mm index 3628a0eb74..3820fe9d60 100644 --- a/platform/darwin/src/MGLShapeSource.mm +++ b/platform/darwin/src/MGLShapeSource.mm @@ -3,6 +3,7 @@ #import "MGLLoggingConfiguration_Private.h" #import "MGLStyle_Private.h" +#import "MGLStyleValue_Private.h" #import "MGLMapView_Private.h" #import "MGLSource_Private.h" #import "MGLFeature_Private.h" @@ -19,6 +20,7 @@ const MGLShapeSourceOption MGLShapeSourceOptionBuffer = @"MGLShapeSourceOptionBuffer"; const MGLShapeSourceOption MGLShapeSourceOptionClusterRadius = @"MGLShapeSourceOptionClusterRadius"; const MGLShapeSourceOption MGLShapeSourceOptionClustered = @"MGLShapeSourceOptionClustered"; +const MGLShapeSourceOption MGLShapeSourceOptionClusterProperties = @"MGLShapeSourceOptionClusterProperties"; const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevel = @"MGLShapeSourceOptionMaximumZoomLevel"; const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevelForClustering = @"MGLShapeSourceOptionMaximumZoomLevelForClustering"; const MGLShapeSourceOption MGLShapeSourceOptionMinimumZoomLevel = @"MGLShapeSourceOptionMinimumZoomLevel"; @@ -84,6 +86,57 @@ mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NSDictionary<MGLShap geoJSONOptions.cluster = value.boolValue; } + if (NSDictionary *value = options[MGLShapeSourceOptionClusterProperties]) { + if (![value isKindOfClass:[NSDictionary<NSString *, NSArray *> class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionClusterProperties must be an NSDictionary with an NSString as a key and an array containing two NSExpression objects as a value."]; + } + + NSEnumerator *stringEnumerator = [value keyEnumerator]; + NSString *key; + + while (key = [stringEnumerator nextObject]) { + NSArray *expressionsArray = value[key]; + if (![expressionsArray isKindOfClass:[NSArray class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionClusterProperties dictionary member value must be an array containing two objects."]; + } + // Check that the array has 2 values. One should be a the reduce expression and one should be the map expression. + if ([expressionsArray count] != 2) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionClusterProperties member value requires array of two objects."]; + } + + // reduceExpression should be a valid NSExpression + NSExpression *reduceExpression = expressionsArray[0]; + if (![reduceExpression isKindOfClass:[NSExpression class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionClusterProperties array value requires two expression objects."]; + } + auto reduce = MGLClusterPropertyFromNSExpression(reduceExpression); + if (!reduce) { + [NSException raise:NSInvalidArgumentException + format:@"Failed to convert MGLShapeSourceOptionClusterProperties reduce expression."]; + } + + // mapExpression should be a valid NSExpression + NSExpression *mapExpression = expressionsArray[1]; + if (![mapExpression isKindOfClass:[NSExpression class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionClusterProperties member value must contain a valid NSExpression."]; + } + auto map = MGLClusterPropertyFromNSExpression(mapExpression); + if (!map) { + [NSException raise:NSInvalidArgumentException + format:@"Failed to convert MGLShapeSourceOptionClusterProperties map expression."]; + } + + std::string keyString = std::string([key UTF8String]); + + geoJSONOptions.clusterProperties.emplace(keyString, std::make_pair(std::move(map), std::move(reduce))); + } + } + if (NSNumber *value = options[MGLShapeSourceOptionLineDistanceMetrics]) { if (![value isKindOfClass:[NSNumber class]]) { [NSException raise:NSInvalidArgumentException diff --git a/platform/darwin/src/MGLStyleValue.mm b/platform/darwin/src/MGLStyleValue.mm index 5103b5f5cf..01ad108d7f 100644 --- a/platform/darwin/src/MGLStyleValue.mm +++ b/platform/darwin/src/MGLStyleValue.mm @@ -44,3 +44,16 @@ id MGLJSONObjectFromMBGLValue(const mbgl::Value &value) { id MGLJSONObjectFromMBGLExpression(const mbgl::style::expression::Expression &mbglExpression) { return MGLJSONObjectFromMBGLValue(mbglExpression.serialize()); } + + +std::unique_ptr<mbgl::style::expression::Expression> MGLClusterPropertyFromNSExpression(NSExpression *expression) { + if (!expression) { + return nullptr; + } + + NSArray *jsonExpression = expression.mgl_jsonExpressionObject; + + auto expr = mbgl::style::expression::dsl::createExpression(mbgl::style::conversion::makeConvertible(jsonExpression)); + + return expr; +} diff --git a/platform/darwin/src/MGLStyleValue_Private.h b/platform/darwin/src/MGLStyleValue_Private.h index 376bf5e73b..82ce232c6b 100644 --- a/platform/darwin/src/MGLStyleValue_Private.h +++ b/platform/darwin/src/MGLStyleValue_Private.h @@ -12,12 +12,15 @@ #include <mbgl/style/conversion/color_ramp_property_value.hpp> #include <mbgl/style/conversion/property_value.hpp> #include <mbgl/style/conversion/position.hpp> +#include <mbgl/style/expression/dsl.hpp> #import <mbgl/style/transition_options.hpp> #import <mbgl/style/types.hpp> #import <mbgl/util/enum.hpp> #include <mbgl/util/interpolate.hpp> +#include <memory> + #if TARGET_OS_IPHONE #import "UIColor+MGLAdditions.h" #else @@ -45,6 +48,8 @@ NS_INLINE mbgl::style::TransitionOptions MGLOptionsFromTransition(MGLTransition return options; } +std::unique_ptr<mbgl::style::expression::Expression> MGLClusterPropertyFromNSExpression(NSExpression *expression); + id MGLJSONObjectFromMBGLExpression(const mbgl::style::expression::Expression &mbglExpression); template <typename MBGLType, typename ObjCType, typename MBGLElement = MBGLType, typename ObjCEnum = ObjCType> diff --git a/platform/darwin/src/NSExpression+MGLAdditions.h b/platform/darwin/src/NSExpression+MGLAdditions.h index 2a33367e9c..2109310e69 100644 --- a/platform/darwin/src/NSExpression+MGLAdditions.h +++ b/platform/darwin/src/NSExpression+MGLAdditions.h @@ -86,6 +86,13 @@ FOUNDATION_EXTERN MGL_EXPORT const MGLExpressionInterpolationMode MGLExpressionI /** `NSExpression` variable that corresponds to the + <a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/#accumulated"><code>id</code></a> + expression operator in the Mapbox Style Specification. + */ +@property (class, nonatomic, readonly) NSExpression *featureAccumulatedVariableExpression; + +/** + `NSExpression` variable that corresponds to the <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-properties"><code>properties</code></a> expression operator in the Mapbox Style Specification. */ diff --git a/platform/darwin/src/NSExpression+MGLAdditions.mm b/platform/darwin/src/NSExpression+MGLAdditions.mm index 2ca4e0ed88..f139b86a88 100644 --- a/platform/darwin/src/NSExpression+MGLAdditions.mm +++ b/platform/darwin/src/NSExpression+MGLAdditions.mm @@ -553,6 +553,10 @@ const MGLExpressionInterpolationMode MGLExpressionInterpolationModeCubicBezier = return [NSExpression expressionForVariable:@"lineProgress"]; } ++ (NSExpression *)featureAccumulatedVariableExpression { + return [NSExpression expressionForVariable:@"featureAccumulated"]; +} + + (NSExpression *)geometryTypeVariableExpression { return [NSExpression expressionForVariable:@"geometryType"]; } @@ -648,7 +652,6 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { @"let": @"MGL_LET", }; }); - if (!object || object == [NSNull null]) { return [NSExpression expressionForConstantValue:nil]; } @@ -667,11 +670,10 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { }]; return [NSExpression expressionForConstantValue:dictionary]; } - if ([object isKindOfClass:[NSArray class]]) { NSArray *array = (NSArray *)object; NSString *op = array.firstObject; - + if (![op isKindOfClass:[NSString class]]) { NSArray *subexpressions = MGLSubexpressionsWithJSONObjects(array); return [NSExpression expressionForFunction:@"MGL_FUNCTION" arguments:subexpressions]; @@ -839,6 +841,8 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { return NSExpression.heatmapDensityVariableExpression; } else if ([op isEqualToString:@"line-progress"]) { return NSExpression.lineProgressVariableExpression; + } else if ([op isEqualToString:@"accumulated"]) { + return NSExpression.featureAccumulatedVariableExpression; } else if ([op isEqualToString:@"geometry-type"]) { return NSExpression.geometryTypeVariableExpression; } else if ([op isEqualToString:@"id"]) { @@ -961,6 +965,9 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { if ([self.variable isEqualToString:@"zoomLevel"]) { return @[@"zoom"]; } + if ([self.variable isEqualToString:@"featureAccumulated"]) { + return @[@"accumulated"]; + } if ([self.variable isEqualToString:@"geometryType"]) { return @[@"geometry-type"]; } @@ -1046,6 +1053,8 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { case NSFunctionExpressionType: { NSString *function = self.function; + + BOOL hasCollectionProperty = !( ! [self.arguments.firstObject isKindOfClass: [NSExpression class]] || self.arguments.firstObject.expressionType != NSAggregateExpressionType || self.arguments.firstObject.expressionType == NSSubqueryExpressionType); NSString *op = MGLExpressionOperatorsByFunctionNames[function]; if (op) { NSArray *arguments = self.arguments.mgl_jsonExpressionObject; @@ -1057,16 +1066,31 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { NSExpression *count = [NSExpression expressionForFunction:@"count:" arguments:self.arguments]; return [NSExpression expressionForFunction:@"divide:by:" arguments:@[sum, count]].mgl_jsonExpressionObject; } else if ([function isEqualToString:@"sum:"]) { - NSArray *arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; + NSArray *arguments; + if (hasCollectionProperty) { + arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; + } else { + arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"]; + } return [@[@"+"] arrayByAddingObjectsFromArray:arguments]; } else if ([function isEqualToString:@"count:"]) { NSArray *arguments = self.arguments.firstObject.mgl_jsonExpressionObject; return @[@"length", arguments]; } else if ([function isEqualToString:@"min:"]) { - NSArray *arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; + NSArray *arguments; + if (!hasCollectionProperty) { + arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"]; + } else { + arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; + } return [@[@"min"] arrayByAddingObjectsFromArray:arguments]; } else if ([function isEqualToString:@"max:"]) { - NSArray *arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; + NSArray *arguments; + if (!hasCollectionProperty) { + arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"]; + } else { + arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; + } return [@[@"max"] arrayByAddingObjectsFromArray:arguments]; } else if ([function isEqualToString:@"exp:"]) { return [NSExpression expressionForFunction:@"raise:toPower:" arguments:@[@(M_E), self.arguments.firstObject]].mgl_jsonExpressionObject; @@ -1074,7 +1098,12 @@ NSArray *MGLSubexpressionsWithJSONObjects(NSArray *objects) { return [NSExpression expressionWithFormat:@"%@ - modulus:by:(%@, 1)", self.arguments.firstObject, self.arguments.firstObject].mgl_jsonExpressionObject; } else if ([function isEqualToString:@"mgl_join:"]) { - NSArray *arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; + NSArray *arguments; + if (!hasCollectionProperty) { + arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"]; + } else { + arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; + } return [@[@"concat"] arrayByAddingObjectsFromArray:arguments]; } else if ([function isEqualToString:@"stringByAppendingString:"]) { NSArray *arguments = self.arguments.mgl_jsonExpressionObject; |