Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Allow initializing MGLGeoJSONSource from a shape #7145

Merged
merged 1 commit into from
Dec 6, 2016

Conversation

frederoni
Copy link
Contributor

First stab at #6986

I'm slightly hesitant about replacing -[MGLGeoJSONSource initWithIdentifier:features:options:] with feature because of the additional initialization of MGLShapeCollectionFeature devs has to do now compared to a concise array literal prior to this change.

@1ec5 👀

@frederoni frederoni added iOS Mapbox Maps SDK for iOS macOS Mapbox Maps SDK for macOS release blocker Blocks the next final release ⚠️ DO NOT MERGE Work in progress, proof of concept, or on hold labels Nov 22, 2016
@frederoni frederoni added this to the ios-v3.4.0 milestone Nov 22, 2016
@frederoni frederoni self-assigned this Nov 22, 2016
@mention-bot
Copy link

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

@1ec5
Copy link
Contributor

1ec5 commented Nov 23, 2016

I share you hesitation about removing -[MGLGeoJSONSource initWithIdentifier:features:options:]. -[MGLShapeCollectionFeature shapeCollectionWithShapes:] is kind of unwieldy. Should we ease the pain by keeping -[MGLGeoJSONSource initWithIdentifier:features:options:] as yet another initializer on MGLGeoJSONSource?

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) {
Copy link
Contributor

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]) {
Copy link
Contributor

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.

@frederoni frederoni force-pushed the fred-conform-geojson-6986 branch 4 times, most recently from daa94bc to 7b32cd4 Compare November 24, 2016 16:02
@frederoni
Copy link
Contributor Author

frederoni commented Nov 24, 2016

Removed debug code and cleaned up.
Todo:

  • Support initializing MGLGeoJSONSource with a geometry?
  • -[MGLGeoJSONSourceTests testMGLGeoJSONSourceWithPolygonFeatures] is failing unexpectedly.
  • features are not set if MGLGeoJSONSource is initialized with data.

@frederoni frederoni force-pushed the fred-conform-geojson-6986 branch 3 times, most recently from 40df884 to 4eb7c7b Compare November 28, 2016 13:15
@frederoni frederoni changed the title GeoJSON support for feature or collection GeoJSON support Nov 28, 2016
source->setGeoJSON(geojson);
_features = MGLFeaturesFromMBGLFeatures(geojson);
Copy link
Contributor Author

@frederoni frederoni Nov 28, 2016

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?

Copy link
Contributor

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.

@frederoni
Copy link
Contributor Author

Depending on what we want, I think this is good for another review and possibly also merge.
#6986 doesn't occur anymore but there's a drawback. Using -[MGLGeoJSONSource initWithIdentifier:geoJSONData:options:] will leave the features property unset because it can contain plain geometries.

@frederoni frederoni removed the ⚠️ DO NOT MERGE Work in progress, proof of concept, or on hold label Nov 28, 2016
@1ec5
Copy link
Contributor

1ec5 commented Nov 28, 2016

Maybe that means it needs to be a shape property (normally holding a shape collection feature), with features being a convenience property based on it?

@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;
Copy link
Contributor

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);
Copy link
Contributor

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.

@frederoni frederoni force-pushed the fred-conform-geojson-6986 branch 2 times, most recently from 7a85f4b to 0172790 Compare November 30, 2016 16:18
@@ -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;
Copy link
Contributor

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`.
Copy link
Contributor

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;
Copy link
Contributor

@1ec5 1ec5 Nov 30, 2016

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] };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about interior rings?

@1ec5 1ec5 changed the title GeoJSON support Allow initializing MGLGeoJSONSource from a shape Nov 30, 2016
MGLShapeCollection *collection = (MGLShapeCollection *)_shape;
for (MGLShape *shape in collection.shapes) {
if ([shape conformsToProtocol:@protocol(MGLFeature)]) {
[features addObject:(id <MGLFeature>)shape];
Copy link
Contributor

@1ec5 1ec5 Dec 1, 2016

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
Copy link
Contributor

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.

Copy link
Contributor Author

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: ?

Copy link
Contributor

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.

Copy link
Contributor

@1ec5 1ec5 left a 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;
Copy link
Contributor

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.

Copy link
Contributor Author

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"];
Copy link
Contributor

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.

@1ec5
Copy link
Contributor

1ec5 commented Dec 5, 2016

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.

@@ -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");
Copy link
Contributor Author

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?

Copy link
Contributor

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.

@boundsj
Copy link
Contributor

boundsj commented Dec 5, 2016

The iOS release branch now has c8e34d5 so if this branch is rebased on top of that, the node tests will pass.

@frederoni frederoni force-pushed the fred-conform-geojson-6986 branch 2 times, most recently from b78e8aa to 47fd189 Compare December 6, 2016 12:04
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
iOS Mapbox Maps SDK for iOS macOS Mapbox Maps SDK for macOS release blocker Blocks the next final release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants