Add iOS bindings for cluster properties (#15515)
This commit is contained in:
@@ -41,6 +41,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -84,6 +84,13 @@ FOUNDATION_EXTERN MGL_EXPORT const MGLExpressionInterpolationMode MGLExpressionI
|
||||
*/
|
||||
@property (class, nonatomic, readonly) NSExpression *featureIdentifierVariableExpression;
|
||||
|
||||
/**
|
||||
`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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -554,7 +554,39 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate {
|
||||
|
||||
XCTAssertNotNil(attributedExpression)
|
||||
}
|
||||
|
||||
|
||||
func testMGLShapeSourceOptionClusterProperties() {
|
||||
//#-example-code
|
||||
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]
|
||||
//#-end-example-code
|
||||
let geoJSON: [String: Any] = [
|
||||
"type" : "Feature",
|
||||
"geometry" : [
|
||||
"coordinates" : [
|
||||
-77.00896639534831,
|
||||
38.87031006108791,
|
||||
0.0
|
||||
],
|
||||
"type" : "Point"
|
||||
],
|
||||
"properties" : [
|
||||
"cluster" : true,
|
||||
"cluster_id" : 123,
|
||||
"point_count" : 4567,
|
||||
]
|
||||
]
|
||||
|
||||
let clusterShapeData = try! JSONSerialization.data(withJSONObject: geoJSON, options: [])
|
||||
let shape = try! MGLShape(data: clusterShapeData, encoding: String.Encoding.utf8.rawValue)
|
||||
let source = MGLShapeSource(identifier: "source", shape: shape, options: options)
|
||||
mapView.style?.addSource(source)
|
||||
|
||||
}
|
||||
// For testMGLMapView().
|
||||
func myCustomFunction() {}
|
||||
}
|
||||
|
||||
@@ -179,6 +179,13 @@ using namespace std::string_literals;
|
||||
NSMutableDictionary *context = [@{@"lineProgress": @1} mutableCopy];
|
||||
XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], @1);
|
||||
}
|
||||
{
|
||||
NSExpression *expression = [NSExpression expressionForVariable:@"featureAccumulated"];
|
||||
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"accumulated"]);
|
||||
XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$featureAccumulated"].mgl_jsonExpressionObject, @[@"accumulated"]);
|
||||
XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:@[@"accumulated"]], expression);
|
||||
}
|
||||
|
||||
{
|
||||
NSExpression *expression = [NSExpression expressionForVariable:@"geometryType"];
|
||||
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"geometry-type"]);
|
||||
@@ -380,6 +387,26 @@ using namespace std::string_literals;
|
||||
XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 + 1"].mgl_jsonExpressionObject, jsonExpression);
|
||||
XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression);
|
||||
}
|
||||
{
|
||||
NSExpression *testExpression = [NSExpression expressionWithFormat:@"sum:({1, 1, 2})"];
|
||||
NSExpression *expression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForAggregate:@[MGLConstantExpression(@1), MGLConstantExpression(@1), MGLConstantExpression(@2)]]]];
|
||||
|
||||
NSArray *jsonExpression = @[@"+", @1, @1, @2];
|
||||
|
||||
XCTAssertEqualObjects(testExpression.mgl_jsonExpressionObject, jsonExpression);
|
||||
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
|
||||
XCTAssertEqualObjects(expression, testExpression);
|
||||
}
|
||||
{
|
||||
NSExpression *expression = [NSExpression expressionForFunction:@"sum:" arguments:@[MGLConstantExpression(@1), MGLConstantExpression(@1), MGLConstantExpression(@2)]];
|
||||
NSArray *jsonExpression = @[@"+", @1, @1, @2];
|
||||
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
|
||||
|
||||
// - [NSExpression expressionWithMGLJSONObject:] creates an expression with an aggregate expression as an argument. This is not equal to an expression with an array of expressions as an argument. For testing purposes, we will compare their operands and arrays of expressions.
|
||||
NSExpression *aggregateExpression = [NSExpression expressionWithMGLJSONObject:jsonExpression];
|
||||
XCTAssertEqualObjects(aggregateExpression.operand, expression.operand);
|
||||
XCTAssertEqualObjects(aggregateExpression.arguments.firstObject.collection, expression.arguments);
|
||||
}
|
||||
{
|
||||
NSArray *threeArguments = @[MGLConstantExpression(@1), MGLConstantExpression(@1), MGLConstantExpression(@1)];
|
||||
NSExpression *expression = [NSExpression expressionForFunction:@"add:to:" arguments:threeArguments];
|
||||
@@ -417,6 +444,24 @@ using namespace std::string_literals;
|
||||
// NSExpression lacks a shorthand operator for modulus.
|
||||
XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], expression);
|
||||
}
|
||||
{
|
||||
NSExpression *expression = [NSExpression expressionForFunction:@"max:" arguments:arguments];
|
||||
NSArray *jsonExpression = @[@"max", @1, @1];
|
||||
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
|
||||
|
||||
NSExpression *aggregateExpression = [NSExpression expressionWithMGLJSONObject:jsonExpression];
|
||||
XCTAssertEqualObjects(aggregateExpression.operand, expression.operand);
|
||||
XCTAssertEqualObjects(aggregateExpression.arguments.firstObject.collection, expression.arguments);
|
||||
}
|
||||
{
|
||||
NSExpression *expression = [NSExpression expressionForFunction:@"min:" arguments:arguments];
|
||||
NSArray *jsonExpression = @[@"min", @1, @1];
|
||||
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
|
||||
|
||||
NSExpression *aggregateExpression = [NSExpression expressionWithMGLJSONObject:jsonExpression];
|
||||
XCTAssertEqualObjects(aggregateExpression.operand, expression.operand);
|
||||
XCTAssertEqualObjects(aggregateExpression.arguments.firstObject.collection, expression.arguments);
|
||||
}
|
||||
{
|
||||
NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" arguments:@[MGLConstantExpression(@1.5)]];
|
||||
NSArray *jsonExpression = @[@"ceil", @1.5];
|
||||
@@ -621,6 +666,16 @@ using namespace std::string_literals;
|
||||
XCTAssertEqualObjects([aftermarketExpression expressionValueWithObject:nil context:nil], @"OldMacDonald");
|
||||
XCTAssertEqualObjects([NSExpression expressionWithMGLJSONObject:jsonExpression], aftermarketExpression);
|
||||
}
|
||||
{
|
||||
NSExpression *expression = [NSExpression expressionForFunction:@"mgl_join:" arguments:@[@"Old", @"MacDonald"]];
|
||||
NSExpression *aftermarketExpression = [NSExpression expressionWithFormat:@"mgl_join({'Old', 'MacDonald'})"];
|
||||
NSArray *jsonExpression = @[@"concat", @"Old", @"MacDonald"];
|
||||
XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression);
|
||||
|
||||
XCTAssertEqualObjects(aftermarketExpression.mgl_jsonExpressionObject, expression.mgl_jsonExpressionObject);
|
||||
NSExpression *aggregateExpression = [NSExpression expressionWithMGLJSONObject:jsonExpression];
|
||||
XCTAssertEqualObjects(aggregateExpression.operand, expression.operand);
|
||||
}
|
||||
{
|
||||
NSExpression *expression = [NSExpression expressionForFunction:@"uppercase:" arguments:arguments];
|
||||
NSArray *jsonExpression = @[@"upcase", @"MacDonald"];
|
||||
|
||||
@@ -13,8 +13,12 @@
|
||||
@implementation MGLShapeSourceTests
|
||||
|
||||
- (void)testGeoJSONOptionsFromDictionary {
|
||||
NSExpression *reduceExpression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForKeyPath:@"featureAccumulated"], [NSExpression expressionForKeyPath:@"sumValue"]]];
|
||||
NSExpression *mapExpression = [NSExpression expressionForKeyPath:@"mag"];
|
||||
NSArray *clusterPropertyArray = @[reduceExpression, mapExpression];
|
||||
NSDictionary *options = @{MGLShapeSourceOptionClustered: @YES,
|
||||
MGLShapeSourceOptionClusterRadius: @42,
|
||||
MGLShapeSourceOptionClusterProperties: @{@"sumValue": clusterPropertyArray},
|
||||
MGLShapeSourceOptionMaximumZoomLevelForClustering: @98,
|
||||
MGLShapeSourceOptionMaximumZoomLevel: @99,
|
||||
MGLShapeSourceOptionBuffer: @1976,
|
||||
@@ -29,6 +33,7 @@
|
||||
XCTAssertEqual(mbglOptions.buffer, 1976);
|
||||
XCTAssertEqual(mbglOptions.tolerance, 0.42);
|
||||
XCTAssertTrue(mbglOptions.lineMetrics);
|
||||
XCTAssertTrue(!mbglOptions.clusterProperties.empty());
|
||||
|
||||
options = @{MGLShapeSourceOptionClustered: @"number 1"};
|
||||
XCTAssertThrows(MGLGeoJSONOptionsFromDictionary(options));
|
||||
|
||||
@@ -14,6 +14,10 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
|
||||
|
||||
* Improved rendering performance for the styles with multiple sources ([#15756](https://github.com/mapbox/mapbox-gl-native/pull/15756))
|
||||
|
||||
### Styles and rendering
|
||||
|
||||
* Added an `MGLShapeSourceOptionClusterProperties` option that allows styling individual clusters based on aggregated feature data. ([#15515](https://github.com/mapbox/mapbox-gl-native/pull/15515))
|
||||
|
||||
### Other changes
|
||||
|
||||
* Added `-[MGLMapSnapshotOverlay coordinateForPoint:]` and `-[MGLMapSnapshotOverlay pointForCoordinate:]` to convert between context and map coordinates, mirroring those of `MGLMapSnapshot`. ([#15746](https://github.com/mapbox/mapbox-gl-native/pull/15746))
|
||||
|
||||
@@ -99,6 +99,7 @@ GeoJSONSource::Impl::Impl(const Impl& other, const GeoJSON& geoJSON)
|
||||
Feature feature;
|
||||
clusterOptions.map = [&](const PropertyMap& properties) -> PropertyMap {
|
||||
PropertyMap ret{};
|
||||
if (properties.empty()) return ret;
|
||||
for (const auto& p : options.clusterProperties) {
|
||||
feature.properties = properties;
|
||||
ret[p.first] = evaluateFeature<Value>(feature, p.second.first);
|
||||
|
||||
Reference in New Issue
Block a user