-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[ios, macos] Add support for data driven styling #7596
Conversation
@boundsj, thanks for your PR! By analyzing this pull request, we identified @1ec5, @jfirebaugh and @friedbunny to be potential reviewers. |
|
@boundsj I'm not seeing where those additions are used elsewhere in the diff. Can you point me to where they were needed? The omission of methods such as |
Ah, GitHub was hiding parts of the diff so they didn't show up in text search. Yes, code such as this should instead be written using a visitor (example). One advantage of doing so is that future additions to the variant types will produce a compile-time error until the code is updated to handle the new type. |
This PR has the potential to introduce a lot of classes, more than we’d want the developer to have to juggle. The point of implementing MGLStyleValue as a class cluster in the first place was to reduce the number of classes the developer would have to know about in order to use style values in a basic way. I’d prefer if we could somehow draw class-level distinctions based on only the most important axis (in terms of mapbox/mapbox-gl-style-spec#613) and represent any other axes with enum-typed properties or optional properties/parameters. By analogy, the NSPredicate class cluster publicly exposes only NSCompoundPredicate and NSComparisonPredicate as concrete subclasses, but NSComparisonPredicate has a
Can we implement composite functions through composition? That is, could we represent an exponential composite function as an MGLStyleExponentialFunction with functions as stop values? This does mean shifting some validation of the stop values from design time to runtime, but I think this is a reasonable concession. While we’re going about renaming classes, the class names proposed here are a bit ungrammatical. A subclass usually inserts an adjective right after the class prefix. (I’ve always felt weird about MGLStyleConstantValue; it should be MGLConstantStyleValue.) For example:
|
@@ -388,6 +388,10 @@ var allPaintProperties = []; | |||
var allTypes = []; | |||
|
|||
for (var layer of layers) { | |||
|
|||
// Ignore layers that are in the spec yet still not supported in mbgl core | |||
if (layer.type === 'fill-extrusion') { continue; } |
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.
Let’s delete the fill extrusion layer type and its properties from spec
right off the bat so we don’t forget to account for this omission when we introduce additional code that enumerates the others (or forget to remove these special cases when we do implement fill extrusion layers).
} | ||
|
||
- (MGLStyleValue<<%- propertyType(property, true) %>> *)<%- objCGetter(property) %> { | ||
MGLAssertStyleLayerIsValid(); | ||
|
||
auto propertyValue = self.rawLayer->get<%- camelize(originalPropertyName(property)) %>() ?: self.rawLayer->getDefault<%- camelize(originalPropertyName(property)) %>(); | ||
auto propertyValue = self.rawLayer->get<%- camelize(originalPropertyName(property)) %>(); | ||
<% if (property["property-function"] == true) { -%> |
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.
Nit: no need for == true
, because true
is truthy.
platform/darwin/src/MGLStyleValue.h
Outdated
@@ -57,6 +60,29 @@ NS_ASSUME_NONNULL_BEGIN | |||
*/ | |||
+ (instancetype)valueWithBase:(CGFloat)base stops:(NSDictionary<NSNumber *, MGLStyleValue<T> *> *)stops; | |||
|
|||
// TODO: API docs | |||
+ (instancetype)valueWithIntervalStops:(NSDictionary<NSNumber *, MGLStyleValue<T> *> *)intervalStops; |
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_DICTIONARY_OF
for compatibility with applications linked against the iOS 8.x SDK.
platform/darwin/src/MGLStyleValue.h
Outdated
+ (instancetype)valueWithAttributeName:(NSString *)attributeName exponentialStops:(NSDictionary<id, MGLStyleValue<T> *> *)exponentialStops; | ||
|
||
// TODO: API docs | ||
+ (instancetype)valueWithAttributeName:(NSString *)attributeName base:(CGFloat)base exponentialStops:(NSDictionary<id, MGLStyleValue<T> *> *)exponentialStops; |
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.
#7486 renamed base
to interpolationBase
. That’ll come over in the next merge to master.
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.
oops. thanks for the reminder
6fff71c
to
8992383
Compare
@jfirebaugh thanks for pointing me towards the variants and visitor pattern. The previous implementation has been replaced with a set of evaluators and visitors. In addition to the benefit you mentioned, I think this approach makes it easier to follow the partitions of the conversion of functions and stops in the implementation.
I've been able to eliminate all references to PropertyValue's |
13be93f
to
0f4f303
Compare
5d97e0e
to
4b49075
Compare
@1ec5 thanks for notes in #7596 (comment). Here is an updated proposal for this API: The principle axis of function categories are:
All of these mbgl types could be represented by the single existing class:
Internally, The stops axis could be represented by a publicly exposed enumeration with the values:
With these classes, Camera functions
param type:
When the type is These methods would create an Source functions
param type:
When the type is
Convenience method that creates an identity function. This could be included since an identity function does not need any input except for the attribute key name. These methods would create an Composite functions
param type: The composite function is similar to This method would create an Legacy function supportThe following legacy methods could continue to be exposed and will create functions that are now known as
|
#7596 (comment) proposes essentially that. Note that mbgl requires the attribute name to be specified up front (i.e. |
@1ec5 when thinking about the proposal in #7596 (comment), one alternative that comes to mind is to create a set of classes to represent the stop types. We could still classify that secondary axis with an internal enum value in For example, instead of creating a kind of source function that has interval stops like this:
we could offer something like this:
I think the advantage of this is that it would help reduce mistakes like specifying that the function is |
When designing this API, you should expect that |
That reminds me, Parts of #7596 (comment) seem strange since base is a float that only sometimes makes sense depending on the enumerated type of the function. But base and color space properties would always make sense for a proper exponential stops object. |
@jfirebaugh tests are passing, but please hold off on merging this branch to Note: to support assertions in the Darwin tests, I backfilled an inequality operator in: https://github.com/mapbox/mapbox-gl-native/blob/boundsj-dds-darwin-wip/include/mbgl/style/data_driven_property_value.hpp#L29-L32 |
1fd2ca8
to
2fed47f
Compare
b4e4d22
to
d0a8102
Compare
Here is an updated status report, with examples, on the state of the Darwin platform API: APIdata-driven, camera function with exponential color stop valueslet stops = [
0: MGLStyleValue<UIColor>(rawValue: .red),
10: MGLStyleValue<UIColor>(rawValue: .red),
15: MGLStyleValue<UIColor>(rawValue: .green)
]
circleStyleLayer.circleColor = MGLStyleValue<UIColor>.cameraFunctionValue(
stopType: .exponential,
stops: stops,
options: [.interpolationBase: 10.0]
) data-driven, source function with categorical color stop values with integer attribute keyslet stops = [
0: MGLStyleValue<UIColor>(rawValue: .green),
100: MGLStyleValue<UIColor>(rawValue: .orange)
]
circleStyleLayer.circleColor = MGLStyleValue<UIColor>.sourceFunctionValue(
stopType: .categorical,
stops: stops,
attributeName: "temp",
options: [.defaultValue: MGLStyleValue<UIColor>(rawValue: .red)]
) Naming
As you can see above, I went with Composite functions
I ended up not implementing that approach because a) the core implementation truly expects a composite of stops with constant values and b) the API was much harder to use (and made less sense) if the developer had to make full on source functions to then stuff in stops (that we would then just throw away and take the stops from). It also did not make sense for the outer function to specify an The API we have now looks more like a Swift version of the js examples and Android implementation noted in #7752 (comment) data-driven, composite function with inner exponential color stop values nested in outer camera stopslet stops: [NSNumber: [NSNumber: MGLStyleValue<NSNumber>]] = [
0: [0: smallRadiusValue], // zoom level 0, temp value of 0 use small radius
10: [200: smallRadiusValue], // zoom level 10, temp value of 200 use small radius
20: [200: largeRadiusValue] // zoom level 20, temp value of 200 use small radius
]
circleStyleLayer.circleRadius = MGLStyleValue<NSNumber>.compositeFunctionValue(
stopType: .exponential,
stops: stops,
attributeName: "temp",
options: nil
) GettersIt is still the case that getting values back out requires some casting gymnastics: // get a value back
if let returnedCircleRadius = circleStyleLayer.circleRadius as? MGLCompositeStyleFunction<NSNumber> {
if let returnedStops = returnedCircleRadius.stops as NSDictionary? as? [NSNumber: [NSNumber: MGLStyleValue<NSNumber>]] {
let lhs: MGLStyleValue<NSNumber> = returnedStops[0]!.values.first!
let rhs: MGLStyleValue<NSNumber> = radiusCompositeExponentialOrIntervalStops[0]!.values.first!
XCTAssertEqual(lhs, rhs)
}
} 🏋️ Next stepsAll of the examples above (and many more) are available in MGLStyleValueTests.swift. To finish up I need to:
|
Noting that node tests started failing here (and on |
platform/darwin/src/MGLStyleValue.h
Outdated
+ (instancetype)valueWithInterpolationBase:(CGFloat)interpolationBase stops:(NSDictionary<NSNumber *, MGLStyleValue<T> *> *)stops __attribute__((deprecated("Use +cameraFunctionValueWithStopType:stops:options:"))); | ||
|
||
// TODO: API docs | ||
+ (instancetype)cameraFunctionValueWithStopType:(MGLStyleFunctionStopType)stopType stops:(NS_DICTIONARY_OF(id, MGLStyleValue<T> *) *)stops options:(nullable NS_DICTIONARY_OF(MGLStyleFunctionOption, id) *)options NS_SWIFT_NAME(cameraFunctionValue(stopType:stops: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.
The overridden Swift name notwithstanding, it feels awkward to name an initializer such that it bridges into Swift as MGLStyleValue.cameraFunctionValue(…
, using the class method syntax, instead of MGLStyleValue.init(…
, using the initializer syntax. For one thing, it means repetition at the call site, which Swift strives to avoid.
Naming the factory methods +valueWith…
would have the unfortunate effect of obscuring the type (camera, source, etc.), which is important. To disambiguate the various factory methods at the call site, how about naming the selectors along the lines of +valueWithStopType:cameraStops:options:
?
MGL_EXPORT | ||
@interface MGLStyleFunction<T> : MGLStyleValue<T> | ||
@interface MGLCameraStyleFunction<T> : MGLStyleValue<T> |
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.
A @compatibility_alias
is needed from MGLStyleFunction to MGLCameraStyleFunction to maintain backwards compatibility with iOS SDK v3.4.0. Alternatively, we could keep MGLStyleFunction as the abstract superclass of MGLCameraStyleFunction et al.
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.
Done
platform/darwin/src/MGLStyleValue.h
Outdated
extern MGL_EXPORT const MGLStyleFunctionOption MGLStyleFunctionOptionDefaultValue; | ||
|
||
// TODO: API docs | ||
typedef NS_ENUM(NSUInteger, MGLStyleFunctionStopType) { |
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 term would refer to UIColor in MGLCameraStyleValue<UIColor>
? No matter what we call it, I think developers would gravitate toward “stop type” or “type of stop” for that concept as well.
Instead of referring to exponential, interval, etc. as “stop types”, how about referring to them as “interpolation modes”? MGLInterpolationModeExponential
wouldn’t be that much typing. 😛
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.
I like that and thanks for referencing the helpful SpriteKit example. My only concern is that, with DDS, the identify function actually uses the notion of stops only as a placeholder and interpolation is not a factor. I suppose that, no matter what we call this enum, some clarification about what an identity function is will be required.
I agree with your choices on composite function design. I was even considering changing the core API so that it uses less composition, in order to reflect the style spec reality that an exponential stop interpolation base is provided once, function-wide, not on a per-inner-function basis. |
0b7836c
to
a558b14
Compare
a558b14
to
fd7b289
Compare
@jfirebaugh I think this can be merged to I created #7924 to track documentation work for DDS on this platform and that can be done on master or the eventual release branch. |
Really exciting! Is color conversion supported yet? (spec) My colors are in rgb hex and are not displaying, not sure if I should open a new issue or if I'm missing something.
It's worth noting the lines are completely invisible, not defaulting to gray. |
RGB color interpolation should be supported. Can you open a new ticket so we can track this issue specifically? (Alternative color spaces for interpolation are tracked in #6442, by the way.) |
@nitrag @1ec5 here is an example of a feature with rgb hex color in attributes that gets converted correctly with the current 3.5.0 beta 1
|
These examples inspired me to whip up #8025. 😄 |
Nevermind, it works when you remember to add the style layer to the mapView. Much noob! |
iOS portion of #4860
depends on: #7372
related to: #7752
Implement DDS getters and setters