-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Allow initializing MGLGeoJSONSource from a shape #7145
Conversation
@frederoni, thanks for your PR! By analyzing the history of the files in this pull request, we identified @boundsj, @ituaijagbone and @1ec5 to be potential reviewers. |
I share you hesitation about removing |
id serialized = [NSJSONSerialization JSONObjectWithData:self.geoJSONData options:0 error:nil]; | ||
BOOL isFeatureCollection = [serialized isKindOfClass:NSArray.class] || ([serialized isKindOfClass:NSDictionary.class] && [[(NSDictionary *)serialized objectForKey:@"type"] isEqualToString:@"FeatureCollection"]); | ||
|
||
if (isFeatureCollection) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this conditional exist? mbgl::style::Source::setGeoJSON()
takes a mapbox::geojson::geojson
, which is the type returned by mapbox::geojson::parse()
, so no get()
is needed for either case. _feature
is of type id <MGLFeature>
, so MGLFeatureFromMBGLFeature()
works for both cases too.
for (id <MGLFeaturePrivate> feature in self.features) { | ||
featureCollection.push_back([feature mbglFeature]); | ||
// Feature collection | ||
if ([self.feature isMemberOfClass:MGLShapeCollectionFeature.class]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MGLShapeCollectionFeature implements -mbglFeature
, so this case is also unnecessary.
daa94bc
to
7b32cd4
Compare
Removed debug code and cleaned up.
|
40df884
to
4eb7c7b
Compare
source->setGeoJSON(geojson); | ||
_features = MGLFeaturesFromMBGLFeatures(geojson); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't assign features here if we're gonna support plain geometries or a single feature. Would it make sense to rename features to geometries and write a GeoJSON evaluator which overloads featureCollection, feature and geometry?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a shape
property that can be any MGLShape (or any MGLFeature). We can have a features
property as a convenience for when shape
happens to contain an MGLShapeCollectionFeature.
Depending on what we want, I think this is good for another review and possibly also merge. |
4eb7c7b
to
339c774
Compare
Maybe that means it needs to be a |
@param options An `NSDictionary` of options for this source. | ||
@return An initialized GeoJSON source. | ||
*/ | ||
- (instancetype)initWithIdentifier:(NSString *)identifier feature:(nullable id<MGLFeature>)feature options:(nullable NS_DICTIONARY_OF(MGLGeoJSONSourceOption, id) *)options NS_DESIGNATED_INITIALIZER; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since mbgl::style::GeoJSONSource::setGeoJSON()
takes an mbgl::GeoJSON
(a variant of geometry
, feature
, or feature_collection
), it would be better for this initializer to take any MGLShape. All MGLShape subclasses in turn have subclasses that conform to MGLFeature, so we wouldn’t be losing any expressiveness that way.
source->setGeoJSON(geojson); | ||
_features = MGLFeaturesFromMBGLFeatures(geojson); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a shape
property that can be any MGLShape (or any MGLFeature). We can have a features
property as a convenience for when shape
happens to contain an MGLShapeCollectionFeature.
7a85f4b
to
0172790
Compare
@@ -114,7 +128,7 @@ extern const MGLGeoJSONSourceOption MGLGeoJSONSourceOptionSimplificationToleranc | |||
is set to `nil`. This property is unavailable until the receiver is passed into | |||
`-[MGLStyle addSource]`. | |||
*/ | |||
@property (nonatomic, nullable) NS_ARRAY_OF(id <MGLFeature>) *features; | |||
@property (nonatomic, nullable) MGLShape *shape; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update the documentation for this property.
@@ -138,6 +152,10 @@ extern const MGLGeoJSONSourceOption MGLGeoJSONSourceOptionSimplificationToleranc | |||
*/ | |||
@property (nonatomic, nullable) NSURL *URL; | |||
|
|||
/** | |||
Returns all objects from the shape that conforms to `MGLFeature`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes it sound like it does a deep search through the shape collection feature for any shapes, whereas it's only set to the top-level features, and only if a shape collection feature or array of features was passed in on initialization.
/** | ||
Returns all objects from the shape that conforms to `MGLFeature`. | ||
*/ | ||
- (NSArray<MGLFeature> *)features; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use NS_ARRAY_OF()
in public APIs for the benefit of developers compiling against the iOS 8 or OS X 10.10 SDKs.
Also, this method should be a property like before.
Finally, the return type should be an array of id <MGLFeature>
, not an NSArray conforming to the MGLFeature protocol.
|
||
mbgl::FillAnnotation annotation { geometry }; | ||
auto polygon = mbgl::Polygon<double> { [self ring] }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about interior rings?
MGLShapeCollection *collection = (MGLShapeCollection *)_shape; | ||
for (MGLShape *shape in collection.shapes) { | ||
if ([shape conformsToProtocol:@protocol(MGLFeature)]) { | ||
[features addObject:(id <MGLFeature>)shape]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be more efficient to compute the features
property as soon as shape
is set, or on demand when calling -features
? Or perhaps a lazy-loading strategy is called for, similar to how I treated overlayBounds
in #7251. Basically, if a features ivar is nil
, -features
would compute the features in the shape and store the results in that ivar. -initWithIdentifier:features:options:
would go ahead and set that ivar to the features that are passed in, so a subsequent call to -features
would require no computation.
@@ -49,9 +50,20 @@ - (instancetype)initWithIdentifier:(NSString *)identifier URL:(NSURL *)url optio | |||
return self; | |||
} | |||
|
|||
- (instancetype)initWithIdentifier:(NSString *)identifier feature:(id<MGLFeature>)feature options:(NSDictionary<MGLGeoJSONSourceOption,id> *)options |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to require a feature instead of any ordinary shape? Looks like we’re only using this parameter for an MGLShape-typed property anyways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly not to break the API. Should we rename it to -[MGLGeoJSONSource initWithIdentifier:shape:options:]
or keep feature:
and features:
while adding shape:
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about -initWithIdentifier:shape:options:
, which would be expressive enough to take any MGLShape, including any MGLFeature. We’d want to treat the shape as a feature if possible and fall back to treating it as a non-feature, to avoid losing attribute information.
As for the other initializer, which is merely a convenience, how about -initWithIdentifier:features:options:
initializer, as incongruous as it’d be? I don’t think we’d want to encourage developers to intermix non-features and features at the top level of the same source, because then we’d have to cater to the lowest common denominator and treat them all like ordinary shapes. (They can still do so by passing an MGLShapeCollection into -initWithIdentifier:shape:options:
.)
I’m starting to think we should omit the features
property just to keep things simple for us. The developer can still get the features out of shape
as long as they cast shape
to an MGLShapeCollectionFeature. Maybe @incanus or @ericrwolfe have opinions here, given that they’ve been working with this API at the application level recently.
2ad126b
to
52695cf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One question and one minor issue, but otherwise good to go.
@param options An `NSDictionary` of options for this source. | ||
@return An initialized GeoJSON source. | ||
*/ | ||
- (instancetype)initWithIdentifier:(NSString *)identifier features:(NSArray<id<MGLFeature>> *)features options:(nullable NS_DICTIONARY_OF(MGLGeoJSONSourceOption, id) *)options NS_DESIGNATED_INITIALIZER; | ||
- (instancetype)initWithIdentifier:(NSString *)identifier shape:(nullable MGLShape *)shape options:(nullable NS_DICTIONARY_OF(MGLGeoJSONSourceOption, id) *)options NS_DESIGNATED_INITIALIZER; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does it mean to create a source from no shapes at all? It does work in the current release branch, using an empty array of features, but we should make sure it works when shape is nil.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nil shape works but I can't think of any case where you'd want to do that.
self.rawSource->setGeoJSON(geojson); | ||
|
||
_features = MGLFeaturesFromMBGLFeatures(featureCollection); | ||
[self willChangeValueForKey:@"shape"]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These change notifications are unnecessary. Since setShape: is a standard KVC selector, these change notifications will be generated for you implicitly.
The Coveralls failure is caused by an unrelated change in test coverage in mbgl. You can get around it by pushing another change, so that coverage will be even. |
52695cf
to
bec9b4c
Compare
@@ -236,7 +242,8 @@ - (void)testMGLGeoJSONSourceWithShapeCollectionFeatures { | |||
|
|||
MGLShapeCollectionFeature *shapeCollectionFeature_1 = [MGLShapeCollectionFeature shapeCollectionWithShapes:@[polygonFeature, polylineFeature, multiPolygonFeature, multiPolylineFeature, pointCollectionFeature, pointFeature, shapeCollectionFeature]]; | |||
|
|||
XCTAssertThrowsSpecificNamed([[MGLGeoJSONSource alloc] initWithIdentifier:@"source-id" features:@[shapeCollectionFeature_1] options:nil], NSException, @"Method unavailable"); | |||
#warning DEBUG | |||
//XCTAssertThrowsSpecificNamed([[MGLGeoJSONSource alloc] initWithIdentifier:@"source-id" shape:shapeCollectionFeature_1 options:nil], NSException, @"Method unavailable"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@1ec5 what was the purpose of this test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like it's intended to trip an exception in -[MGLShapeCollectionFeature mbglFeature]
, which is unimplemented in the previous code.
bec9b4c
to
3e2cf4e
Compare
The iOS release branch now has c8e34d5 so if this branch is rebased on top of that, the node tests will pass. |
b78e8aa
to
47fd189
Compare
47fd189
to
fdd3c40
Compare
First stab at #6986
I'm slightly hesitant about replacing
-[MGLGeoJSONSource initWithIdentifier:features:options:]
withfeature
because of the additional initialization of MGLShapeCollectionFeature devs has to do now compared to a concise array literal prior to this change.@1ec5 👀