Closed jfirebaugh closed 7 years ago
One point here worth reconsidering (or maybe you'd already reconsidered it enough) is the term property
. Not only is it an overloaded term, but, as @tmcw pointed out in chat, it seems inconsistent with the vector tile spec's use of attribute
: https://www.mapbox.com/vector-tiles/specification/#encoding-attr, https://github.com/mapbox/vector-tile-spec/tree/master/2.1#44-feature-attributes. The more we can reduce ambiguity between property
, attribute
, field
, etc., the better.
I'd like to stick with "property". https://github.com/mapbox/vector-tile-spec/issues/63
One point here worth reconsidering (or maybe you'd already reconsidered it enough) is the term property. Not only is it an overloaded term, but, as @tmcw pointed out in chat, it seems inconsistent with the vector tile spec's use of attribute
FYI, the iOS and macOS SDKs do standardize on “attribute” because “property” is already a language feature. I just couldn’t fathom MGLFeature storing properties inside a properties
property (but not properties like identifier
and coordinates
). Fortunately, that kind of confusion seems to be less of a problem for the style and vector tile specifications than for the SDKs.
It seems like identity
is a special case of a continuous
function?
I think this proposal simplifies and clarifies the function spec. The main UI challenge here will be communicating the four function types: continuous, discrete, identity, composite to users. Onces a user understands them, everything else will be much more intuitive.
Some thoughts:
An identity function returns the value of the selected property. In some cases (when the property values form a continuous domain/range), it can be viewed as a special case of continuous
, but I think it's better served with a distinct type:
interpolation-*
or stops
properties and their evaluation algorithm is different than a continuous function.text-anchor
whose property is set to a feature property for a source layer of city labels which use "right"
for west-coast cities and "left"
for east-coast cities, in order to place labels over water when possible.We could split out another type: zoom
. This type would work like continuous
, but it would not allow a property
property, and continuous
could then require property
and always be "data-driven". This partitions the types:
zoom-driven: zoom
data-driven: continuous
, discrete
, identity
zoom-and-data-driven: composite
We've talked about allowing e.g. pitch-driven functions, so to generalize this further, we could name this type camera
, give it a camera-property
property defaulting to zoom
, and call it "camera-driven" rather than "zoom-driven". (To take this approach further, I'd advocate for calling the second category "source-driven" rather than "data-driven", renaming the property
property to source-property
, and so on.)
We've talked about allowing e.g. pitch-driven functions, so to generalize this further, we could name this type camera, give it a camera-property property defaulting to zoom, and call it "camera-driven" rather than "zoom-driven".
Not to derail this discussion, and I’m not sure how practical it would be, but I wonder if it’d be more useful to vary paint and layout properties by viewing distance (as a more generalized version of zoom level) than by pitch. That is, when the pitch is 60°, a marker close to the viewer would use a more detailed icon while a marker far from the viewer would abbreviate its label more, and this would hold true as the center coordinate changes but the zoom level and pitch stay constant. I suppose a viewing distance–driven function could coexist with a camera-driven function.
I'm excited about this effort to simplify the function syntax! We have learned so much about the UX of functions since the initial syntax was designed.
The taxonomy of functions proposed in https://github.com/mapbox/mapbox-gl-style-spec/issues/613#issuecomment-264524327 is great. By eliminating invalid combinations of functions we make the spec more robust, understandable, and pave the way for UI design in Studio.
I have some thoughts about the naming of these new types. Some of the names refer to the nature of the data (continuous
, discrete
) while others refer to the source of the data (i.e. zoom
, camera
, composite
). Including the nature of the data and the source of the data in each name makes the taxonomy clear from the names alone:
camera-continuous
source-continuous
, source-discrete
, source-identity
camerasource-continuous
This definitely clarifies and simplifies the function landscape. How does this interact with the idea of bringing in arbitrary function expressions (https://github.com/mapbox/mapbox-gl-function/issues/28)? Would that provide an alternative to some of the concepts in this proposal? For instance:
zoom
and properties.foo
(and properties.bar
, for that matter) just as different input variables into a multivariate function, this could eliminate the need to distinguish zoom-driven from property-driven functions from the user's POV (even though the actual implementation might still distinguish them).discrete
and continuous
essentially describe how values for an input variable are be interpreted. If the arbitrary function system includes a mechanism for explicitly declaring input variables and their types, that might be a better place for those descriptors.It is possible to simplify the taxonomy further by making the domain values the source of truth for the "nature" of the data (continuous
vs discrete
vs identity
)
I recognize that this taxonomy does not lend itself well to implementation in a static type system. We may need to explicitly differentiate between continuous
, discrete
and identity
functions in our GL Native type system.
a numeric or numeric array domain indicates a continuous function
A numeric does not necessarily indicate a continuous function. Numeric values are sometimes used as enumerated constants.
I'm purposefully leaving arbitrary function expressions out of this proposal. We have implementation experience (and are developing UI experience) for the function types in this proposal, and the proposal is guided by that experience. We don't have equivalent experience with function expressions.
A numeric does not necessarily indicate a continuous function. Numeric values are sometimes used as enumerated constants.
This is a moot point in practice. If the domain is a set of enumerated constants, there's no* input value for which the output value of a numeric "source-continuous" function is different than the output value of a "source-discrete" function.
{
type: "source-continuous",
stops: [[0, 0], [2, 4]]
}
{
type: "source-discrete",
stops: [[0, 0], [2, 4]]
}
* Depending our implementation, invalid domain values may result in a different output values. The behaviour in this case could be controlled by the user via the interpolation
parameter.
Earlier I said I'd prefer not to merge identity
with continuous
, but I can imagine merging all three of continuous
, discrete
and identity
into a single source
type. identity
would be replaced with a source
type function without stops
or interpolation-*
, while discrete
would be replaced with a source
type function with "interpolation": "none"
.
This produces three types: camera
, source
, and composite
, and suggests a UI that first asks what the input to the function should be, rather than asking for a function type. For example, this could be a dropdown with options:
If "zoom" or "pitch" is chosen, it's a camera
function with the selected camera-property
. If a source property is chosen, it's source
, and then metadata about the property values can guide further choices (e.g. if the values are strings, it's got to be "interpolation": "none"
; if they're numeric, provide an "interpolation" UI option defaulted to "exponential").
This is a moot point in practice.
It isn't moot once we introduce default values for discrete functions. https://github.com/mapbox/mapbox-gl-style-spec/issues/480
It isn't moot once we introduce default values for discrete functions. #480
Good point. Could it make sense to have a "interpolation": "default-value"
mode?
* Depending our implementation, invalid domain values may result in a different output values. The behaviour in this case could be controlled by the user via the
interpolation
parameter.
Love the proposal! This will certainly make things much easier to reason about. The only point I'm not sure about is the composite functions proposal (item 5 in the post) — turning them into nested functions where outer/inner functions can have different interpolations sounds pretty complicated (in addition to making the definitions huge). As far as I recall, @ansis considered this at some point but rejected it because there are technical difficulties in implementation. Is this right?
This produces three types: camera, source, and composite, and suggests a UI that first asks what the input to the function should be, rather than asking for a function type.
We would likely design a UI like this either way so I am in favor writing it into the spec so our ui models the spec better.
This produces three types:
camera
,source
, andcomposite
...
These three types correspond to three fundamental strategies in the rendering implementation:
camera
function is evaluated on the CPU, on the main thread. The result is provided to the shader via a uniform (in GL JS) or constant attribute (in GL native).source
function is evaluated on the CPU, on the worker thread. The result (an array of per-feature values) is provided to the shader via a single vertex attribute.source
function is evaluated on the CPU, on the worker thread, while the "zoom" portion is evaluated in the shader. The intermediate result (an array of per-feature values for each of several zoom levels) is provided to the shader via several vertex attributes, and the shader blends between them according to the current camera value, which is provided via a uniform.So camera
/source
/composite
forms a taxonomy that is a good model for both the implementation and the UI. This is a strong indication that it's a good taxonomy.
This issue was moved to mapbox/mapbox-gl-js#4154
The space of function types is currently difficult to grok because there are effectively two axes of function types:
It's not well documented or easy to grasp how these two axes combine, or what combinations are valid. I've tried to do so in #604, but this doesn't remove the underlying complexity from the domain model.
I believe that in order for users to use functions effectively, we need to rationalize and simplify our taxonomy.
Here's what I propose:
type
axis and property for functions, with the following values:continuous
-- a function in which stop domain values represent sample points in a continuous domaindiscrete
-- a function in which stop domain values enumerate a discrete domainidentity
-- the identity functioncomposite
-- the type formerly known as "zoom-and-property"type
property is always required and never deduced from other property values. Implementations and user interfaces can always look at this value and then decide what to do; they never have to implement a type-deduction algorithm, which would need to be synchronized between implementations and updated as new rules are introduced.property
property is optional forcontinuous
functions, and required fordiscrete
andidentity
functions (see below forcomposite
functions). If present, the function is said to be a "data-driven function". If absent, the function is said to be a "zoom-driven function".continuous
orcomposite
function supports the following properties:interpolation
-- possible values are"exponential"
(default for "interpolated" properties; allowed only for interpolated properties) and"step-after"
(default for "piecewise-constant" properties; replaces the previousinterval
function type).interpolation-base
-- renamed frombase
. Permitted only with"interpolation": "exponential"
.interpolation-color-space
-- renamed fromcolorSpace
. Permitted only only with"interpolation": "exponential"
and when the range type iscolor
.composite
functions are floating point zoom level values. Stop range values forcomposite
functions are functions, with types limited tocontinuous
anddiscrete
.type
,property
, andinterpolation-*
properties. Theproperty
value is always required.interpolation-*
properties that differ from the inner functions.Because this proposal involves numerous breaking changes, it would most likely have to involve introducing a new version of the style specification. I think that reducing the complexity of the function taxonomy would be worth a style version bump, even if we made no other breaking changes.
cc @mapbox/gl @mapbox/appbox