Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Expressions #10654

Merged
merged 1 commit into from
Dec 19, 2017
Merged

Expressions #10654

merged 1 commit into from
Dec 19, 2017

Conversation

tobrun
Copy link
Member

@tobrun tobrun commented Dec 7, 2017

This PR adds binding integration for expressions from #9439

Symbol layer example:

ezgif com-video-to-gif 6

    mapboxMap.addLayer(new SymbolLayer(LAYER_ID, SOURCE_ID)
      .withProperties(

        // icon configuration
        iconImage(get(FEATURE_ID)),
        iconAllowOverlap(false),
        iconSize(
          division(get(FEATURE_RANK), 2)
        ),
        iconAnchor(ICON_ANCHOR_BOTTOM),

        // text field configuration
        textField(
          concat(
            upcase("a "),
            get(FEATURE_TYPE),
            downcase(" IN "),
            get(FEATURE_REGION)
          )
        ),
        textSize(
          product(get(FEATURE_RANK), pi())
        ),
        textAnchor(TEXT_ANCHOR_TOP)
      )
    );

Todo:

  • Expression.java
    • implement all expressions from style-spec
    • allow for nested expressions
    • typed API;
      • String
      • Math
      • Color
      • Feature data
      • Lookup
      • Decision
      • Ramps, scales, curves
        • expose linear, exponential and cubicBezier expressions
        • expose interpolation + steps
      • Variable binding
      • Zoom
      • Heatmap
  • add integration in
    • layout properties
    • paint properties
    • filters -> not implemented, will be added as tailwork
  • junit tests validating the Object[] output from an expression
  • add javadoc
  • update test app examples (partially done, ongoing effort)
  • deprecate old API (done for layout/paint properties)

Closes #9803

cc @mapbox/android @anandthakker

@tobrun tobrun added the Android Mapbox Maps SDK for Android label Dec 7, 2017
@tobrun tobrun added this to the android-v6.0.0 milestone Dec 7, 2017
@tobrun tobrun self-assigned this Dec 7, 2017
@ThibaudM
Copy link

Could you also add a getFilter() accessor?
An implementation was proposed in #9077 but has been rejected.

@1ec5
Copy link
Contributor

1ec5 commented Dec 12, 2017

#9077 was declined because this PR will obsolete the underlying implementation details of that PR, but #7978 continues to track implementing a getFilter() accessor for parity with the other platforms.

@tobrun tobrun force-pushed the tvn-expressions-wip branch 4 times, most recently from 71c779e to ec425ff Compare December 12, 2017 19:48
@tobrun
Copy link
Member Author

tobrun commented Dec 12, 2017

Noting here that I would prefer doing expression filter support as tailwork. Would love to get some feedback on this PR first before moving forward on that. This PR is ready for review.

cc @ivovandongen @anandthakker

@anandthakker
Copy link
Contributor

Noting here that I would prefer doing expression filter support as tailwork

👍, especially since expression-based filters are still to be done in core

@tobrun
Copy link
Member Author

tobrun commented Dec 12, 2017

Ok, that explains a couple of things :) I initially thought I had to wire those up myself but now seeing that symbol_layer_impl.cpp etc is found in core.

Copy link
Contributor

@anandthakker anandthakker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

public class Expression {

private final String operator;
private final Object[] values;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word "value" gets so easily overloaded -- would it make sense to call this arguments, which is how we refer to them in the style spec as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intuitively, I'd have expected this to be Expression[] rather than Object[]. I'm guessing it's Object[] so that string/number/boolean literals can be used directly?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is correct, capturing from chat that I will look into the feasibility to use something as Expression instead.

*/
public static Expression var(Object... expressions) {
return new Expression("var", expressions);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name argument must be a string literal -- it can't be a subexpression -- so it might make sense to remove this overload.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can the string literal come from another expression?

eg.
var(get("featureKey"))

If that is the case I will remove the varargs approach and expose the following:

  • var(Expression expression)
  • var(String string)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope -- "literal" here means it can't be the output of an expression, but has to be a string directly.

* @param stops pair of input and output values
* @return expression
*/
public static Expression step(Expression expression, Object... stops) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename expression to input for consistency with docs?

* @param stops pair of input and output values
* @return expression
*/
public static Expression interpolate(Expression interpolation, Number number, Object... stops) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to step: maybe rename number to input?

* @param stops pair of input and output values
* @return expression
*/
public static Expression interpolate(Expression interpolation, Expression number, Object... stops) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could see the case for creating a separate Interpolation type for the first argument here. (So linear(), exponential(), and cubicBezier() would return an Interpolation value.) Not sure whether it's actually better to do so, just mentioning it as an alternative.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One benefit of doing it that way is that users couldn't do something weird like sum(linear(), 2). As it stands now, if they did this, they'd get an error message that linear is an unknown expression operator -- which might be confusing to them, since the linear() method claims to produce an expression.

@@ -27,9 +28,17 @@ public void setProperties(@NonNull PropertyValue<?>... properties) {
for (PropertyValue<?> property : properties) {
Object converted = convertValue(property.value);
if (property instanceof PaintPropertyValue) {
nativeSetPaintProperty(property.name, converted);
if(converted instanceof Expression) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be part of convertValue()? (And space between if and ()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow, don't know how I could have missed that

*
* @param <T>
*/
private static class ExpressionValue<T> extends Expression<T> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ExpressionLiteral might be a better name for this class.

* a combination of the zoom level and individual feature properties.
* </p>
*/
public class Expression<T> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document <T>

*/
public class Expression<T> {

private String operator;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can both be final so that the object is immutable?

private String operator;
private Expression[] arguments;

Expression() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not used except by the subclass? If it initializes the members to null, the object can be immutable

*/
private static class ExpressionValue<T> extends Expression<T> {

private Object object;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be <T> instead of Object?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes this should be enforced

* @param expression an expression to convert to a color
* @return expression
*/
public static Expression toRgba(Expression<Color> expression) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing generic specifier (<Color>) on return type?

Copy link
Member Author

@tobrun tobrun Dec 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct about missing specifier, this case it should be <Array> shown in style spec with ["to-rgba", color]: array<number, 4>).

* @param input boolean input
* @return expression
*/
public static Expression<Boolean> not(Boolean input) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be boolean instead? Otherwise, we should indicate nullability restrictions

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct, all boolean input should primitive

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also went through the api and applied @NonNull were needed

* @param expression an expression object or expression string
* @return expression
*/
public static Expression<Number> length(Expression expression) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it preferable to use Expression<?> over Expression here?

* @param operator the expression operator
* @param arguments expressions input
*/
public Expression(@NonNull String operator, @Nullable Expression... arguments) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use @Size(min=1) on varargs here?

Copy link
Member Author

@tobrun tobrun Dec 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are cases where the size may be 0, eg: ["pi"]: number or ["properties"]: object. I wasn't aware of this annotation, will try using it in other varargs occurrences in the class.

Copy link
Contributor

@ivovandongen ivovandongen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some commented code can be removed. Otherwise 👍

)
));
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be removed right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

} else if (item.getItemId() == R.id.menu_action_filter) {
SymbolLayer layer = mapboxMap.getLayerAs(LAYER_ID);
layer.setFilter(Filter.eq(FEATURE_RANK, 1));
//layer.setFilter(eq(get(FEATURE_RANK), 1));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be removed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@tobrun tobrun merged commit 757cc0f into master Dec 19, 2017
@tobrun tobrun deleted the tvn-expressions-wip branch December 19, 2017 07:55
@1ec5
Copy link
Contributor

1ec5 commented Jan 6, 2018

#9077 was declined because this PR will obsolete the underlying implementation details of that PR, but #7978 continues to track implementing a getFilter() accessor for parity with the other platforms.

Make that #10720.

@tobrun tobrun mentioned this pull request Jan 26, 2018
23 tasks
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Android Mapbox Maps SDK for Android
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants