[ios, macos] Add convenience initializers to shape source (#7665)

This adds two new convenience initializers to MGLShapeSource:

-initWithIdentifier:features:options: takes an array of shape objects
that conform to MGLFeature, inserts them in a shape collection feature
and creates a source with that shape. -initWithIdentifier:shapes:options
does the same but with concrete MGLShape objects that get added to
a shape collection.

Throw an exception if an shape source is created with the features
initializer but is sent an array of features that contains something
that is not actually an object that conforms to the feature protocol.

Updates to geojson data guide

Qualify APIs that take arrays of shapes that are features
This commit is contained in:
Jesse Bounds
2017-01-11 16:05:38 -08:00
committed by GitHub
parent 4332b3b953
commit 6ad47cfbad
7 changed files with 110 additions and 8 deletions

View File

@@ -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

View File

@@ -184,7 +184,7 @@ MGL_DEFINE_FEATURE_IS_EQUAL();
@dynamic shapes;
+ (instancetype)shapeCollectionWithShapes:(NSArray *)shapes {
+ (instancetype)shapeCollectionWithShapes:(NS_ARRAY_OF(MGLShape<MGLFeature> *) *)shapes {
return [super shapeCollectionWithShapes:shapes];
}

View File

@@ -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<MGLFeature> *) *)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
/**

View File

@@ -56,6 +56,21 @@ const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance = @"MGLSh
return self;
}
- (instancetype)initWithIdentifier:(NSString *)identifier features:(NS_ARRAY_OF(MGLShape<MGLFeature> *) *)features options:(nullable NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options {
for (id <MGLFeature> 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];
}

View File

@@ -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

View File

@@ -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

View File

@@ -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];