Add features initializer to MGLGeoJSONSource#6524
Conversation
|
@ituaijagbone, thanks for your PR! By analyzing this pull request, we identified @incanus, @1ec5 and @boundsj to be potential reviewers. |
1ec5
left a comment
There was a problem hiding this comment.
You're on the right track. Here are a few preliminary comments.
|
|
||
| NSMutableArray *coordinates = [NSMutableArray array]; | ||
|
|
||
| for (int index = 0; index < self.pointCount; index++) { |
There was a problem hiding this comment.
int might not be large enough for the number of coordinates that may be in this polyline. Use the same type as pointCount.
| - (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier URL:(NSURL *)url options:(NS_DICTIONARY_OF(NSString *, id) *)options NS_DESIGNATED_INITIALIZER; | ||
|
|
||
|
|
||
| - (instancetype)initWithSourceIdentifier:(NSString *)sourceIdentifier Features:(NS_ARRAY_OF(id<MGLFeature>) *)features options:(nullable NS_DICTIONARY_OF(NSString *, id) *)options NS_DESIGNATED_INITIALIZER; |
There was a problem hiding this comment.
Nit: lowercase the Features parameter.
| _features = MGLFeaturesFromMBGLFeatures(geojson); | ||
| } else { | ||
|
|
||
| NSMutableArray<NS_DICTIONARY_OF(NSString *, id) *> *featuresArray = [NSMutableArray array]; |
There was a problem hiding this comment.
Use the macros consistently: either change NSMutableArray to NS_MUTABLE_ARRAY_OF, or change NS_DICTIONARY_OF to NSDictionary.
| @"features":featuresArray}; | ||
|
|
||
| NSError *error; | ||
| NSData *featuresJSONData = [NSJSONSerialization dataWithJSONObject:featureCollection options:NSJSONWritingPrettyPrinted error:&error]; |
There was a problem hiding this comment.
Pretty printing is good for debugging purposes, but it'll need to be removed before merging because it increases memory consumption.
| - (NS_DICTIONARY_OF(NSString *, id) *)featureDictionary { | ||
| // TODO: MGLPolygonFeature | ||
|
|
||
| NS_MUTABLE_ARRAY_OF(NS_MUTABLE_ARRAY_OF(NS_ARRAY_OF(NSNumber *) *) *) *coordinates = [NSMutableArray array]; |
There was a problem hiding this comment.
These macros (or the lightweight generics syntax) are useful in headers and method signatures, but they’re totally optional inside method implementations. As you can see, they’re a bit unwieldy, so there’s no shame in leaving them out inside the method.
| return geometries; | ||
| } | ||
|
|
||
| - (void)addTypeAndCoordinatesFrom:(id<MGLFeature>)feature to:(NS_MUTABLE_ARRAY_OF(NS_DICTIONARY_OF(NSString *, id) *)**)geometries { |
There was a problem hiding this comment.
This is a questionable refactoring. First of all, since the array is mutable, you can simply pass a pointer to it rather than a double-pointer. Secondly, if all the classes that implement MGLFeature have a -featureDictionary method, it would be cleaner to declare that method on MGLFeature. That way, -geometryCollection: can simply call feature.featureDictionary without caring which specific class feature happens to be.
There was a problem hiding this comment.
Yes You are right. Calling -featureDictionary should work directly. Thank you.
| }; | ||
| } | ||
|
|
||
| - (NS_MUTABLE_ARRAY_OF(NS_DICTIONARY_OF(NSString *, id) *) *)geometryCollection:(NS_ARRAY_OF(MGLShape *) *)shapes { |
There was a problem hiding this comment.
MGLShapeCollectionFeature already knows about its shapes, so there’s no need to pass it in here. Just use self.shapes inside the method.
boundsj
left a comment
There was a problem hiding this comment.
Looking good! Seems like we will need to regenerate the layer code to get the tests passing again now that the GeoJSON source method signatures have been updated.
|
|
||
| @synthesize identifier; | ||
| @synthesize attributes; | ||
| // synthesize the feature dictionary |
There was a problem hiding this comment.
It'd fine to remove this comment
|
|
||
| - (NS_DICTIONARY_OF(NSString *, id) *)featureDictionary { | ||
| return @{@"type":@"Feature", | ||
| @"properties":(self.attributes) ? self.attributes : [NSDictionary dictionary], |
There was a problem hiding this comment.
nit this could be an empty NSDictionary literal
|
|
||
| - (NS_DICTIONARY_OF(NSString *, id) *)featureDictionary { | ||
| // TODO: MGLMultiPointFeature | ||
| NS_MUTABLE_ARRAY_OF(NS_ARRAY_OF(NSNumber *) *) *coordinates = [NSMutableArray array]; |
There was a problem hiding this comment.
nit: I definitely prefer, inside method implementations, to just write NSMutableArray *coordinates (+1 to #6524 (comment))
| @param sourceIdentifier A string that uniquely identifies the source. | ||
| @param URL An HTTP(S) URL, absolute file URL, or local file URL relative to the | ||
| current application’s resource bundle. | ||
| @param features An array of features that conform to the MGLFeature protocol. |
There was a problem hiding this comment.
nit: It'd be nice to surround MGLFeature in back ticks so that the generated documentation highlights the word like MGLFeature
|
|
||
|
|
||
| - (void)testMGLGeoJSONSourceWithPolygonFeatures { | ||
| // TODO: Create feature |
There was a problem hiding this comment.
nit remove the scaffolding comments
| @"features":featuresArray}; | ||
|
|
||
| NSError *error; | ||
| NSData *featuresJSONData = [NSJSONSerialization dataWithJSONObject:featureCollection options:0 error:&error]; |
There was a problem hiding this comment.
#6524 (comment) suggested removing the pretty printing. Noting also that this will be refactored away in a future commit where we eventually just set mbgl::GeoJSON{ featureCollection } and avoid the trip to create JSON data.
|
We should probably wait for #6588 to land before continuing with this because of the the duplicate work and conflicts in |
|
#6588 has landed. As expected, there are a number of conflicts, but they should be straightforward to resolve. |
e073b10 to
113c1dd
Compare
48ff580 to
44f88ab
Compare
|
@incanus @1ec5 @frederoni please feel free to have another look at this when you have a chance. I'm not sure why the CI builds started failing today but it does not seem to be related to any changes in this PR. Note the comment in 70962eb. I'm up for suggestions about where / how to share the |
There was a problem hiding this comment.
How about stuffing the value in an NSExpression and calling the existing category method?
There was a problem hiding this comment.
Fixed in 3770a036d68a4bcca7ffa91a4ff11036262d491a
There was a problem hiding this comment.
Fixed in 3770a036d68a4bcca7ffa91a4ff11036262d491a
There was a problem hiding this comment.
Pass C++ objects around by reference rather than as pointers.
There was a problem hiding this comment.
Fixed in 3770a036d68a4bcca7ffa91a4ff11036262d491a
There was a problem hiding this comment.
Fixed in 3770a036d68a4bcca7ffa91a4ff11036262d491a
There was a problem hiding this comment.
Would it be more convenient for NSDictionary to know how to turn itself into a PropertyMap?
There was a problem hiding this comment.
Fixed in 3770a036d68a4bcca7ffa91a4ff11036262d491a
There was a problem hiding this comment.
MGLShapeCollectionFeature is a concrete class.
There was a problem hiding this comment.
Fixed in 3a6be23e3a922e739b6e7b1687d5630621bb7ba6
There was a problem hiding this comment.
Mention that this class also doubles as a superclass of MGLPolyline and MGLPolygon, despite being a distinct geometry.
There was a problem hiding this comment.
Fixed in 3a6be23e3a922e739b6e7b1687d5630621bb7ba6
There was a problem hiding this comment.
Fixed in 3a6be23e3a922e739b6e7b1687d5630621bb7ba6
There was a problem hiding this comment.
This is really unwieldy. Just use an unqualified mutable array.
There was a problem hiding this comment.
Also initialize it with a capacity.
There was a problem hiding this comment.
Fixed in 3770a036d68a4bcca7ffa91a4ff11036262d491a
There was a problem hiding this comment.
A bunch of these files are missing from the macOS project.
There was a problem hiding this comment.
Fixed in 4268576d1e5dc05514ebf412a6b9723ab4623b93
|
Since yesterday afternoon, I'm still getting |
There was a problem hiding this comment.
This class doesn't seem to be adding much. The lone function can be a standalone C++ function.
There was a problem hiding this comment.
The non-feature direct subclasses of MGLShape should have geoJSONDictionary methods that encapsulate these geometry structures.
There was a problem hiding this comment.
Once that happens, the bodies of most of these featureDictionary methods can be factored out.
There was a problem hiding this comment.
I took this I idea and ran with it a bit in ad4e54d
There was a problem hiding this comment.
+[NSArray arrayWithArray:] creates a non-mutable array.
There was a problem hiding this comment.
This is intentional. The method says that it returns a non-mutable array even though it uses a mutable array internally to create the collection
(NSArray *)geometryCollection
There was a problem hiding this comment.
My bad, I figured -[NSMutableArray copy] returns an NSMutableArray, but it returns an NSArray. Carry on.
There was a problem hiding this comment.
Passing in the property map by non-const reference is similar to an inout C parameter: you're actually mutating the map that's being passed in. So there's no need to return anything. In fact, this method could be renamed to "populatePropertyMap" or somesuch. But if an empty property map is always passed in, why not create it locally instead of accepting it as a parameter?
There was a problem hiding this comment.
If the intent is to convert a dictionary into a property map, this could be a category method on NSDictionary that takes no parameters and returns a property map.
There was a problem hiding this comment.
This was fixed in bc1dda3. Note thatpropertyMapWithDictionary: and vectorForArray: (name change in 95d548b) still take NSDictionarys and NSArrays so that the entire dictionary can be traversed (recursively) including if any node is a dictionary or array. The (mbgl::PropertyMap)mbgl_propertyMap method already exists and is the only public method of the category. It is intended to take no parameters and return a property map.
There was a problem hiding this comment.
Similarly, this would be a category method on NSArray that returns a vector of values, and the method name would be something like mgl_valueVector.
3a6be23 to
206af31
Compare
There was a problem hiding this comment.
The word “feature” in this method name is redundant.
There was a problem hiding this comment.
Pedantry: it’s a representation of an instance of the class, not of the class itself.
There was a problem hiding this comment.
The dictionary includes a
geometrykey corresponding to the receiver’s underlying geometry data, apropertieskey corresponding to the receiver’sattributesproperty, and anidkey corresponding to the receiver’sidentifierproperty.
There was a problem hiding this comment.
Rename -geoJSONGeometryDictionary to -geoJSONDictionary and -geoJSONFeatureDictionary to geoJSONDictionary. Then this method can call super’s implementation and wrap it in NSDictionaryFeatureForGeometry().
There was a problem hiding this comment.
Nit: the if statement is unnecessary; it’s perfectly fine to set to nil using subscript notation.
There was a problem hiding this comment.
If I remove that conditional, I get a runtime exception when [NSExpression expressionForConstantValue:identifier]; is called. I'm using NSExpression for the conversion of the id to the mbgl variant .
There was a problem hiding this comment.
Huh, I wouldn't've expected foo[@"bar"] = nil to do anything other than remove the "bar" key from foo.
There was a problem hiding this comment.
Sorry, I was in mbglFeature() not NSDictionaryFeatureForGeometry(). Fixed in acf792b
There was a problem hiding this comment.
Remove the dictionary parameter and use self instead. Below, call the method recursively on dictionary[key] rather than on self. Then rename this method to -mgl_propertyMap (not -mbgl_propertyMap – MGL is the class prefix for this target).
There was a problem hiding this comment.
Should we throw an exception if we encounter an unknown type?
There was a problem hiding this comment.
I thought about this and decided to not throw an exception when I added it yesterday.
The reason is that the first thing mgl_featureIdentifier does is call mgl_convertedValueWithValue which will throw an exception if there is an unknown type. mgl_featureIdentifier exists only to be able to get the correct raw value from mbgl::Value returned by mgl_convertedValueWithValue and the only reason we are doing any of this here is to share the conversion logic that happens to currently live in the NSExpression category.
I think you could make the case that we should catch an exception thrown by mgl_convertedValueWithValue and re-raise it with a different message (instead of the Can’t convert %s:%@ to mbgl::Value) that a developer will see if they send an invalid value for the id / identifier. However, I was thinking that it might make sense to wait and refactor this if we introduce a category method on NSObject as you've suggested in conversation. It is a bit weird at the moment though.
There was a problem hiding this comment.
Once we refactor this so that any object knows how to "valueize" itself, we'll be able to remove these conditionals too.
There was a problem hiding this comment.
These files are missing from macos.xcodeproj. Also, NSArray+MGLAdditions.mm appears to be in Sources twice.
There was a problem hiding this comment.
Will fix macos. I used the standard mechanism in xcode for adding files. I guess I'll manually edit the file but seems strange that I need to do this.
There was a problem hiding this comment.
Looks like this is common. I think every impl file is in there twice and often headers are too. Look just below:
404C26E21D89B877000AA13D /* MGLTileSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 404C26E01D89B877000AA13D /* MGLTileSet.h */; settings = {ATTRIBUTES = (Public, ); }; }; 404C26E31D89B877000AA13D /* MGLTileSet.h in Headers */ = {isa = PBXBuildFile; fileRef = 404C26E01D89B877000AA13D /* MGLTileSet.h */; settings = {ATTRIBUTES = (Public, ); }; }; 404C26E41D89B877000AA13D /* MGLTileSet.mm in Sources */ = {isa = PBXBuildFile; fileRef = 404C26E11D89B877000AA13D /* MGLTileSet.mm */; }; 404C26E51D89B877000AA13D /* MGLTileSet.mm in Sources */ = {isa = PBXBuildFile; fileRef = 404C26E11D89B877000AA13D /* MGLTileSet.mm */; };
Maybe this is a bug related to adding to both static and dynamic targets?
I'm not going to touch this in this PR. Let's open a new issue to clean this up if we need to.
There was a problem hiding this comment.
Oh, you’re right, I thought I was looking at the project structure but I was looking at target membership instead.
Regarding the macOS side, adding a file to ios.xcodeproj won’t automatically add it to macos.xcodeproj. I realize it’s a bit inconvenient. #5942 was an attempt at merging the two projects so that you always get presented with all three targets when adding a file. Unfortunately it slowed down the index too much for comfort.
There was a problem hiding this comment.
Yeah that's fine. I just forgot
This adds a features initializer to MGLGeoJSONSource. The initializer takes shapes and converts them to JSON representation before passing to core. This also adds methods to the MGLShape concrete subclasses so that they can represent themselves in NSDictionary form suitable for transforming to JSON (GeoJSON).
c4ea541 to
ac22db1
Compare
|
Noting that although the Qt and Node builds show that they were unsuccessful they actually passed if you click through to bitrise. |
MGLShape subclasses can now return NSDictionaries that represent the shapes' GeoJSON geometries. MGLFeature classes can now return NSDictionaries that represent the features as GeoJSON features. This makes it trivial to serialize iOS and macOS shapes and features into GeoJSON. MGLFeature instances can also return a representation of themselves as an mbgl::Feature. This capability is used in a refactoring of the implementations of MGLGeoJSONSource to more efficiently transform MGLFeatures into mbgl::Features so they can be fed directly into mbgl::GeoJSONSource without having to round trip through NSJSONSerialization. The MGLFeature identifier is converted into the mbgl::identifier type based on the type of the identifier. The MGLFeature attributes are recursively converted into an mbgl::PropertyMap and each value is converted into one of the types that the PropertyMap's Value variant supports.
ac22db1 to
6ffe51b
Compare


fixes #6302