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

Commit

Permalink
[android] add number format expression
Browse files Browse the repository at this point in the history
  • Loading branch information
tobrun committed Aug 21, 2019
1 parent 888d2b0 commit 3321bde
Show file tree
Hide file tree
Showing 4 changed files with 327 additions and 13 deletions.
4 changes: 4 additions & 0 deletions platform/android/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
Mapbox welcomes participation and contributions from everyone. If you'd like to do so please see the [`Contributing Guide`](https://github.com/mapbox/mapbox-gl-native/blob/master/CONTRIBUTING.md) first to get started.

## master

### Features
- Add number-format expression [#15424](https://github.com/mapbox/mapbox-gl-native/pull/15424)

### Bug fixes
- Fixed use of objects after moving, potentially causing crashes. [#15408](https://github.com/mapbox/mapbox-gl-native/pull/1540)
- Fixed a possible crash that could be caused by invoking the wrong layer implementation casting function [#15398](https://github.com/mapbox/mapbox-gl-native/pull/15398).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.Size;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
Expand Down Expand Up @@ -3123,6 +3122,40 @@ public static Expression number(@NonNull Expression... input) {
return new Expression("number", input);
}

/**
* Converts the input number into a string representation using the providing formatting rules.
* If set, the locale argument specifies the locale to use, as a BCP 47 language tag.
* If set, the currency argument specifies an ISO 4217 code to use for currency-style formatting.
* If set, the min-fraction-digits and max-fraction-digits arguments specify the minimum and maximum number
* of fractional digits to include.
*
* @param number number expression
* @param options number formatting options
* @return expression
*/
public static Expression numberFormat(@NonNull Expression number, @NonNull NumberFormatOption... options) {
final Map<String, Expression> map = new HashMap<>();
for (NumberFormatOption option : options) {
map.put(option.type, option.value);
}
return new Expression("number-format", number, new ExpressionMap(map));
}

/**
* Converts the input number into a string representation using the providing formatting rules.
* If set, the locale argument specifies the locale to use, as a BCP 47 language tag.
* If set, the currency argument specifies an ISO 4217 code to use for currency-style formatting.
* If set, the min-fraction-digits and max-fraction-digits arguments specify the minimum and maximum number
* of fractional digits to include.
*
* @param number number expression
* @param options number formatting options
* @return expression
*/
public static Expression numberFormat(@NonNull Number number, @NonNull NumberFormatOption... options) {
return numberFormat(literal(number), options);
}

/**
* Asserts that the input value is a boolean.
* If multiple values are provided, each one is evaluated in order until a boolean value is obtained.
Expand Down Expand Up @@ -4364,21 +4397,138 @@ public static class FormatEntry {
}
}

/**
* Base class for an option entry that is encapsulated as a json object member for an expression.
*/
private static class Option {
@NonNull
String type;
@NonNull
Expression value;

/**
* Create an option option entry that is encapsulated as a json object member for an expression.
*
* @param type json object member name
* @param value json object member value
*/
Option(@NonNull String type, @NonNull Expression value) {
this.type = type;
this.value = value;
}
}

/**
* Holds format options used in a {@link #numberFormat(Number, NumberFormatOption...)} expression.
*/
public static class NumberFormatOption extends Option {

/**
* {@inheritDoc}
*/
NumberFormatOption(@NonNull String type, @NonNull Expression value) {
super(type, value);
}

/**
* Number formatting option for specifying the locale to use, as a BCP 47 language tag.
*
* @param string the locale to use while performing number formatting
* @return number format option
*/
@NonNull
public static NumberFormatOption locale(@NonNull Expression string) {
return new NumberFormatOption("locale", string);
}

/**
* Number formatting option for specifying the locale to use, as a BCP 47 language tag.
*
* @param string the locale to use while performing number formatting
* @return number format option
*/
@NonNull
public static NumberFormatOption locale(@NonNull String string) {
return new NumberFormatOption("locale", literal(string));
}

/**
* Number formatting option for specifying the currency to use, an ISO 4217 code.
*
* @param string the currency to use while performing number formatting
* @return number format option
*/
@NonNull
public static NumberFormatOption currency(@NonNull Expression string) {
return new NumberFormatOption("currency", string);
}

/**
* Number formatting options for specifying the currency to use, an ISO 4217 code.
*
* @param string the currency to use while performing number formatting
* @return number format option
*/
@NonNull
public static NumberFormatOption currency(@NonNull String string) {
return new NumberFormatOption("currency", literal(string));
}

/**
* Number formatting options for specifying the minimum fraction digits to include.
*
* @param number the amount of minimum fraction digits to include
* @return number format option
*/
@NonNull
public static NumberFormatOption minFractionDigits(@NonNull Expression number) {
return new NumberFormatOption("min-fraction-digits", number);
}

/**
* Number formatting options for specifying the minimum fraction digits to include.
*
* @param number the amount of minimum fraction digits to include
* @return number format option
*/
@NonNull
public static NumberFormatOption minFractionDigits(int number) {
return new NumberFormatOption("min-fraction-digits", literal(number));
}

/**
* Number formatting options for specifying the maximum fraction digits to include.
*
* @param number the amount of minimum fraction digits to include
* @return number format option
*/
@NonNull
public static NumberFormatOption maxFractionDigits(@NonNull Expression number) {
return new NumberFormatOption("max-fraction-digits", number);
}

/**
* Number formatting options for specifying the maximum fraction digits to include.
*
* @param number the amount of minimum fraction digits to include
* @return number format option
*/
@NonNull
public static NumberFormatOption maxFractionDigits(@NonNull int number) {
return new NumberFormatOption("max-fraction-digits", literal(number));
}
}

/**
* Holds format options used in a {@link #formatEntry(Expression, FormatOption...)} that builds
* a {@link #format(FormatEntry...)} expression.
* <p>
* If an option is not set, it defaults to the base value defined for the symbol.
*/
public static class FormatOption {
@NonNull
private String type;
@NonNull
private Expression value;
public static class FormatOption extends Option {

FormatOption(@NonNull String type, @NonNull Expression value) {
this.type = type;
this.value = value;
super(type, value);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@
import org.junit.runner.RunWith;

import java.io.IOException;
import java.util.Arrays;

import timber.log.Timber;

import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.formatFontScale;
import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.formatTextColor;
import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.formatTextFont;
import static com.mapbox.mapboxsdk.style.expressions.Expression.NumberFormatOption.currency;
import static com.mapbox.mapboxsdk.style.expressions.Expression.NumberFormatOption.locale;
import static com.mapbox.mapboxsdk.style.expressions.Expression.NumberFormatOption.maxFractionDigits;
import static com.mapbox.mapboxsdk.style.expressions.Expression.NumberFormatOption.minFractionDigits;
import static com.mapbox.mapboxsdk.style.expressions.Expression.collator;
import static com.mapbox.mapboxsdk.style.expressions.Expression.eq;
import static com.mapbox.mapboxsdk.style.expressions.Expression.exponential;
Expand All @@ -41,6 +46,7 @@
import static com.mapbox.mapboxsdk.style.expressions.Expression.literal;
import static com.mapbox.mapboxsdk.style.expressions.Expression.match;
import static com.mapbox.mapboxsdk.style.expressions.Expression.number;
import static com.mapbox.mapboxsdk.style.expressions.Expression.numberFormat;
import static com.mapbox.mapboxsdk.style.expressions.Expression.rgb;
import static com.mapbox.mapboxsdk.style.expressions.Expression.rgba;
import static com.mapbox.mapboxsdk.style.expressions.Expression.step;
Expand All @@ -58,7 +64,9 @@
import static com.mapbox.mapboxsdk.testapp.action.MapboxMapAction.invoke;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

@RunWith(AndroidJUnit4.class)
public class ExpressionTest extends EspressoTest {
Expand Down Expand Up @@ -561,6 +569,142 @@ public void testTextFieldFormattedArgument() {
});
}

@Test
public void testNumberFormatCurrencyExpression() {
validateTestSetup();
invoke(mapboxMap, (uiController, mapboxMap) -> {
LatLng latLng = new LatLng(51, 17);
mapboxMap.getStyle()
.addSource(new GeoJsonSource("source", Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())));
SymbolLayer layer = new SymbolLayer("layer", "source");
mapboxMap.getStyle().addLayer(layer);

layer.setProperties(
textField(
numberFormat(12.345, locale("en-US"), currency("USD"))
)
);
TestingAsyncUtils.INSTANCE.waitForLayer(uiController, mapView);

assertFalse(mapboxMap.queryRenderedFeatures(
mapboxMap.getProjection().toScreenLocation(latLng), "layer").isEmpty()
);
assertNull(layer.getTextField().getExpression());
assertEquals("$12.35", layer.getTextField().getValue().getFormattedSections()[0].getText());
});
}

@Test
public void testNumberFormatMaxExpression() {
validateTestSetup();
invoke(mapboxMap, (uiController, mapboxMap) -> {
LatLng latLng = new LatLng(51, 17);
mapboxMap.getStyle()
.addSource(new GeoJsonSource("source", Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())));
SymbolLayer layer = new SymbolLayer("layer", "source");
mapboxMap.getStyle().addLayer(layer);

layer.setProperties(
textField(
numberFormat(12.34567890, maxFractionDigits(5), minFractionDigits(0))
)
);
TestingAsyncUtils.INSTANCE.waitForLayer(uiController, mapView);

assertFalse(mapboxMap.queryRenderedFeatures(
mapboxMap.getProjection().toScreenLocation(latLng), "layer").isEmpty()
);
assertNull(layer.getTextField().getExpression());
assertEquals("12.34568", layer.getTextField().getValue().getFormattedSections()[0].getText());
});
}

@Test
public void testNumberFormatMinExpression() {
validateTestSetup();
invoke(mapboxMap, (uiController, mapboxMap) -> {
LatLng latLng = new LatLng(51, 17);
mapboxMap.getStyle()
.addSource(new GeoJsonSource("source", Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())));
SymbolLayer layer = new SymbolLayer("layer", "source");
mapboxMap.getStyle().addLayer(layer);

layer.setProperties(
textField(
numberFormat(12.0000001, maxFractionDigits(5), minFractionDigits(0))
)
);
TestingAsyncUtils.INSTANCE.waitForLayer(uiController, mapView);

assertFalse(mapboxMap.queryRenderedFeatures(
mapboxMap.getProjection().toScreenLocation(latLng), "layer").isEmpty()
);
assertNull(layer.getTextField().getExpression());
assertEquals("12", layer.getTextField().getValue().getFormattedSections()[0].getText());
});
}

@Test
public void testNumberFormatLocaleExpression() {
validateTestSetup();
invoke(mapboxMap, (uiController, mapboxMap) -> {
LatLng latLng = new LatLng(51, 17);
mapboxMap.getStyle()
.addSource(new GeoJsonSource("source", Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())));
SymbolLayer layer = new SymbolLayer("layer", "source");
mapboxMap.getStyle().addLayer(layer);

layer.setProperties(
textField(
numberFormat(12.0000001, locale("nl-BE"), maxFractionDigits(5), minFractionDigits(1))
)
);
TestingAsyncUtils.INSTANCE.waitForLayer(uiController, mapView);

assertFalse(mapboxMap.queryRenderedFeatures(
mapboxMap.getProjection().toScreenLocation(latLng), "layer").isEmpty()
);
assertNull(layer.getTextField().getExpression());
assertEquals("12,0", layer.getTextField().getValue().getFormattedSections()[0].getText());
});
}

@Test
public void testNumberFormatNonConstantExpression() {
validateTestSetup();
invoke(mapboxMap, (uiController, mapboxMap) -> {
LatLng latLng = new LatLng(51, 17);
Feature feature = Feature.fromGeometry(Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude()));
feature.addNumberProperty("number_value", 12.345678);

mapboxMap.getStyle().addSource(new GeoJsonSource("source", feature));
SymbolLayer layer = new SymbolLayer("layer", "source");
mapboxMap.getStyle().addLayer(layer);

Expression numberFormatExpression = numberFormat(
number(get("number_value")),
locale("nl-BE"),
maxFractionDigits(5),
minFractionDigits(1)
);

layer.setProperties(textField(numberFormatExpression));

TestingAsyncUtils.INSTANCE.waitForLayer(uiController, mapView);

assertFalse(mapboxMap.queryRenderedFeatures(
mapboxMap.getProjection().toScreenLocation(latLng), "layer").isEmpty()
);

assertNotNull(layer.getTextField().getExpression());

// Expressions evaluated to string are wrapped by a format expression, take array index 1 to get original
Object[] returnExpression = (Object[]) layer.getTextField().getExpression().toArray()[1];
Object[] setExpression = numberFormatExpression.toArray();
assertTrue("Object[] expression representation should match", Arrays.deepEquals(returnExpression, setExpression));
});
}

private void setupStyle() {
invoke(mapboxMap, (uiController, mapboxMap) -> {
// Add a source
Expand Down
Loading

0 comments on commit 3321bde

Please sign in to comment.