Skip to content
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

Rationalize and simplify the taxonomy of functions #4154

Closed
lucaswoj opened this issue Feb 1, 2017 · 22 comments
Closed

Rationalize and simplify the taxonomy of functions #4154

lucaswoj opened this issue Feb 1, 2017 · 22 comments
Labels
breaking change ⚠️ Requires a backwards-incompatible change to the API cross-platform 📺 Requires coordination with Mapbox GL Native (style specification, rendering tests, etc.)

Comments

@lucaswoj
Copy link
Contributor

lucaswoj commented Feb 1, 2017

From @jfirebaugh on December 1, 2016 23:18

The space of function types is currently difficult to grok because there are effectively two axes of function types:

  • identity, exponential, interval, and categorical
  • zoom, property, zoom-and-property

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:

  1. A single type axis and property for functions, with the following values:
    • continuous -- a function in which stop domain values represent sample points in a continuous domain
    • discrete -- a function in which stop domain values enumerate a discrete domain
    • identity -- the identity function
    • composite -- the type formerly known as "zoom-and-property"
  2. The 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.
  3. A property property is optional for continuous functions, and required for discrete and identity functions (see below for composite 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".
  4. A continuous or composite 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 previous interval function type).
    • interpolation-base -- renamed from base. Permitted only with "interpolation": "exponential".
    • interpolation-color-space -- renamed from colorSpace. Permitted only only with "interpolation": "exponential" and when the range type is color.
  5. Stop domain values for composite functions are floating point zoom level values. Stop range values for composite functions are functions, with types limited to continuous and discrete.
    • However all such "inner" functions must share the same type, property, and interpolation-* properties. The property value is always required.
    • The "outer" composite function may have interpolation-* properties that differ from the inner functions.
    • Ideally we would enforce these requirements via object structure somehow, but I'm not sure of the best way to do so.

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

Copied from original issue: mapbox/mapbox-gl-style-spec#613

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @davidtheclark on December 2, 2016 0:33

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.

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @jfirebaugh on December 2, 2016 0:54

I'd like to stick with "property". mapbox/vector-tile-spec#63

@lucaswoj lucaswoj added the breaking change ⚠️ Requires a backwards-incompatible change to the API label Feb 1, 2017
@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @1ec5 on December 2, 2016 9:56

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.

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @kkaefer on December 2, 2016 11:18

It seems like identity is a special case of a continuous function?

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @samanpwbb on December 2, 2016 15:56

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:

  • There is temptation in Studio to try to 'guess' function types for users based on information about data coming from tilestats and tileJSON. I think there's going to be a right way to do this and a wrong way. Ideally we: 1) have smart defaults, 2) make it possible for user to change those defaults and 3) do a great job illustrating what these function types actually mean through visuals and consistent clear language.
  • This proposal is compatible with a 'zoom function' button in a UI if we want.
  • What exactly is an 'identity' function? Does it need to be it's own type?

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @jfirebaugh on December 2, 2016 18:14

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:

  • Identity functions don't allow interpolation-* or stops properties and their evaluation algorithm is different than a continuous function.
  • Some property values may in fact form a discrete domain/range. Imagine an identity function used for 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.

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @jfirebaugh on December 2, 2016 18:21

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

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @1ec5 on December 2, 2016 18:52

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.

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

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 mapbox/mapbox-gl-style-spec#613 (comment) 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 functions: camera-continuous
  • source functions: source-continuous, source-discrete, source-identity
  • camerasource functions: camerasource-continuous

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @anandthakker on December 2, 2016 19:12

This definitely clarifies and simplifies the function landscape. How does this interact with the idea of bringing in arbitrary function expressions (mapbox/mapbox-gl-function#28)? Would that provide an alternative to some of the concepts in this proposal? For instance:

  • If the arbitrary function approach could treat 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.

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

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)

  • a string or boolean domain indicates a discrete function
  • a numeric or numeric array domain indicates a continuous function
  • the lack of a domain indicates an identity function

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.

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @jfirebaugh on December 2, 2016 19:27

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.

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @jfirebaugh on December 2, 2016 19:31

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.

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

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.

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @jfirebaugh on December 2, 2016 19:57

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:

  • zoom
  • pitch
  • -- menu divider --
  • property_a
  • property_b
  • property_c
  • -- menu divider --
  • zoom and property
  • pitch and property

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").

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @jfirebaugh on December 2, 2016 19:58

This is a moot point in practice.

It isn't moot once we introduce default values for discrete functions. mapbox/mapbox-gl-style-spec#480

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

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.

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @mourner on December 2, 2016 21:42

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?

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @samanpwbb on December 5, 2016 16:10

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.

@lucaswoj
Copy link
Contributor Author

lucaswoj commented Feb 1, 2017

From @jfirebaugh on December 6, 2016 1:3

This produces three types: camera, source, and composite...

These three types correspond to three fundamental strategies in the rendering implementation:

  • A 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).
  • A 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.
  • The "source" portion of a 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.

@1ec5
Copy link
Contributor

1ec5 commented Apr 17, 2017

A belated update: Android SDK v5.0.0, iOS SDK v3.5.0, and macOS SDK v0.3.0 all shipped with a runtime styling API that conforms to the taxonomy proposed in #4154 (comment). (These SDKs support the same existing style JSON syntax as GL JS for the time being.) The remaining work is to modify the style specification (and thus the style JSON file format and GL JS’s runtime styling API) to conform to this taxonomy as well.

@anandthakker
Copy link
Contributor

Addressed by #4777

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking change ⚠️ Requires a backwards-incompatible change to the API cross-platform 📺 Requires coordination with Mapbox GL Native (style specification, rendering tests, etc.)
Projects
None yet
Development

No branches or pull requests

3 participants