-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
implement zoom and data driven functions #2454
Conversation
@scothis how does the proposed function syntax look from a Studio perspective? what restrictions would help make the UI simpler? |
I would prefer this simpler but equally expressive syntax: {
'circle-radius': {
stops: [
[{$zoom: 0, mapbox: 0}, 2],
[{$zoom: 0, mapbox: 100}, 10],
[{$zoom: 6, mapbox: 0}, 20],
[{$zoom: 6, mapbox: 100}, 100],
}
} |
I lean toward the syntax @lucaswoj proposes because it follows the current key-value pattern we have now for stops. The difference is that we have a compound key. However, the syntax does lead me to believe that the property being data-driven can change. For example:
I might propose yet another option that enforces a single property to derive values from:
Either syntax is workable if we apply the right validation constraints. It will be better to strongly enforce validation constraints early on and slowly relax them as needed than the other way around. |
I would like to eventually support functions with stops based on multiple properties like: [{$zoom: 0, population: 100, landarea: 0}, 2] I agree that we should enforce homogeneous properties programmatically |
The main constraint would be that you need to have a stop for every pair of zoom and value used in other stops. If you imagine it as a spreadsheet with zoom on one axis and data values on the other axis then every cell needs a style value. In pseudo code:
There would be exactly
This looks good to me. Maybe without the I slightly prefer syntaxes with nested arrays because it's closer to how the implementation works. The data functions are evaluated for a property to create a custom zoom function for each feature. The stops can be sorted into nested arrays easily so it's not a big difference.
I'm imagining the zoom+data functions as spreadsheets and 3d, 4d, etc spreadsheets are scary. I think the number of stops would get unwieldy really fast if stops support multiple properties. There might be simpler ways can handle these use cases. I think it makes sense to stick with 2d functions for now. |
bb9aa63
to
8abd688
Compare
spec and function pull requests: |
Let's think carefully about this spec so that we never have to break backwards compatibility. This should be orthogonal to our short term vs long term priorities.
I'm 👎 on a solution that allows only interpolation in some cases and allows only arbitrary functions in other cases. We should go all-in on interpolation or all-in on arbitrary functions. @scothis and I talked on Friday about the implications of supporting arbitrary functions in studio. He was in favor of arbitrary functions built atop the existing filter syntax. We could expose our existing interpolation functions as primitives to these functions: "circle-radius": [
'exponential', // function name
[[0, 10], [20, 100]] // stops
] Spitballing additional syntax here: "circle-radius": [
'exponential', // function name
[[0, 10], [20, 100]], // stops
'$zoom', // input
2 // base
] "circle-radius": [
'exponential', // function name
[[0, 10], [20, 100]], // stops
['*', '$zoom', 'population'], // input
] "circle-radius": ['*', '$zoom', 'population'] "circle-color": ['if',
[['>=', 'population', 10000], 'red'],
['else', 'blue'],
] To support this, we'd probably have to move to a model of dynamically writing more shader code. |
I added a caveat that we will need the ability to have and detect pre-built functions in Studio. Most users should not need to drop to the low level syntax in order to do basic interpolation. |
To clarify what this means:
|
I'm strongly 👎 on arbitrary functions that mix zoom and data inputs. The current zoom+data implementation is designed so that the data part of the function can be evaluated separately from the zoom part. This let's us evaluate the data part in -js when creating the buffers and the zoom part in the shaders on every frame. The current limitations allow for highly optimized implementation, both in terms of shader complexity and buffer size. For example, to evaluate
I'm against 3d, 4d, etc interpolation because I can't think of a case where n-dimensional interpolation is what you actually want. I also think that in order for the interpolation to be unambiguous you would need to provide an exponential number of stops, which seems really unwieldy. My suggestion was to use an expression of data properties as the input to the scale, instead of just a direct data property. There would be no conditionals or comparisons. Just math operators that combine data values. I don't think we need to do this yet.
We've already committed to scales/interpolation for data driven functions. It makes sense zoom+data functions work the same way. This is what's currently implemented. The current implementation covers a lot of use cases and fits in with the existing spec. Any large changes that would be breaking for these zoom+data functions would likely also be breaking for the existing separate zoom functions and data functions. I think we should go with something similar to what's implemented here. It might be helpful to voice about this today. |
I agree with all of your points above ☝️ I'm not particularly tied to any particular syntax specification for this feature but I have a couple requirements that aren't met by the nested function syntax:
Let's voice on this later today and see if we can figure out a function syntax specification that meets all of our requirements. |
Sorry, I never mentioned that I switched the syntax to the one proposed by you and @scothis. It now looks like this: Lines 53 to 69 in 8abd688
|
|
||
for (var c = 0; c < bucket.childLayers.length; c++) { | ||
var childLayer = bucket.childLayers[c]; | ||
layerPaintAttributes[childLayer.id] = { enabled: [], disabled: [] }; | ||
layerPaintAttributes[childLayer.id] = { enabled: [], uniforms: [] }; |
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 make more sense to stick to either a) enabled
/disabled
or b) attributes
/uniforms
?
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'll switch this to attributes
/ uniforms
. Zoom + data functions create uniforms
All the code in this PR looks great! Next Steps
|
return mix(value2, value3, t - 2.0); | ||
} | ||
} | ||
|
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.
If we want to bring this to all other shaders, it will start growing very complex and repetitive... Perhaps we should consider a GLSL build system like https://github.com/stackgl/glslify
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 could alternately use a simpler approach than glslify
ref 50f2cf5#commitcomment-16958568
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 could make a zoom_functions.glsl
and prepend it to all vertex shaders.
How do I start using this? Will this be implemented in Studio? Documentation on how to do this via style JSON? |
@dpieri This feature will be implemented in Studio once it has been expanded to all paint properties and is usable across both GL JS and GL Native platforms. Think of this like a dev preview for now. |
The syntax we eventually decided upon is: {
'circle-radius': {
property: 'landarea',
stops: [
[{zoom: 0, value: 0}, 2],
[{zoom: 0, value: 100}, 10],
[{zoom: 6, value: 0}, 20],
[{zoom: 6, value: 100}, 100],
}
} |
fixes #2428
For zoom+data driven functions, the buffers contain the values for four stops. Each shader reads all four values and uses a uniform, like
u_radius_t
to interpolate between the stops. Attributes with one component, likeradius
, are packed into a vec4. Attributes with more than one component are passed as separate glsl attributes.Since functions may have more than four zoom stops the buffers may not hold all the stops. It adds the four nearest stops to the buffers (two stops before the tile's zoom, two stops after). The uniform used for interpolation accounts for this.
Zoom+data driven functions currently look like this:
The current implementation does not require that all the data subfunctions have the same stops or the same 'property' value. We may want to add that restriction to make UIs for editing functions simpler. Should these be valid? https://gist.github.com/ansis/625ed5b14e046a09b79a4ddb78cf214f
Does this function definition look ok, or does anyone have other ideas? I'll fix style validation after we settle on a format.
https://github.com/mapbox/mapbox-gl-function/tree/zoom-and-data implements the mapbox-gl-function part of this.
👀 @lucaswoj @jfirebaugh