From 957296b2fbbac8803025d50ccc57c7addd704567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Fri, 28 Feb 2020 18:46:10 -0800 Subject: [PATCH 1/5] [ios, macos] Convert geometries to shapes, not features Fixed an issue where GeoJSON geometries were converted to MGLFeature instances instead of MGLShape objects. --- platform/darwin/src/MGLFeature.mm | 71 +++++++++++++++++-------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/platform/darwin/src/MGLFeature.mm b/platform/darwin/src/MGLFeature.mm index df6b1bffea..f8806b3f3c 100644 --- a/platform/darwin/src/MGLFeature.mm +++ b/platform/darwin/src/MGLFeature.mm @@ -352,19 +352,20 @@ - (NSDictionary *)geoJSONDictionary { class GeometryEvaluator { private: const mbgl::PropertyMap *shared_properties; + const bool is_in_feature; public: - GeometryEvaluator(const mbgl::PropertyMap *properties = nullptr): - shared_properties(properties) + GeometryEvaluator(const mbgl::PropertyMap *properties = nullptr, const bool isInFeature = false): + shared_properties(properties), + is_in_feature(isInFeature) {} - MGLShape * operator()(const mbgl::EmptyGeometry &) const { - MGLEmptyFeature *feature = [[MGLEmptyFeature alloc] init]; - return feature; + MGLShape * operator()(const mbgl::EmptyGeometry &) const { + return is_in_feature ? [[MGLEmptyFeature alloc] init] : [[MGLShape alloc] init]; } - MGLShape * operator()(const mbgl::Point &geometry) const { - Class pointFeatureClass = [MGLPointFeature class]; + MGLShape * operator()(const mbgl::Point &geometry) const { + Class shapeClass = is_in_feature ? [MGLPointFeature class] : [MGLPointAnnotation class]; // If we're dealing with a cluster, we should change the class type. // This could be generic and build the subclass at runtime if it turns @@ -375,32 +376,34 @@ - (NSDictionary *)geoJSONDictionary { auto clusterValue = clusterIt->second; if (clusterValue.template is()) { if (clusterValue.template get()) { - pointFeatureClass = [MGLPointFeatureCluster class]; + shapeClass = [MGLPointFeatureCluster class]; } } } } - MGLPointFeature *feature = [[pointFeatureClass alloc] init]; - feature.coordinate = toLocationCoordinate2D(geometry); - return feature; + MGLPointAnnotation *shape = [[shapeClass alloc] init]; + shape.coordinate = toLocationCoordinate2D(geometry); + return shape; } - MGLShape * operator()(const mbgl::LineString &geometry) const { + MGLShape * operator()(const mbgl::LineString &geometry) const { std::vector coordinates = toLocationCoordinates2D(geometry); - return [MGLPolylineFeature polylineWithCoordinates:&coordinates[0] count:coordinates.size()]; + Class shapeClass = is_in_feature ? [MGLPolylineFeature class] : [MGLPolyline class]; + return [shapeClass polylineWithCoordinates:&coordinates[0] count:coordinates.size()]; } - MGLShape * operator()(const mbgl::Polygon &geometry) const { - return toShape(geometry); + MGLShape * operator()(const mbgl::Polygon &geometry) const { + return toShape(geometry, is_in_feature); } - MGLShape * operator()(const mbgl::MultiPoint &geometry) const { + MGLShape * operator()(const mbgl::MultiPoint &geometry) const { std::vector coordinates = toLocationCoordinates2D(geometry); - return [[MGLPointCollectionFeature alloc] initWithCoordinates:&coordinates[0] count:coordinates.size()]; + Class shapeClass = is_in_feature ? [MGLPointCollectionFeature class] : [MGLPointCollection class]; + return [[shapeClass alloc] initWithCoordinates:&coordinates[0] count:coordinates.size()]; } - MGLShape * operator()(const mbgl::MultiLineString &geometry) const { + MGLShape * operator()(const mbgl::MultiLineString &geometry) const { NSMutableArray *polylines = [NSMutableArray arrayWithCapacity:geometry.size()]; for (auto &lineString : geometry) { std::vector coordinates = toLocationCoordinates2D(lineString); @@ -408,26 +411,29 @@ - (NSDictionary *)geoJSONDictionary { [polylines addObject:polyline]; } - return [MGLMultiPolylineFeature multiPolylineWithPolylines:polylines]; + Class shapeClass = is_in_feature ? [MGLMultiPolylineFeature class] : [MGLMultiPolyline class]; + return [shapeClass multiPolylineWithPolylines:polylines]; } - MGLShape * operator()(const mbgl::MultiPolygon &geometry) const { + MGLShape * operator()(const mbgl::MultiPolygon &geometry) const { NSMutableArray *polygons = [NSMutableArray arrayWithCapacity:geometry.size()]; for (auto &polygon : geometry) { - [polygons addObject:toShape(polygon)]; + [polygons addObject:toShape(polygon, false)]; } - return [MGLMultiPolygonFeature multiPolygonWithPolygons:polygons]; + Class shapeClass = is_in_feature ? [MGLMultiPolygonFeature class] : [MGLMultiPolygon class]; + return [shapeClass multiPolygonWithPolygons:polygons]; } - MGLShape * operator()(const mapbox::geometry::geometry_collection &collection) const { + MGLShape * operator()(const mapbox::geometry::geometry_collection &collection) const { NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()]; for (auto &geometry : collection) { // This is very much like the transformation that happens in MGLFeaturesFromMBGLFeatures(), but these are raw geometries with no associated feature IDs or attributes. - MGLShape *shape = mapbox::geometry::geometry::visit(geometry, *this); + MGLShape *shape = mapbox::geometry::geometry::visit(geometry, *this); [shapes addObject:shape]; } - return [MGLShapeCollectionFeature shapeCollectionWithShapes:shapes]; + Class shapeClass = is_in_feature ? [MGLShapeCollectionFeature class] : [MGLShapeCollection class]; + return [shapeClass shapeCollectionWithShapes:shapes]; } private: @@ -442,8 +448,8 @@ static CLLocationCoordinate2D toLocationCoordinate2D(const mbgl::Point &point return coordinates; } - template - static U *toShape(const mbgl::Polygon &geometry) { + template + static U *toShape(const mbgl::Polygon &geometry, const bool isInFeature) { auto &linearRing = geometry.front(); std::vector coordinates = toLocationCoordinates2D(linearRing); NSMutableArray *innerPolygons; @@ -457,16 +463,17 @@ static CLLocationCoordinate2D toLocationCoordinate2D(const mbgl::Point &point } } - return [U polygonWithCoordinates:&coordinates[0] count:coordinates.size() interiorPolygons:innerPolygons]; + Class shapeClass = isInFeature ? [V class] : [U class]; + return [shapeClass polygonWithCoordinates:&coordinates[0] count:coordinates.size() interiorPolygons:innerPolygons]; } }; template class GeoJSONEvaluator { public: - MGLShape * operator()(const mbgl::Geometry &geometry) const { + MGLShape * operator()(const mbgl::Geometry &geometry) const { GeometryEvaluator evaluator; - MGLShape *shape = mapbox::geometry::geometry::visit(geometry, evaluator); + MGLShape *shape = mapbox::geometry::geometry::visit(geometry, evaluator); return shape; } @@ -507,8 +514,8 @@ static CLLocationCoordinate2D toLocationCoordinate2D(const mbgl::Point &point ValueEvaluator evaluator; attributes[@(pair.first.c_str())] = mbgl::Value::visit(value, evaluator); } - GeometryEvaluator evaluator(&feature.properties); - MGLShape *shape = mapbox::geometry::geometry::visit(feature.geometry, evaluator); + GeometryEvaluator evaluator(&feature.properties, true); + MGLShape *shape = (MGLShape *)mapbox::geometry::geometry::visit(feature.geometry, evaluator); if (!feature.id.is()) { shape.identifier = mbgl::FeatureIdentifier::visit(feature.id, ValueEvaluator()); } From 93f5290c0e1345a444182c1cab8c6d2a64bd3a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Fri, 28 Feb 2020 18:50:46 -0800 Subject: [PATCH 2/5] [ios, macos] SELF IN MGLShape expressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added support for the expression syntax “SELF IN shape” or “shape CONTAINS SELF”, which is interpreted as a “within” expression in JSON format. --- .../docs/guides/For Style Authors.md.ejs | 1 + .../docs/guides/Predicates and Expressions.md | 12 +++ platform/darwin/src/MGLShape.h | 6 ++ .../src/NSComparisonPredicate+MGLAdditions.mm | 3 + .../darwin/src/NSExpression+MGLAdditions.mm | 28 ++++++- .../darwin/src/NSPredicate+MGLAdditions.mm | 8 ++ platform/darwin/test/MGLPredicateTests.mm | 76 ++++++++++++++++++- platform/ios/CHANGELOG.md | 3 +- platform/ios/docs/guides/For Style Authors.md | 1 + platform/macos/CHANGELOG.md | 3 +- .../macos/docs/guides/For Style Authors.md | 1 + 11 files changed, 136 insertions(+), 6 deletions(-) diff --git a/platform/darwin/docs/guides/For Style Authors.md.ejs b/platform/darwin/docs/guides/For Style Authors.md.ejs index 0cba86d719..120982a2af 100644 --- a/platform/darwin/docs/guides/For Style Authors.md.ejs +++ b/platform/darwin/docs/guides/For Style Authors.md.ejs @@ -361,6 +361,7 @@ In style specification | Method, function, or predicate type | Format string syn `case` | `+[NSExpression expressionForConditional:trueExpression:falseExpression:]` or `MGL_IF` or `+[NSExpression mgl_expressionForConditional:trueExpression:falseExpresssion:]` | `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` or `+[NSExpression mgl_expressionForMatchingExpression:inDictionary:defaultExpression:]` | `MGL_MATCH(x, 0, 'zero match', 1, 'one match', 'two match', 'default')` +`within` | `NSInPredicateOperatorType` | `SELF IN %@` or `%@ CONTAINS SELF` where `%@` is an `MGLShape` `interpolate` | `mgl_interpolate:withCurveType:parameters:stops:` or `+[NSExpression mgl_expressionForInterpolatingExpression:withCurveType:parameters:stops:]` | `step` | `mgl_step:from:stops:` or `+[NSExpression mgl_expressionForSteppingExpression:fromExpression:stops:]` | `let` | `mgl_expressionWithContext:` | `MGL_LET('ios', 11, 'macos', 10.13, $ios + $macos)` diff --git a/platform/darwin/docs/guides/Predicates and Expressions.md b/platform/darwin/docs/guides/Predicates and Expressions.md index 8dd3664f76..67d39d2124 100644 --- a/platform/darwin/docs/guides/Predicates and Expressions.md +++ b/platform/darwin/docs/guides/Predicates and Expressions.md @@ -55,6 +55,18 @@ The following aggregate operators are supported: `NSInPredicateOperatorType` | `key IN { 'iOS', 'macOS', 'tvOS', 'watchOS' }` `NSContainsPredicateOperatorType` | `{ 'iOS', 'macOS', 'tvOS', 'watchOS' } CONTAINS key` +You can use the `IN` and `CONTAINS` operators to test whether a value appears in a collection, whether a string is a substring of a larger string, or whether the evaluated feature (`SELF`) lies within a given `MGLShape` or `MGLFeature`. For example, to show one delicious local chain of sandwich shops, but not similarly named steakhouses and pizzerias: + +```objc +MGLPolygon *cincinnati = [MGLPolygon polygonWithCoordinates:cincinnatiCoordinates count:sizeof(cincinnatiCoordinates) / sizeof(cincinnatiCoordinates[0])]; +deliLayer.predicate = [NSPredicate predicateWithFormat:@"class = 'food_and_drink' AND name CONTAINS 'Izzy' AND SELF IN %@", cincinnati]; +``` + +```swift +let cincinnati = MGLPolygon(coordinates: &cincinnatiCoordinates, count: UInt(cincinnatiCoordinates.count)) +deliLayer.predicate = NSPredicate(format: "class = 'food_and_drink' AND name CONTAINS 'Izzy' AND SELF IN %@", cincinnati) +``` + The following combinations of comparison operators and modifiers are supported: `NSComparisonPredicateModifier` | `NSPredicateOperatorType` | Format string syntax diff --git a/platform/darwin/src/MGLShape.h b/platform/darwin/src/MGLShape.h index 6954045fd7..317aefe37f 100644 --- a/platform/darwin/src/MGLShape.h +++ b/platform/darwin/src/MGLShape.h @@ -29,6 +29,12 @@ NS_ASSUME_NONNULL_BEGIN shapes collectively using a concrete instance of `MGLVectorStyleLayer`. Alternatively, you can add some kinds of shapes directly to a map view as annotations or overlays. + + You can filter the features in a `MGLVectorStyleLayer` or vary their layout or + paint attributes based on the features’ geographies. Pass an `MGLShape` into an + `NSPredicate` with the format `SELF IN %@` or `%@ CONTAINS SELF` and set the + `MGLVectorStyleLayer.predicate` property to that predicate, or set a layout or + paint attribute to a similarly formatted `NSExpression`. */ MGL_EXPORT @interface MGLShape : NSObject diff --git a/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm b/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm index 8c7afb5722..b2f161343d 100644 --- a/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSComparisonPredicate+MGLAdditions.mm @@ -161,6 +161,9 @@ - (id)mgl_jsonExpressionObject { return @[op, leftHandPredicate.mgl_jsonExpressionObject, rightHandPredicate.mgl_jsonExpressionObject]; } case NSInPredicateOperatorType: { + if (self.leftExpression.expressionType == NSEvaluatedObjectExpressionType) { + return @[@"within", self.rightExpression.mgl_jsonExpressionObject]; + } // An “in” expression comparing two string literals is unfortunately // misinterpreted as a legacy “in” filter due to ambiguity. Wrap one // argument in a “literal” expression to force an expression. diff --git a/platform/darwin/src/NSExpression+MGLAdditions.mm b/platform/darwin/src/NSExpression+MGLAdditions.mm index 80008aa69b..48c7837981 100644 --- a/platform/darwin/src/NSExpression+MGLAdditions.mm +++ b/platform/darwin/src/NSExpression+MGLAdditions.mm @@ -1,5 +1,6 @@ #import "MGLFoundation_Private.h" #import "MGLGeometry_Private.h" +#import "MGLShape_Private.h" #import "NSExpression+MGLPrivateAdditions.h" #import "MGLTypes.h" @@ -539,6 +540,14 @@ - (id)mgl_has:(id)element { @end +@implementation MGLShape (MGLExpressionAdditions) + +- (id)mgl_jsonExpressionObject { + return self.geoJSONDictionary; +} + +@end + @implementation NSExpression (MGLAdditions) + (NSExpression *)zoomLevelVariableExpression { @@ -659,11 +668,24 @@ + (instancetype)expressionWithMGLJSONObject:(id)object { if ([object isKindOfClass:[NSString class]] || [object isKindOfClass:[NSNumber class]] || [object isKindOfClass:[NSValue class]] || - [object isKindOfClass:[MGLColor class]]) { + [object isKindOfClass:[MGLColor class]] || + [object isKindOfClass:[MGLShape class]]) { return [NSExpression expressionForConstantValue:object]; } if ([object isKindOfClass:[NSDictionary class]]) { + if (object[@"type"]) { + NSError *error; + NSData *shapeData = [NSJSONSerialization dataWithJSONObject:object options:0 error:&error]; + MGLShape *shape; + if (shapeData && !error) { + shape = [MGLShape shapeWithData:shapeData encoding:NSUTF8StringEncoding error:&error]; + } + if (shape && !error) { + return [NSExpression expressionForConstantValue:shape]; + } + } + NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:[object count]]; [object enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { dictionary[key] = [NSExpression expressionWithMGLJSONObject:obj]; @@ -1031,6 +1053,10 @@ - (id)mgl_jsonExpressionObject { } return @[jsonObject, attributedDictionary]; } + if ([constantValue isKindOfClass:[MGLShape class]]) { + MGLShape *shape = (MGLShape *)constantValue; + return shape.geoJSONDictionary; + } return self.constantValue; } diff --git a/platform/darwin/src/NSPredicate+MGLAdditions.mm b/platform/darwin/src/NSPredicate+MGLAdditions.mm index 28cc6c6f08..49974c243a 100644 --- a/platform/darwin/src/NSPredicate+MGLAdditions.mm +++ b/platform/darwin/src/NSPredicate+MGLAdditions.mm @@ -155,6 +155,14 @@ + (instancetype)predicateWithMGLJSONObject:(id)object { NSArray *subpredicates = MGLSubpredicatesWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); return [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates]; } + if ([op isEqualToString:@"within"]) { + NSArray *subexpressions = MGLSubexpressionsWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); + return [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForEvaluatedObject] + rightExpression:subexpressions[0] + modifier:NSDirectPredicateModifier + type:NSInPredicateOperatorType + options:0]; + } NSExpression *expression = [NSExpression expressionWithMGLJSONObject:object]; return [NSComparisonPredicate predicateWithLeftExpression:expression diff --git a/platform/darwin/test/MGLPredicateTests.mm b/platform/darwin/test/MGLPredicateTests.mm index 181899ab06..d1bdcd40f7 100644 --- a/platform/darwin/test/MGLPredicateTests.mm +++ b/platform/darwin/test/MGLPredicateTests.mm @@ -4,6 +4,17 @@ #import "NSPredicate+MGLPrivateAdditions.h" #import "MGLValueEvaluator.h" +@implementation NSString (MGLAdditions) + +- (NSString *)stringByRemovingPointerAddresses { + return [self stringByReplacingOccurrencesOfString:@"\\b0x[0-9a-f]+\\b" + withString:@"0xdeadbeef" + options:NSRegularExpressionSearch + range:NSMakeRange(0, self.length)]; +} + +@end + @interface MGLPredicateTests : XCTestCase @end @@ -362,6 +373,69 @@ - (void)testComparisonPredicates { NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} < $featureIdentifier"]; XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); } + { + NSArray *expected = @[ + @"within", + @{ + @"type": @"Polygon", + @"coordinates": @[ + @[ + @[@0, @0], + @[@0, @1], + @[@1, @1], + @[@1, @0], + ], + ], + }, + ]; + CLLocationCoordinate2D coordinates[] = { + { .latitude = 0, .longitude = 0 }, + { .latitude = 1, .longitude = 0 }, + { .latitude = 1, .longitude = 1 }, + { .latitude = 0, .longitude = 1 }, + }; + MGLPolygon *shape = [MGLPolygon polygonWithCoordinates:coordinates count:sizeof(coordinates) / sizeof(coordinates[0])]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF IN %@", shape]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + XCTAssertEqualObjects([NSPredicate predicateWithMGLJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMGLJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = @[ + @"within", + @{ + @"type": @"Feature", + @"id": @"unit", + @"properties": @{}, + @"geometry": @{ + @"type": @"Polygon", + @"coordinates": @[ + @[ + @[@0, @0], + @[@0, @1], + @[@1, @1], + @[@1, @0], + ], + ], + }, + }, + ]; + CLLocationCoordinate2D coordinates[] = { + { .latitude = 0, .longitude = 0 }, + { .latitude = 1, .longitude = 0 }, + { .latitude = 1, .longitude = 1 }, + { .latitude = 0, .longitude = 1 }, + }; + MGLPolygonFeature *feature = [MGLPolygonFeature polygonWithCoordinates:coordinates count:sizeof(coordinates) / sizeof(coordinates[0])]; + feature.identifier = @"unit"; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ CONTAINS SELF", feature]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"SELF IN %@", feature]; + XCTAssertEqualObjects([NSPredicate predicateWithMGLJSONObject:expected], predicateAfter); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMGLJSONObject:expected] + mustRoundTrip:YES]; + } } - (void)testComparisonPredicatesWithOptions { @@ -506,7 +580,7 @@ - (void)testSymmetryWithPredicate:(NSPredicate *)forwardPredicate mustRoundTrip: if (mustRoundTrip) { // A collection of ints may turn into an aggregate of longs, for // example, so compare formats instead of the predicates themselves. - XCTAssertEqualObjects(forwardPredicate.predicateFormat, forwardPredicateAfter.predicateFormat); + XCTAssertEqualObjects(forwardPredicate.predicateFormat.stringByRemovingPointerAddresses, forwardPredicateAfter.predicateFormat.stringByRemovingPointerAddresses); } else { XCTAssertEqualObjects(forwardPredicate, forwardPredicateAfter); } diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 4b15a4412a..f3afb8a3ae 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -6,8 +6,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ### Styles and rendering -* The `IN` and `CONTAINS` predicate operators can now test whether a string is a substring of another string. ([#16162](https://github.com/mapbox/mapbox-gl-native/pull/16162), [#183](https://github.com/mapbox/mapbox-gl-native-ios/pull/183)) -* Added the `within` expression function for testing whether the evaluated feature lies within the given GeoJSON object. Use this function in expressions in style JSON or with the `MGL_FUNCTION()` syntax in an `NSExpression` format string. ([#16157](https://github.com/mapbox/mapbox-gl-native/pull/16157)) +* The `IN` and `CONTAINS` predicate operators can now test whether a string is a substring of another string or whether the evaluated feature (`SELF`) lies within a given `MGLShape` or `MGLFeature`. ([#183](https://github.com/mapbox/mapbox-gl-native-ios/pull/183), [#184](https://github.com/mapbox/mapbox-gl-native-ios/pull/184]) * Added the `MGLLineStyleLayer.lineSortKey` and `MGLFillStyleLayer.fillSortKey` properties. ([#179](https://github.com/mapbox/mapbox-gl-native-ios/pull/179), [#16194](https://github.com/mapbox/mapbox-gl-native/pull/16194), [#16220](https://github.com/mapbox/mapbox-gl-native/pull/16220)) * The `MGLSymbolStyleLayer.iconTextFit` property now respects the cap insets of any [nine-part stretchable image](https://developer.apple.com/documentation/uikit/uiimage#1658362) passed into the `-[MGLStyle setImage:forName:]` method. You can define the stretchable area in Xcode’s asset catalog or by calling the `-[UIImage resizableImageWithCapInsets:]` method. ([#182](https://github.com/mapbox/mapbox-gl-native-ios/pull/182)) * The `-[MGLStyle localizeLabelsIntoLocale:]` and `-[NSExpression mgl_expressionLocalizedIntoLocale:]` methods can now localize text into Traditional Chinese and Vietnamese. ([#173](https://github.com/mapbox/mapbox-gl-native-ios/pull/173)) diff --git a/platform/ios/docs/guides/For Style Authors.md b/platform/ios/docs/guides/For Style Authors.md index cbaa5b83c8..99cd21d867 100644 --- a/platform/ios/docs/guides/For Style Authors.md +++ b/platform/ios/docs/guides/For Style Authors.md @@ -354,6 +354,7 @@ In style specification | Method, function, or predicate type | Format string syn `case` | `+[NSExpression expressionForConditional:trueExpression:falseExpression:]` or `MGL_IF` or `+[NSExpression mgl_expressionForConditional:trueExpression:falseExpresssion:]` | `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` or `+[NSExpression mgl_expressionForMatchingExpression:inDictionary:defaultExpression:]` | `MGL_MATCH(x, 0, 'zero match', 1, 'one match', 'two match', 'default')` +`within` | `NSInPredicateOperatorType` | `SELF IN %@` or `%@ CONTAINS SELF` where `%@` is an `MGLShape` `interpolate` | `mgl_interpolate:withCurveType:parameters:stops:` or `+[NSExpression mgl_expressionForInterpolatingExpression:withCurveType:parameters:stops:]` | `step` | `mgl_step:from:stops:` or `+[NSExpression mgl_expressionForSteppingExpression:fromExpression:stops:]` | `let` | `mgl_expressionWithContext:` | `MGL_LET('ios', 11, 'macos', 10.13, $ios + $macos)` diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index e9eb05897c..c4932d2a90 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -6,8 +6,7 @@ * Added the `-[MGLMapViewDelegate mapView:shouldRemoveStyleImage:]` method for optimizing style image caching. ([#14769](https://github.com/mapbox/mapbox-gl-native/pull/14769)) * Added the `image` expression function for converting an image name into a style image. Use this function in expressions in style JSON or with the `MGL_FUNCTION()` syntax in an `NSExpression` format string. Image expressions are compatible with the `mgl_attributed:` expression function and `MGLAttributedExpression` classes for embedding icons inline in text labels. ([#15877](https://github.com/mapbox/mapbox-gl-native/pull/15877), [#15937](https://github.com/mapbox/mapbox-gl-native/pull/15937)) -* The `IN` and `CONTAINS` predicate operators can now test whether a string is a substring of another string. ([#16162](https://github.com/mapbox/mapbox-gl-native/pull/16162), [#183](https://github.com/mapbox/mapbox-gl-native-ios/pull/183)) -* Added the `within` expression function for testing whether the evaluated feature lies within the given GeoJSON object. Use this function in expressions in style JSON or with the `MGL_FUNCTION()` syntax in an `NSExpression` format string. ([#16157](https://github.com/mapbox/mapbox-gl-native/pull/16157), [#16194](https://github.com/mapbox/mapbox-gl-native/pull/16194), [#16220](https://github.com/mapbox/mapbox-gl-native/pull/16220)) +* The `IN` and `CONTAINS` predicate operators can now test whether a string is a substring of another string or whether the evaluated feature (`SELF`) lies within a given `MGLShape` or `MGLFeature`. ([#183](https://github.com/mapbox/mapbox-gl-native-ios/pull/183), [#184](https://github.com/mapbox/mapbox-gl-native-ios/pull/184]) * Added the `MGLSymbolStyleLayer.textWritingModes` layout property. This property can be set to `MGLTextWritingModeHorizontal` or `MGLTextWritingModeVertical`. ([#14932](https://github.com/mapbox/mapbox-gl-native/pull/14932)) * Added the `MGLLineStyleLayer.lineSortKey` and `MGLFillStyleLayer.fillSortKey` properties. ([#179](https://github.com/mapbox/mapbox-gl-native-ios/pull/179)) * The `MGLIdeographicFontFamilyName` Info.plist key now also accepts an array of font family names, to customize font fallback behavior. It can also be set to a Boolean value of `NO` to force the SDK to typeset CJK characters in a remote font specified by `MGLSymbolStyleLayer.textFontNames`. ([#14862](https://github.com/mapbox/mapbox-gl-native/pull/14862)) diff --git a/platform/macos/docs/guides/For Style Authors.md b/platform/macos/docs/guides/For Style Authors.md index 34af689fe6..c7bbc4fba3 100644 --- a/platform/macos/docs/guides/For Style Authors.md +++ b/platform/macos/docs/guides/For Style Authors.md @@ -347,6 +347,7 @@ In style specification | Method, function, or predicate type | Format string syn `case` | `+[NSExpression expressionForConditional:trueExpression:falseExpression:]` or `MGL_IF` or `+[NSExpression mgl_expressionForConditional:trueExpression:falseExpresssion:]` | `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` or `+[NSExpression mgl_expressionForMatchingExpression:inDictionary:defaultExpression:]` | `MGL_MATCH(x, 0, 'zero match', 1, 'one match', 'two match', 'default')` +`within` | `NSInPredicateOperatorType` | `SELF IN %@` or `%@ CONTAINS SELF` where `%@` is an `MGLShape` `interpolate` | `mgl_interpolate:withCurveType:parameters:stops:` or `+[NSExpression mgl_expressionForInterpolatingExpression:withCurveType:parameters:stops:]` | `step` | `mgl_step:from:stops:` or `+[NSExpression mgl_expressionForSteppingExpression:fromExpression:stops:]` | `let` | `mgl_expressionWithContext:` | `MGL_LET('ios', 11, 'macos', 10.13, $ios + $macos)` From 5196e37d4813901af868b95edb18de86440fb982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Wed, 11 Mar 2020 18:45:45 -0700 Subject: [PATCH 3/5] [ios, macos] Convert NSDictionary to GeoJSON --- platform/darwin/src/MGLConversion.h | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/platform/darwin/src/MGLConversion.h b/platform/darwin/src/MGLConversion.h index 3f7ffdd0c1..257b8dfc97 100644 --- a/platform/darwin/src/MGLConversion.h +++ b/platform/darwin/src/MGLConversion.h @@ -1,3 +1,5 @@ +#import "MGLShape_Private.h" + #include NS_ASSUME_NONNULL_BEGIN @@ -124,9 +126,21 @@ class ConversionTraits { } } - static optional toGeoJSON(const Holder&, Error& error) { - error = { "toGeoJSON not implemented" }; - return {}; + static optional toGeoJSON(const Holder& holder, Error& error) { + id object = holder.value; + if ([object isKindOfClass:[NSDictionary class]]) { + NSError *serializationError; + NSData *data = [NSJSONSerialization dataWithJSONObject:object options:0 error:&serializationError]; + if (serializationError) { + error = { std::string(serializationError.localizedDescription.UTF8String) }; + return {}; + } + NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + return mapbox::geojson::parse(string.UTF8String); + } else { + error = { std::string([NSString stringWithFormat:@"%@ is not a GeoJSON object.", object].UTF8String) }; + return {}; + } } private: From 7b92e2a0328aa90974398f39e7054e1cf9c66b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Wed, 11 Mar 2020 18:46:19 -0700 Subject: [PATCH 4/5] [macos] Highlight UC POIs --- platform/macos/app/MapDocument.m | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index 3431f4e258..aca8759e48 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -1055,6 +1055,22 @@ - (IBAction)manipulateStyle:(id)sender { MGLRasterStyleLayer * imageLayer = [[MGLRasterStyleLayer alloc] initWithIdentifier:@"radar-layer" source:imageSource]; [self.mapView.style addLayer:imageLayer]; + + MGLCircleStyleLayer *ucLayer = [[MGLCircleStyleLayer alloc] initWithIdentifier:@"uc" source:streetsSource]; + ucLayer.sourceLayerIdentifier = @"poi_label"; + ucLayer.predicate = [NSPredicate predicateWithFormat:@"$geometryType = 'Point'"]; + CLLocationCoordinate2D ucCoordinates[] = { + { .latitude = 39.1279, .longitude = -84.5209 }, + { .latitude = 39.1273, .longitude = -84.5112 }, + { .latitude = 39.1355, .longitude = -84.5102 }, + { .latitude = 39.1360, .longitude = -84.5212 }, + { .latitude = 39.1279, .longitude = -84.5209 }, + }; + MGLPolygon *uc = [MGLPolygon polygonWithCoordinates:ucCoordinates count:sizeof(ucCoordinates) / sizeof(ucCoordinates[0])]; + ucLayer.circleOpacity = [NSExpression expressionWithFormat:@"TERNARY(SELF IN %@, 1, 0)", uc]; + ucLayer.circleRadius = [NSExpression expressionForConstantValue:@5]; + ucLayer.circleColor = [NSExpression expressionForConstantValue:NSColor.redColor]; + [self.mapView.style addLayer:ucLayer]; } - (IBAction)dropPin:(NSMenuItem *)sender { From e27b8892152bb4abacf720e28ca4b362b3a3f170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Tue, 17 Mar 2020 02:01:03 -0700 Subject: [PATCH 5/5] [ios, macos] Sorted a changelog entry --- platform/ios/CHANGELOG.md | 2 +- platform/macos/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index f3afb8a3ae..6161d47f0d 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -22,6 +22,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ### Networking and storage * Downloaded offline packs no longer reduce the storage space available for ambient caching of tiles and other resources. ([#15622](https://github.com/mapbox/mapbox-gl-native/pull/15622)) +* Added the `-[MGLOfflineStorage preloadData:forURL:modificationDate:expirationDate:eTag:mustRevalidate:completionHandler:]` method for determining when the data is ready to retrieve from the cache. ([#188](https://github.com/mapbox/mapbox-gl-native-ios/pull/188)) * Fixed issues where an offline pack would stop downloading before completion. ([#16230](https://github.com/mapbox/mapbox-gl-native/pull/16230), [#16240](https://github.com/mapbox/mapbox-gl-native/pull/16240)) * When an offline pack encounters an HTTP 404 error, the `MGLOfflinePackUserInfoKeyError` user info key of the `MGLOfflinePackErrorNotification` now indicates the resource that could not be downloaded. ([#16240](https://github.com/mapbox/mapbox-gl-native/pull/16240)) @@ -30,7 +31,6 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Added the `MGLMapView.minimumPitch` and `MGLMapView.maximumPitch` properties to further limit how much the user or your code can tilt the map. ([#208](https://github.com/mapbox/mapbox-gl-native-ios/pull/208)) * Improved performance when continuously animating a tilted map. ([#16287](https://github.com/mapbox/mapbox-gl-native/pull/16287)) * Fixed a memory leak when zooming with any options enabled in the `MGLMapView.debugMask` property. ([#15179](https://github.com/mapbox/mapbox-gl-native/issues/15179)) -* Added the `-[MGLOfflineStorage preloadData:forURL:modificationDate:expirationDate:eTag:mustRevalidate:completionHandler:]` method for determining when the data is ready to retrieve from the cache. ([#188](https://github.com/mapbox/mapbox-gl-native-ios/pull/188)) ## 5.7.0 - February 13, 2020 diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index c4932d2a90..9aed9fdcf8 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -55,11 +55,11 @@ * Ideographic glyphs from Chinese, Japanese, and Korean are no longer downloaded by default as part of offline packs; they are instead rendered on-device, saving bandwidth and storage while improving performance. ([#14176](https://github.com/mapbox/mapbox-gl-native/pull/14176)) * Downloaded offline packs no longer reduce the storage space available for ambient caching of tiles and other resources. ([#15622](https://github.com/mapbox/mapbox-gl-native/pull/15622)) * Added the `MGLMapView.prefetchesTiles` property to configure lower-resolution tile prefetching behavior. ([#14816](https://github.com/mapbox/mapbox-gl-native/pull/14816)) +* Added the `-[MGLOfflineStorage preloadData:forURL:modificationDate:expirationDate:eTag:mustRevalidate:completionHandler:]` method for determining when the data is ready to retrieve from the cache. ([#188](https://github.com/mapbox/mapbox-gl-native-ios/pull/188)) * Fixed a crash when `-[MGLOfflinePack invalidate]` is called on different threads. ([#15582](https://github.com/mapbox/mapbox-gl-native/pull/15582)) * Fixed issues where an offline pack would stop downloading before completion. ([#16230](https://github.com/mapbox/mapbox-gl-native/pull/16230), [#16240](https://github.com/mapbox/mapbox-gl-native/pull/16240)) * When an offline pack encounters an HTTP 404 error, the `MGLOfflinePackUserInfoKeyError` user info key of the `MGLOfflinePackErrorNotification` now indicates the resource that could not be downloaded. ([#16240](https://github.com/mapbox/mapbox-gl-native/pull/16240)) * Expired resources are now fetched at a lower priority than new resources. ([#15950](https://github.com/mapbox/mapbox-gl-native/pull/15950)) -* Added the `-[MGLOfflineStorage preloadData:forURL:modificationDate:expirationDate:eTag:mustRevalidate:completionHandler:]` method for determining when the data is ready to retrieve from the cache. ([#188](https://github.com/mapbox/mapbox-gl-native-ios/pull/188)) ### Other changes