diff --git a/platform/darwin/docs/guides/Working with GeoJSON Data.md b/platform/darwin/docs/guides/Working with GeoJSON Data.md index 0fffed06d..adaf053e4 100644 --- a/platform/darwin/docs/guides/Working with GeoJSON Data.md +++ b/platform/darwin/docs/guides/Working with GeoJSON Data.md @@ -36,6 +36,16 @@ resulting shape or feature object into the the map, or you can use the object and its properties to power non-map-related functionality in your application. +To include multiple shapes in the source, create and pass an `MGLShapeCollection` or + `MGLShapeCollectionFeature` object to + `-[MGLShapeSource initWithIdentifier:shape:options:]`. Alternatively, use the + `-[MGLShapeSource initWithIdentifier:features:options:]` or + `-[MGLShapeSource initWithIdentifier:shapes:options:]` method to create a shape source + with an array. `-[MGLShapeSource initWithIdentifier:features:options:]` accepts only `MGLFeature` + instances, such as `MGLPointFeature` objects, whose attributes you can use when + applying a predicate to `MGLVectorStyleLayer` or configuring a style layer’s + appearance. + ## Extracting GeoJSON data from the map Any `MGLShape`, `MGLFeature`, or `MGLShapeCollectionFeature` object has an diff --git a/platform/darwin/src/MGLFeature.mm b/platform/darwin/src/MGLFeature.mm index 5b63beae8..3bd7eae53 100644 --- a/platform/darwin/src/MGLFeature.mm +++ b/platform/darwin/src/MGLFeature.mm @@ -184,7 +184,7 @@ MGL_DEFINE_FEATURE_IS_EQUAL(); @dynamic shapes; -+ (instancetype)shapeCollectionWithShapes:(NSArray *)shapes { ++ (instancetype)shapeCollectionWithShapes:(NS_ARRAY_OF(MGLShape *) *)shapes { return [super shapeCollectionWithShapes:shapes]; } diff --git a/platform/darwin/src/MGLShapeSource.h b/platform/darwin/src/MGLShapeSource.h index 8c8a2ab4e..7d8a64595 100644 --- a/platform/darwin/src/MGLShapeSource.h +++ b/platform/darwin/src/MGLShapeSource.h @@ -103,8 +103,7 @@ extern const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance; CLLocationCoordinate2D(latitude: 38.91, longitude: -77.04), ] let polyline = MGLPolylineFeature(coordinates: &coordinates, count: UInt(coordinates.count)) - let shape = MGLShapeCollectionFeature(shapes: [polyline]) - let source = MGLShapeSource(identifier: "lines", shape: shape, options: nil) + let source = MGLShapeSource(identifier: "lines", features: [polyline], options: nil) mapView.style?.addSource(source) ``` */ @@ -131,7 +130,9 @@ extern const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance; To specify attributes about the shape, use an instance of an `MGLShape` subclass that conforms to the `MGLFeature` protocol, such as `MGLPointFeature`. To include multiple shapes in the source, use an `MGLShapeCollection` or - `MGLShapeCollectionFeature` object. + `MGLShapeCollectionFeature` object, or use the + `-initWithIdentifier:features:options:` or + `-initWithIdentifier:shapes:options:` methods. To create a shape from GeoJSON source code, use the `+[MGLShape shapeWithData:encoding:error:]` method. @@ -143,6 +144,45 @@ extern const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance; */ - (instancetype)initWithIdentifier:(NSString *)identifier shape:(nullable MGLShape *)shape options:(nullable NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options NS_DESIGNATED_INITIALIZER; +/** + Returns a shape source with an identifier, an array of features, and a dictionary + of options for the source. + + Unlike `-initWithIdentifier:shapes:options:`, this method accepts `MGLFeature` + instances, such as `MGLPointFeature` objects, whose attributes you can use when + applying a predicate to `MGLVectorStyleLayer` or configuring a style layer’s + appearance. + + To create a shape from GeoJSON source code, use the + `+[MGLShape shapeWithData:encoding:error:]` method. + + @param identifier A string that uniquely identifies the source. + @param features An array of objects that conform to the MGLFeature protocol. + @param options An `NSDictionary` of options for this source. + @return An initialized shape source. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier features:(NS_ARRAY_OF(MGLShape *) *)features options:(nullable NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options; + +/** + Returns a shape source with an identifier, an array of shapes, and a dictionary of + options for the source. + + Any `MGLFeature` instance passed into this initializer is treated as an ordinary + shape, causing any attributes to be inaccessible to an `MGLVectorStyleLayer` when + evaluating a predicate or configuring certain layout or paint attributes. To + preserve the attributes associated with each feature, use the + `-initWithIdentifier:features:options:` method instead. + + To create a shape from GeoJSON source code, use the + `+[MGLShape shapeWithData:encoding:error:]` method. + + @param identifier A string that uniquely identifies the source. + @param shapes An array of shapes; each shape is a member of a concrete subclass of MGLShape. + @param options An `NSDictionary` of options for this source. + @return An initialized shape source. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier shapes:(NS_ARRAY_OF(MGLShape *) *)shapes options:(nullable NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options; + #pragma mark Accessing a Source’s Content /** diff --git a/platform/darwin/src/MGLShapeSource.mm b/platform/darwin/src/MGLShapeSource.mm index 0647236e8..50322f300 100644 --- a/platform/darwin/src/MGLShapeSource.mm +++ b/platform/darwin/src/MGLShapeSource.mm @@ -56,6 +56,21 @@ const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance = @"MGLSh return self; } +- (instancetype)initWithIdentifier:(NSString *)identifier features:(NS_ARRAY_OF(MGLShape *) *)features options:(nullable NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options { + for (id feature in features) { + if (![feature conformsToProtocol:@protocol(MGLFeature)]) { + [NSException raise:NSInvalidArgumentException format:@"The object %@ included in the features argument does not conform to the MGLFeature protocol.", feature]; + } + } + MGLShapeCollectionFeature *shapeCollectionFeature = [MGLShapeCollectionFeature shapeCollectionWithShapes:features]; + return [self initWithIdentifier:identifier shape:shapeCollectionFeature options:options]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier shapes:(NS_ARRAY_OF(MGLShape *) *)shapes options:(nullable NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options { + MGLShapeCollection *shapeCollection = [MGLShapeCollection shapeCollectionWithShapes:shapes]; + return [self initWithIdentifier:identifier shape:shapeCollection options:options]; +} + - (instancetype)initWithRawSource:(mbgl::style::GeoJSONSource *)rawSource { return [super initWithRawSource:rawSource]; } diff --git a/platform/darwin/test/MGLDocumentationExampleTests.swift b/platform/darwin/test/MGLDocumentationExampleTests.swift index 5f0e83b2c..d796b4e70 100644 --- a/platform/darwin/test/MGLDocumentationExampleTests.swift +++ b/platform/darwin/test/MGLDocumentationExampleTests.swift @@ -66,8 +66,7 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { CLLocationCoordinate2D(latitude: 38.91, longitude: -77.04), ] let polyline = MGLPolylineFeature(coordinates: &coordinates, count: UInt(coordinates.count)) - let shape = MGLShapeCollectionFeature(shapes: [polyline]) - let source = MGLShapeSource(identifier: "lines", shape: shape, options: nil) + let source = MGLShapeSource(identifier: "lines", features: [polyline], options: nil) mapView.style?.addSource(source) //#-end-example-code diff --git a/platform/darwin/test/MGLShapeSourceTests.mm b/platform/darwin/test/MGLShapeSourceTests.mm index 2662d4b6f..cf32b5c82 100644 --- a/platform/darwin/test/MGLShapeSourceTests.mm +++ b/platform/darwin/test/MGLShapeSourceTests.mm @@ -278,4 +278,43 @@ XCTAssert(shape.shapes.count == 6, @"Shape collection should contain 6 shapes"); } +- (void)testMGLShapeSourceWithFeaturesConvenienceInitializer { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(100.0, 0.0), + CLLocationCoordinate2DMake(101.0, 0.0), + CLLocationCoordinate2DMake(101.0, 1.0), + CLLocationCoordinate2DMake(100.0, 1.0), + CLLocationCoordinate2DMake(100.0, 0.0)}; + + MGLPolygonFeature *polygonFeature = [MGLPolygonFeature polygonWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0]) interiorPolygons:nil]; + + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"source-id" features:@[polygonFeature] options:nil]; + MGLShapeCollectionFeature *shape = (MGLShapeCollectionFeature *)source.shape; + + XCTAssertTrue([shape isKindOfClass:[MGLShapeCollectionFeature class]]); + XCTAssertEqual(shape.shapes.count, 1, @"Shape collection should contain 1 shape"); + + // when a shape is included in the features array + MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0]) interiorPolygons:nil]; + + XCTAssertThrowsSpecificNamed([[MGLShapeSource alloc] initWithIdentifier:@"source-id-invalid" features:@[polygon] options:nil], NSException, NSInvalidArgumentException, @"Shape source should raise an exception if a shape is sent to the features initializer"); +} + +- (void)testMGLShapeSourceWithShapesConvenienceInitializer { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(100.0, 0.0), + CLLocationCoordinate2DMake(101.0, 0.0), + CLLocationCoordinate2DMake(101.0, 1.0), + CLLocationCoordinate2DMake(100.0, 1.0), + CLLocationCoordinate2DMake(100.0, 0.0)}; + + MGLPolygon *polygon = [MGLPolygon polygonWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0]) interiorPolygons:nil]; + + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"source-id" shapes:@[polygon] options:nil]; + MGLShapeCollectionFeature *shape = (MGLShapeCollectionFeature *)source.shape; + + XCTAssertTrue([shape isKindOfClass:[MGLShapeCollection class]]); + XCTAssertEqual(shape.shapes.count, 1, @"Shape collection should contain 1 shape"); +} + @end diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 13401997a..ff69581da 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -919,8 +919,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { } dispatch_async(dispatch_get_main_queue(), ^{ - MGLShapeCollectionFeature *features = [MGLShapeCollectionFeature shapeCollectionWithShapes:visibleFeatures]; - MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:querySourceID shape:features options:nil]; + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:querySourceID features:visibleFeatures options:nil]; [self.mapView.style addSource:source]; MGLFillStyleLayer *fillLayer = [[MGLFillStyleLayer alloc] initWithIdentifier:queryLayerID source:source];