summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorŁukasz Paczos <lukas.paczos@gmail.com>2018-09-28 09:05:41 +0200
committerŁukasz Paczos <lukasz.paczos@mapbox.com>2018-10-24 13:35:36 +0200
commit12b496945498358b7afb4af1efe4ae0c52b9c7d3 (patch)
treec4d4c31cbccfb39bcc8a4fd6a2a09f627173a56b
parent78a1c30e80c70f36ffec8193753919277418a3ca (diff)
downloadqtlocation-mapboxgl-12b496945498358b7afb4af1efe4ae0c52b9c7d3.tar.gz
[android] "format" expression support
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java319
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyValue.java4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java28
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs34
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/Formatted.java54
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/FormattedSection.java100
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/expressions/ExpressionTest.java115
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/CircleLayerTest.java7
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/ExpressionTest.java262
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillExtrusionLayerTest.java4
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillLayerTest.java4
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java2
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/LineLayerTest.java7
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java65
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/layer.junit.ejs58
-rw-r--r--platform/android/core-files.txt4
-rw-r--r--platform/android/src/conversion/constant.cpp3
-rwxr-xr-xplatform/android/src/jni.cpp4
-rw-r--r--platform/android/src/style/formatted.cpp43
-rw-r--r--platform/android/src/style/formatted.hpp23
-rw-r--r--platform/android/src/style/formatted_section.cpp11
-rw-r--r--platform/android/src/style/formatted_section.hpp18
-rw-r--r--platform/android/src/style/position.cpp2
23 files changed, 1062 insertions, 109 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java
index f586ecfdb2..1184baba65 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/expressions/Expression.java
@@ -183,7 +183,7 @@ public class Expression {
*/
public static Expression literal(@NonNull Object object) {
if (object.getClass().isArray()) {
- return literal(ExpressionArray.toObjectArray(object));
+ return literal(toObjectArray(object));
} else if (object instanceof Expression) {
throw new RuntimeException("Can't convert an expression to a literal");
}
@@ -197,7 +197,7 @@ public class Expression {
* @return the expression
*/
public static Expression literal(@NonNull Object[] array) {
- return new ExpressionArray(array);
+ return new Expression("literal", new ExpressionLiteralArray(array));
}
/**
@@ -2872,7 +2872,8 @@ public class Expression {
*
* @param expression the expression to evaluate
* @return expression
- * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-is-supported-script">Style specification</a>
+ * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-is-supported-script">Style
+ * specification</a>
*/
public static Expression isSupportedScript(Expression expression) {
return new Expression("is-supported-script", expression);
@@ -2902,7 +2903,8 @@ public class Expression {
*
* @param string the string to evaluate
* @return expression
- * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-is-supported-script">Style specification</a>
+ * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-is-supported-script">Style
+ * specification</a>
*/
public static Expression isSupportedScript(String string) {
return new Expression("is-supported-script", literal(string));
@@ -3224,9 +3226,121 @@ public class Expression {
return new Expression("collator", new ExpressionMap(map));
}
- public static Expression format(Expression input) {
- Map<String, Expression> map = new HashMap<>();
- return new Expression("format", input, new ExpressionMap(map));
+ /**
+ * Returns formatted text containing annotations for use in mixed-format text-field entries.
+ * <p>
+ * To build the expression, use {@link #formatEntry(Expression, FormatOption...)}.
+ * <p>
+ * "format" expression can be used, for example, with the {@link PropertyFactory#textField(Expression)}
+ * and accepts unlimited numbers of formatted sections.
+ * <p>
+ * Each section consist of the input, the displayed text, and options, like font-scale and text-font.
+ * <p>
+ * Example usage:
+ * </p>
+ * <pre>
+ * {@code
+ * SymbolLayer symbolLayer = new SymbolLayer("layer-id", "source-id");
+ * symbolLayer.setProperties(
+ * textField(
+ * format(
+ * formatEntry(
+ * get("header_property"),
+ * fontScale(2.0),
+ * textFont(new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"})
+ * ),
+ * formatEntry(concat(literal("\n"), get("description_property")), fontScale(1.5)),
+ * }
+ * </pre>
+ *
+ * @param formatEntries format entries
+ * @return expression
+ * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-types-format">Style specification</a>
+ */
+ public static Expression format(@NonNull FormatEntry... formatEntries) {
+ // for each entry we are going to build an input and parameters
+ Expression[] mappedExpressions = new Expression[formatEntries.length * 2];
+
+ int mappedIndex = 0;
+ for (FormatEntry formatEntry : formatEntries) {
+ // input
+ mappedExpressions[mappedIndex++] = formatEntry.text;
+
+ // parameters
+ Map<String, Expression> map = new HashMap<>();
+
+ if (formatEntry.options != null) {
+ for (FormatOption option : formatEntry.options) {
+ map.put(option.type, option.value);
+ }
+ }
+
+ mappedExpressions[mappedIndex++] = new ExpressionMap(map);
+ }
+
+ return new Expression("format", mappedExpressions);
+ }
+
+ /**
+ * Returns a format entry that can be used in {@link #format(FormatEntry...)} to create formatted text fields.
+ * <p>
+ * Text is required to be of a resulting type string.
+ * <p>
+ * Text is required to be passed; {@link FormatOption}s are optional and will default to the base values defined
+ * for the symbol.
+ *
+ * @param text displayed text
+ * @param formatOptions format options
+ * @return format entry
+ */
+ public static FormatEntry formatEntry(@NonNull Expression text, @Nullable FormatOption... formatOptions) {
+ return new FormatEntry(text, formatOptions);
+ }
+
+ /**
+ * Returns a format entry that can be used in {@link #format(FormatEntry...)} to create formatted text fields.
+ * <p>
+ * Text is required to be of a resulting type string.
+ * <p>
+ * Text is required to be passed; {@link FormatOption}s are optional and will default to the base values defined
+ * for the symbol.
+ *
+ * @param text displayed text
+ * @return format entry
+ */
+ public static FormatEntry formatEntry(@NonNull Expression text) {
+ return new FormatEntry(text, null);
+ }
+
+ /**
+ * Returns a format entry that can be used in {@link #format(FormatEntry...)} to create formatted text fields.
+ * <p>
+ * Text is required to be of a resulting type string.
+ * <p>
+ * Text is required to be passed; {@link FormatOption}s are optional and will default to the base values defined
+ * for the symbol.
+ *
+ * @param text displayed text
+ * @param formatOptions format options
+ * @return format entry
+ */
+ public static FormatEntry formatEntry(@NonNull String text, @Nullable FormatOption... formatOptions) {
+ return new FormatEntry(literal(text), formatOptions);
+ }
+
+ /**
+ * Returns a format entry that can be used in {@link #format(FormatEntry...)} to create formatted text fields.
+ * <p>
+ * Text is required to be of a resulting type string.
+ * <p>
+ * Text is required to be passed; {@link FormatOption}s are optional and will default to the base values defined
+ * for the symbol.
+ *
+ * @param text displayed text
+ * @return format entry
+ */
+ public static FormatEntry formatEntry(@NonNull String text) {
+ return new FormatEntry(literal(text), null);
}
/**
@@ -3981,18 +4095,7 @@ public class Expression {
if (arguments != null) {
for (Object argument : arguments) {
builder.append(", ");
- if (argument instanceof ExpressionLiteral) {
- Object literalValue = ((ExpressionLiteral) argument).toValue();
-
- // special case for handling unusual input like 'rgba(r, g, b, a)'
- if (literalValue instanceof String) {
- builder.append("\"").append(literalValue).append("\"");
- } else {
- builder.append(literalValue);
- }
- } else {
- builder.append(argument.toString());
- }
+ builder.append(argument.toString());
}
}
builder.append("]");
@@ -4231,6 +4334,99 @@ public class Expression {
}
/**
+ * Holds format entries used in a {@link #format(FormatEntry...)} expression.
+ */
+ public static class FormatEntry {
+ @NonNull
+ private Expression text;
+ @Nullable
+ private FormatOption[] options;
+
+ FormatEntry(@NonNull Expression text, @Nullable FormatOption[] options) {
+ this.text = text;
+ this.options = options;
+ }
+ }
+
+ /**
+ * 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;
+
+ FormatOption(@NonNull String type, @NonNull Expression value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ /**
+ * If set, the font-scale argument specifies a scaling factor relative to the text-size
+ * specified in the root layout properties.
+ * <p>
+ * "font-scale" is required to be of a resulting type number.
+ *
+ * @param expression expression
+ * @return format option
+ */
+ @NonNull
+ public static FormatOption fontScale(@NonNull Expression expression) {
+ return new FormatOption("font-scale", expression);
+ }
+
+ /**
+ * If set, the font-scale argument specifies a scaling factor relative to the text-size
+ * specified in the root layout properties.
+ * <p>
+ * "font-scale" is required to be of a resulting type number.
+ *
+ * @param scale value
+ * @return format option
+ */
+ @NonNull
+ public static FormatOption fontScale(double scale) {
+ return new FormatOption("font-scale", literal(scale));
+ }
+
+ /**
+ * If set, the text-font argument overrides the font specified by the root layout properties.
+ * <p>
+ * "text-font" is required to a literal array.
+ * <p>
+ * The requested font stack has to be a part of the used style.
+ * For more information see <a href="https://www.mapbox.com/help/define-font-stack/">the documentation</a>.
+ *
+ * @param expression expression
+ * @return format option
+ */
+ @NonNull
+ public static FormatOption textFont(@NonNull Expression expression) {
+ return new FormatOption("text-font", expression);
+ }
+
+ /**
+ * If set, the text-font argument overrides the font specified by the root layout properties.
+ * <p>
+ * "text-font" is required to a literal array.
+ * <p>
+ * The requested font stack has to be a part of the used style.
+ * For more information see <a href="https://www.mapbox.com/help/define-font-stack/">the documentation</a>.
+ *
+ * @param fontStack value
+ * @return format option
+ */
+ @NonNull
+ public static FormatOption textFont(@NonNull String[] fontStack) {
+ return new FormatOption("text-font", literal(fontStack));
+ }
+ }
+
+ /**
* Converts a JsonArray or a raw expression to a Java expression.
*/
public final static class Converter {
@@ -4251,10 +4447,24 @@ public class Expression {
final String operator = jsonArray.get(0).getAsString();
final List<Expression> arguments = new ArrayList<>();
- JsonElement jsonElement;
for (int i = 1; i < jsonArray.size(); i++) {
- jsonElement = jsonArray.get(i);
- arguments.add(convert(jsonElement));
+ JsonElement jsonElement = jsonArray.get(i);
+ if (operator.equals("literal") && jsonElement instanceof JsonArray) {
+ JsonArray nestedArray = (JsonArray) jsonElement;
+ Object[] array = new Object[nestedArray.size()];
+ for (int j = 0; j < nestedArray.size(); j++) {
+ JsonElement element = nestedArray.get(j);
+ if (element instanceof JsonPrimitive) {
+ array[j] = convertToValue((JsonPrimitive) element);
+ } else {
+ throw new IllegalArgumentException("Nested literal arrays are not supported.");
+ }
+ }
+
+ arguments.add(new ExpressionLiteralArray(array));
+ } else {
+ arguments.add(convert(jsonElement));
+ }
}
return new Expression(operator, arguments.toArray(new Expression[arguments.size()]));
}
@@ -4290,12 +4500,22 @@ public class Expression {
* @return the expression literal
*/
private static Expression convert(@NonNull JsonPrimitive jsonPrimitive) {
+ return new ExpressionLiteral(convertToValue(jsonPrimitive));
+ }
+
+ /**
+ * Converts a JsonPrimitive to value
+ *
+ * @param jsonPrimitive the json primitive to convert
+ * @return the value
+ */
+ private static Object convertToValue(@NonNull JsonPrimitive jsonPrimitive) {
if (jsonPrimitive.isBoolean()) {
- return new Expression.ExpressionLiteral(jsonPrimitive.getAsBoolean());
+ return jsonPrimitive.getAsBoolean();
} else if (jsonPrimitive.isNumber()) {
- return new Expression.ExpressionLiteral(jsonPrimitive.getAsFloat());
+ return jsonPrimitive.getAsFloat();
} else if (jsonPrimitive.isString()) {
- return new Expression.ExpressionLiteral(jsonPrimitive.getAsString());
+ return jsonPrimitive.getAsString();
} else {
throw new RuntimeException("Unsupported literal expression conversion for " + jsonPrimitive.getClass());
}
@@ -4316,20 +4536,15 @@ public class Expression {
/**
* Expression to wrap Object[] as a literal
*/
- private static class ExpressionArray extends Expression {
-
- private Object[] array;
-
- ExpressionArray(Object[] array) {
- this.array = array;
- }
+ private static class ExpressionLiteralArray extends ExpressionLiteral {
- @NonNull
- @Override
- public Object[] toArray() {
- return new Object[] {
- "literal", array
- };
+ /**
+ * Create an expression literal.
+ *
+ * @param object the object to be treated as literal
+ */
+ ExpressionLiteralArray(@NonNull Object[] object) {
+ super(object);
}
/**
@@ -4339,10 +4554,10 @@ public class Expression {
*/
@Override
public String toString() {
- StringBuilder builder = new StringBuilder("[\"literal\"], [");
- Object argument;
+ Object[] array = (Object[]) literal;
+ StringBuilder builder = new StringBuilder("[");
for (int i = 0; i < array.length; i++) {
- argument = array[i];
+ Object argument = array[i];
if (argument instanceof String) {
builder.append("\"").append(argument).append("\"");
} else {
@@ -4353,9 +4568,23 @@ public class Expression {
builder.append(", ");
}
}
- builder.append("]]");
+ builder.append("]");
return builder.toString();
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ExpressionLiteralArray that = (ExpressionLiteralArray) o;
+
+ return Arrays.equals((Object[]) this.literal, (Object[]) that.literal);
+ }
}
/**
@@ -4373,8 +4602,8 @@ public class Expression {
Map<String, Object> unwrappedMap = new HashMap<>();
for (String key : map.keySet()) {
Expression expression = map.get(key);
- if (expression instanceof Expression.ExpressionLiteral) {
- unwrappedMap.put(key, ((ExpressionLiteral) expression).toValue());
+ if (expression instanceof ValueExpression) {
+ unwrappedMap.put(key, ((ValueExpression) expression).toValue());
} else {
unwrappedMap.put(key, expression.toArray());
}
@@ -4437,7 +4666,7 @@ public class Expression {
* @param object the object to convert to an object array
* @return the converted object array
*/
- static Object[] toObjectArray(Object object) {
+ private static Object[] toObjectArray(Object object) {
// object is a primitive array
int len = java.lang.reflect.Array.getLength(object);
Object[] objects = new Object[len];
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyValue.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyValue.java
index 848165f00f..6936c302b2 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyValue.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyValue.java
@@ -62,7 +62,7 @@ public class PropertyValue<T> {
? Expression.Converter.convert((JsonArray) value)
: (Expression) value;
} else {
- Logger.w(TAG, "not a expression, try value");
+ Logger.w(TAG, "not an expression, try PropertyValue#getValue()");
return null;
}
}
@@ -87,7 +87,7 @@ public class PropertyValue<T> {
// noinspection unchecked
return value;
} else {
- Logger.w(TAG, "not a value, try function");
+ Logger.w(TAG, "not a value, try PropertyValue#getExpression()");
return null;
}
}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java
index 1d45f34bd3..7b9128343c 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/SymbolLayer.java
@@ -13,6 +13,8 @@ import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor;
import com.google.gson.JsonArray;
import com.mapbox.mapboxsdk.style.expressions.Expression;
import com.mapbox.mapboxsdk.style.layers.TransitionOptions;
+import com.mapbox.mapboxsdk.style.types.Formatted;
+import com.mapbox.mapboxsdk.style.types.FormattedSection;
/**
* An icon or a text label.
@@ -365,7 +367,31 @@ public class SymbolLayer extends Layer {
@SuppressWarnings("unchecked")
public PropertyValue<String> getTextField() {
checkThread();
- return (PropertyValue<String>) new PropertyValue("text-field", nativeGetTextField());
+
+ PropertyValue propertyValue = new PropertyValue<>("text-field", nativeGetTextField());
+ if (propertyValue.isExpression()) {
+ return (PropertyValue<String>) propertyValue;
+ } else {
+ Formatted formatted = (Formatted) nativeGetTextField();
+ StringBuilder builder = new StringBuilder();
+ for (FormattedSection section : formatted.getFormattedSections()) {
+ builder.append(section.getText());
+ }
+
+ return (PropertyValue<String>) new PropertyValue("text-field", builder.toString());
+ }
+ }
+
+ /**
+ * Get the TextField property as {@link Formatted} object
+ *
+ * @return property wrapper value around String
+ * @see Expression#format(Expression...)
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue<Formatted> getFormattedTextField() {
+ checkThread();
+ return (PropertyValue<Formatted>) new PropertyValue("text-field", nativeGetTextField());
}
/**
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs
index 958cb7383d..961991c7a1 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs
@@ -18,6 +18,10 @@ import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor;
import com.google.gson.JsonArray;
import com.mapbox.mapboxsdk.style.expressions.Expression;
import com.mapbox.mapboxsdk.style.layers.TransitionOptions;
+<% if (type === 'symbol') { -%>
+import com.mapbox.mapboxsdk.style.types.Formatted;
+import com.mapbox.mapboxsdk.style.types.FormattedSection;
+<% } -%>
/**
* <%- doc %>
@@ -171,8 +175,38 @@ public class <%- camelize(type) %>Layer extends Layer {
@SuppressWarnings("unchecked")
public PropertyValue<<%- propertyType(property) %>> get<%- camelize(property.name) %>() {
checkThread();
+<% if (property.name === 'text-field' && property.type === 'formatted') { -%>
+
+ PropertyValue propertyValue = new PropertyValue<>("text-field", nativeGetTextField());
+ if (propertyValue.isExpression()) {
+ return (PropertyValue<String>) propertyValue;
+ } else {
+ Formatted formatted = (Formatted) nativeGetTextField();
+ StringBuilder builder = new StringBuilder();
+ for (FormattedSection section : formatted.getFormattedSections()) {
+ builder.append(section.getText());
+ }
+
+ return (PropertyValue<String>) new PropertyValue("text-field", builder.toString());
+ }
+<% } else { -%>
return (PropertyValue<<%- propertyType(property) %>>) new PropertyValue("<%- property.name %>", nativeGet<%- camelize(property.name) %>());
+<% } -%>
+ }
+<% if (property.name === 'text-field' && property.type === 'formatted') { -%>
+
+ /**
+ * Get the <%- camelize(property.name) %> property as {@link Formatted} object
+ *
+ * @return property wrapper value around <%- propertyType(property) %>
+ * @see Expression#format(Expression...)
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue<Formatted> getFormatted<%- camelize(property.name) %>() {
+ checkThread();
+ return (PropertyValue<Formatted>) new PropertyValue("<%- property.name %>", nativeGet<%- camelize(property.name) %>());
}
+<% } -%>
<% if (property.type == 'color') { -%>
/**
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/Formatted.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/Formatted.java
new file mode 100644
index 0000000000..b11a1b5bc7
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/Formatted.java
@@ -0,0 +1,54 @@
+package com.mapbox.mapboxsdk.style.types;
+
+import android.support.annotation.Keep;
+import android.support.annotation.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * Represents a string broken into sections annotated with separate formatting options.
+ *
+ * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#types-formatted">Style specification</a>
+ */
+@Keep
+public class Formatted {
+ private final FormattedSection[] formattedSections;
+
+ /**
+ * Create a new formatted text.
+ *
+ * @param formattedSections sections with formatting options
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ public Formatted(FormattedSection[] formattedSections) {
+ this.formattedSections = formattedSections;
+ }
+
+ /**
+ * Returns sections with separate formatting options that are a part of this formatted text.
+ *
+ * @return formatted sections
+ */
+ public FormattedSection[] getFormattedSections() {
+ return formattedSections;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Formatted formatted = (Formatted) o;
+
+ return Arrays.equals(formattedSections, formatted.formattedSections);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(formattedSections);
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/FormattedSection.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/FormattedSection.java
new file mode 100644
index 0000000000..b3c36414cc
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/types/FormattedSection.java
@@ -0,0 +1,100 @@
+package com.mapbox.mapboxsdk.style.types;
+
+import android.support.annotation.Keep;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * A component of the {@link Formatted}.
+ */
+@Keep
+public class FormattedSection {
+ private String text;
+ private double fontScale;
+ private String[] fontStack;
+
+ /**
+ * Creates a formatted section.
+ *
+ * @param text displayed string
+ * @param fontScale scale of the font, 1.0 is default
+ * @param fontStack main and fallback fonts that are a part of the style
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ public FormattedSection(@NonNull String text, double fontScale, @Nullable String[] fontStack) {
+ this.text = text;
+ this.fontScale = fontScale;
+ this.fontStack = fontStack;
+ }
+
+ /**
+ * Creates a formatted section.
+ *
+ * @param text displayed string
+ * @param fontScale scale of the font, 1.0 is default
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ public FormattedSection(@NonNull String text, double fontScale) {
+ this.text = text;
+ this.fontScale = fontScale;
+ }
+
+ /**
+ * Returns the displayed text.
+ *
+ * @return text
+ */
+ @NonNull
+ public String getText() {
+ return text;
+ }
+
+ /**
+ * Returns displayed text's font scale.
+ *
+ * @return font scale, defaults to 1.0
+ */
+ public double getFontScale() {
+ return fontScale;
+ }
+
+ /**
+ * Returns the font stack with main and fallback fonts.
+ *
+ * @return font stack
+ */
+ @Nullable
+ public String[] getFontStack() {
+ return fontStack;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ FormattedSection section = (FormattedSection) o;
+
+ return Double.compare(section.fontScale, fontScale) == 0
+ && (text != null ? text.equals(section.text) : section.text == null)
+ && Arrays.equals(fontStack, section.fontStack);
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ long temp;
+ result = text != null ? text.hashCode() : 0;
+ temp = Double.doubleToLongBits(fontScale);
+ result = 31 * result + (int) (temp ^ (temp >>> 32));
+ result = 31 * result + Arrays.hashCode(fontStack);
+ return result;
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/expressions/ExpressionTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/expressions/ExpressionTest.java
index 61105d89b4..3d1bbf5214 100644
--- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/expressions/ExpressionTest.java
+++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/style/expressions/ExpressionTest.java
@@ -3,8 +3,8 @@ package com.mapbox.mapboxsdk.style.expressions;
import android.graphics.Color;
import com.mapbox.mapboxsdk.style.layers.PropertyFactory;
-
import com.mapbox.mapboxsdk.style.layers.PropertyValue;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
@@ -12,7 +12,10 @@ import org.robolectric.RobolectricTestRunner;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
+import java.util.Map;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.fontScale;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.textFont;
import static com.mapbox.mapboxsdk.style.expressions.Expression.abs;
import static com.mapbox.mapboxsdk.style.expressions.Expression.acos;
import static com.mapbox.mapboxsdk.style.expressions.Expression.all;
@@ -35,6 +38,8 @@ import static com.mapbox.mapboxsdk.style.expressions.Expression.e;
import static com.mapbox.mapboxsdk.style.expressions.Expression.eq;
import static com.mapbox.mapboxsdk.style.expressions.Expression.exponential;
import static com.mapbox.mapboxsdk.style.expressions.Expression.floor;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.format;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.formatEntry;
import static com.mapbox.mapboxsdk.style.expressions.Expression.geometryType;
import static com.mapbox.mapboxsdk.style.expressions.Expression.get;
import static com.mapbox.mapboxsdk.style.expressions.Expression.gt;
@@ -1148,7 +1153,7 @@ public class ExpressionTest {
@Test
public void testLiteralArrayString() throws Exception {
Object[] array = new Object[] {1, "text"};
- String expected = "[\"literal\"], [1, \"text\"]]";
+ String expected = "[\"literal\", [1, \"text\"]]";
String actual = literal(array).toString();
assertEquals("literal array should match", expected, actual);
}
@@ -1403,4 +1408,110 @@ public class ExpressionTest {
Object[] actual = isSupportedScript(get("property_name")).toArray();
assertTrue("expression should match", Arrays.deepEquals(expected, actual));
}
+
+ @Test
+ public void testFormatSingleArgument() {
+ Object[] expected = new Object[] {"format", "test",
+ new TestableExpressionHashMap() {
+ {
+ put("font-scale", 1.5f);
+ put("text-font", new Object[] {"literal", new String[] {"awesome"}});
+ }
+ }
+ };
+ Object[] actual = format(
+ formatEntry(literal("test"), fontScale(literal(1.5)), textFont(literal(new String[] {"awesome"})))
+ ).toArray();
+ assertTrue("expression should match", Arrays.deepEquals(expected, actual));
+ }
+
+ @Test
+ public void testFormatMultipleArgument() {
+ Object[] expected = new Object[] {
+ "format",
+ "test",
+ new TestableExpressionHashMap() {
+ {
+ put("text-font", new Object[] {"literal", new String[] {"awesome"}});
+ }
+ },
+ "test2",
+ new TestableExpressionHashMap() {
+ {
+ put("font-scale", 1.5f);
+ }
+ },
+ "test3",
+ new TestableExpressionHashMap() {
+ {
+ }
+ },
+ "test4",
+ new TestableExpressionHashMap() {
+ {
+ put("font-scale", 1.5f);
+ put("text-font", new Object[] {"literal", new String[] {"awesome"}});
+ }
+ }
+ };
+ Object[] actual = format(
+ formatEntry(literal("test"), textFont(new String[] {"awesome"})),
+ formatEntry("test2", fontScale(1.5)),
+ formatEntry(literal("test3")),
+ formatEntry(literal("test4"), fontScale(literal(1.5)), textFont(new String[] {"awesome"}))
+ ).toArray();
+ assertTrue("expression should match", Arrays.deepEquals(expected, actual));
+ }
+
+ /**
+ * This class overrides {@link java.util.AbstractMap#equals(Object)}
+ * in order to correctly compare nodes values if they are arrays,
+ * which is the case for {@link Expression#format(Expression.FormatEntry...)}'s "text-format" argument.
+ */
+ private class TestableExpressionHashMap extends HashMap<String, Object> {
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (!(o instanceof Map)) {
+ return false;
+ }
+ Map<?, ?> m = (Map<?, ?>) o;
+ if (m.size() != size()) {
+ return false;
+ }
+
+ try {
+ for (Entry<String, Object> e : entrySet()) {
+ String key = e.getKey();
+ Object value = e.getValue();
+ if (value == null) {
+ if (!(m.get(key) == null && m.containsKey(key))) {
+ return false;
+ }
+ } else {
+ if (value instanceof Object[]) {
+ // Use Arrays.deepEquals() if values are Object arrays.
+ if (!Arrays.deepEquals((Object[]) value, (Object[]) m.get(key))) {
+ return false;
+ }
+ } else {
+ if (!value.equals(m.get(key))) {
+ return false;
+ }
+ }
+ }
+ }
+ } catch (ClassCastException unused) {
+ return false;
+ } catch (NullPointerException unused) {
+ return false;
+ }
+
+ return true;
+ }
+ }
} \ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/CircleLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/CircleLayerTest.java
index 1e03a640d1..f6966664f3 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/CircleLayerTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/CircleLayerTest.java
@@ -161,7 +161,6 @@ public class CircleLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testCircleColorTransition() {
validateTestSetup();
@@ -206,7 +205,6 @@ public class CircleLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testCircleColorAsIntConstant() {
validateTestSetup();
@@ -265,7 +263,6 @@ public class CircleLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testCircleOpacityTransition() {
validateTestSetup();
@@ -310,7 +307,6 @@ public class CircleLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testCircleTranslateTransition() {
validateTestSetup();
@@ -426,7 +422,6 @@ public class CircleLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testCircleStrokeColorTransition() {
validateTestSetup();
@@ -471,7 +466,6 @@ public class CircleLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testCircleStrokeColorAsIntConstant() {
validateTestSetup();
@@ -529,5 +523,4 @@ public class CircleLayerTest extends BaseActivityTest {
assertEquals(layer.getCircleStrokeOpacity().getExpression(), expression);
});
}
-
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/ExpressionTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/ExpressionTest.java
index 4e284cdc14..773baf17eb 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/ExpressionTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/ExpressionTest.java
@@ -1,16 +1,22 @@
package com.mapbox.mapboxsdk.testapp.style;
import android.graphics.Color;
+import android.support.test.espresso.UiController;
import android.support.test.runner.AndroidJUnit4;
+import com.mapbox.geojson.Feature;
import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.geometry.LatLng;
+import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.style.expressions.Expression;
import com.mapbox.mapboxsdk.style.layers.CircleLayer;
import com.mapbox.mapboxsdk.style.layers.FillLayer;
import com.mapbox.mapboxsdk.style.layers.Layer;
+import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.mapbox.mapboxsdk.style.sources.Source;
+import com.mapbox.mapboxsdk.style.types.Formatted;
+import com.mapbox.mapboxsdk.style.types.FormattedSection;
import com.mapbox.mapboxsdk.testapp.R;
import com.mapbox.mapboxsdk.testapp.activity.BaseActivityTest;
import com.mapbox.mapboxsdk.testapp.activity.espresso.EspressoTestActivity;
@@ -24,13 +30,18 @@ import java.io.IOException;
import timber.log.Timber;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.fontScale;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.textFont;
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;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.format;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.formatEntry;
import static com.mapbox.mapboxsdk.style.expressions.Expression.get;
import static com.mapbox.mapboxsdk.style.expressions.Expression.interpolate;
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.rgb;
import static com.mapbox.mapboxsdk.style.expressions.Expression.rgba;
import static com.mapbox.mapboxsdk.style.expressions.Expression.step;
@@ -43,9 +54,11 @@ import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleColor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillAntialias;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillColor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.fillOutlineColor;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textField;
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.assertNull;
@RunWith(AndroidJUnit4.class)
public class ExpressionTest extends BaseActivityTest {
@@ -220,7 +233,6 @@ public class ExpressionTest extends BaseActivityTest {
@Test
public void testCollatorExpression() {
validateTestSetup();
- setupStyle();
invoke(mapboxMap, (uiController, mapboxMap) -> {
LatLng latLng = new LatLng(51, 17);
@@ -265,6 +277,254 @@ public class ExpressionTest extends BaseActivityTest {
});
}
+ @Test
+ public void testConstFormatExpressionNoParams() {
+ validateTestSetup();
+ invoke(mapboxMap, (uiController, mapboxMap) -> {
+ LatLng latLng = new LatLng(51, 17);
+ mapboxMap.addSource(new GeoJsonSource("source", Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())));
+ SymbolLayer layer = new SymbolLayer("layer", "source");
+ mapboxMap.addLayer(layer);
+
+ Expression expression = format(
+ formatEntry("test")
+ );
+ layer.setProperties(textField(expression));
+ waitForLayer(uiController, mapboxMap, latLng);
+ assertFalse(mapboxMap.queryRenderedFeatures(mapboxMap.getProjection().toScreenLocation(latLng), "layer")
+ .isEmpty());
+
+ assertNull(layer.getTextField().getExpression());
+ assertEquals("test", layer.getTextField().getValue());
+
+ Formatted expected = new Formatted(new FormattedSection[] {
+ new FormattedSection("test", 1.0f)
+ });
+ assertNull(layer.getFormattedTextField().getExpression());
+ assertEquals(expected, layer.getFormattedTextField().getValue());
+ });
+ }
+
+ @Test
+ public void testConstFormatExpressionFontScaleParam() {
+ validateTestSetup();
+ invoke(mapboxMap, (uiController, mapboxMap) -> {
+ LatLng latLng = new LatLng(51, 17);
+ mapboxMap.addSource(new GeoJsonSource("source", Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())));
+ SymbolLayer layer = new SymbolLayer("layer", "source");
+ mapboxMap.addLayer(layer);
+
+ Expression expression = format(
+ formatEntry("test", fontScale(1.75))
+ );
+ layer.setProperties(textField(expression));
+ waitForLayer(uiController, mapboxMap, latLng);
+ assertFalse(mapboxMap.queryRenderedFeatures(mapboxMap.getProjection().toScreenLocation(latLng), "layer")
+ .isEmpty());
+
+ assertNull(layer.getTextField().getExpression());
+ assertEquals("test", layer.getTextField().getValue());
+
+ Formatted expected = new Formatted(new FormattedSection[] {
+ new FormattedSection("test", 1.75f)
+ });
+ assertNull(layer.getFormattedTextField().getExpression());
+ assertEquals(expected, layer.getFormattedTextField().getValue());
+ });
+ }
+
+ @Test
+ public void testConstFormatExpressionTextFontParam() {
+ validateTestSetup();
+ invoke(mapboxMap, (uiController, mapboxMap) -> {
+ LatLng latLng = new LatLng(51, 17);
+ mapboxMap.addSource(new GeoJsonSource("source", Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())));
+ SymbolLayer layer = new SymbolLayer("layer", "source");
+ mapboxMap.addLayer(layer);
+
+ Expression expression = format(
+ formatEntry(
+ literal("test"), fontScale(1.0), textFont(new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"})
+ )
+ );
+ layer.setProperties(textField(expression));
+ waitForLayer(uiController, mapboxMap, latLng);
+ assertFalse(mapboxMap.queryRenderedFeatures(mapboxMap.getProjection().toScreenLocation(latLng), "layer")
+ .isEmpty());
+
+ assertNull(layer.getTextField().getExpression());
+ assertEquals("test", layer.getTextField().getValue());
+
+ Formatted expected = new Formatted(new FormattedSection[] {
+ new FormattedSection("test", 1.0f,
+ new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"})
+ });
+ assertNull(layer.getFormattedTextField().getExpression());
+ assertEquals(expected, layer.getFormattedTextField().getValue());
+ });
+ }
+
+ @Test
+ public void testConstFormatExpressionAllParams() {
+ validateTestSetup();
+ invoke(mapboxMap, (uiController, mapboxMap) -> {
+ LatLng latLng = new LatLng(51, 17);
+ mapboxMap.addSource(new GeoJsonSource("source", Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())));
+ SymbolLayer layer = new SymbolLayer("layer", "source");
+ mapboxMap.addLayer(layer);
+
+ Expression expression = format(
+ formatEntry(
+ "test", fontScale(0.5), textFont(new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"})
+ )
+ );
+ layer.setProperties(textField(expression));
+ waitForLayer(uiController, mapboxMap, latLng);
+ assertFalse(mapboxMap.queryRenderedFeatures(mapboxMap.getProjection().toScreenLocation(latLng), "layer")
+ .isEmpty());
+
+ assertNull(layer.getTextField().getExpression());
+ assertEquals("test", layer.getTextField().getValue());
+
+ Formatted expected = new Formatted(new FormattedSection[] {
+ new FormattedSection("test", 0.5f, new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"})});
+ assertNull(layer.getFormattedTextField().getExpression());
+ assertEquals(expected, layer.getFormattedTextField().getValue());
+ });
+ }
+
+ @Test
+ public void testConstFormatExpressionMultipleInputs() {
+ validateTestSetup();
+ invoke(mapboxMap, (uiController, mapboxMap) -> {
+ LatLng latLng = new LatLng(51, 17);
+ mapboxMap.addSource(new GeoJsonSource("source", Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())));
+ SymbolLayer layer = new SymbolLayer("layer", "source");
+ mapboxMap.addLayer(layer);
+
+ Expression expression = format(
+ formatEntry(
+ "test", fontScale(1.5), textFont(new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"})
+ ),
+ formatEntry("\ntest2", fontScale(2))
+ );
+ layer.setProperties(textField(expression));
+ waitForLayer(uiController, mapboxMap, latLng);
+ assertFalse(mapboxMap.queryRenderedFeatures(mapboxMap.getProjection().toScreenLocation(latLng), "layer")
+ .isEmpty());
+
+ assertNull(layer.getTextField().getExpression());
+ assertEquals("test\ntest2", layer.getTextField().getValue());
+
+ Formatted expected = new Formatted(new FormattedSection[] {
+ new FormattedSection("test", 1.5f, new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"}),
+ new FormattedSection("\ntest2", 2f),
+ });
+ assertNull(layer.getFormattedTextField().getExpression());
+ assertEquals(expected, layer.getFormattedTextField().getValue());
+ });
+ }
+
+ @Test
+ public void testVariableFormatExpression() {
+ validateTestSetup();
+ invoke(mapboxMap, (uiController, mapboxMap) -> {
+ LatLng latLng = new LatLng(51, 17);
+ Feature feature = Feature.fromGeometry(Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude()));
+ feature.addStringProperty("test_property", "test");
+ feature.addNumberProperty("test_property_number", 1.5);
+ mapboxMap.addSource(new GeoJsonSource("source", feature));
+ SymbolLayer layer = new SymbolLayer("layer", "source");
+ mapboxMap.addLayer(layer);
+
+ Expression expression = format(
+ formatEntry(
+ get("test_property"),
+ fontScale(number(get("test_property_number"))),
+ textFont(new String[] {"Arial Unicode MS Regular", "DIN Offc Pro Regular"})
+ )
+ );
+ layer.setProperties(textField(expression));
+ waitForLayer(uiController, mapboxMap, latLng);
+ assertFalse(mapboxMap.queryRenderedFeatures(mapboxMap.getProjection().toScreenLocation(latLng), "layer")
+ .isEmpty());
+
+ assertEquals(expression, layer.getTextField().getExpression());
+ assertNull(layer.getTextField().getValue());
+
+ assertEquals(expression, layer.getFormattedTextField().getExpression());
+ assertNull(layer.getFormattedTextField().getValue());
+ });
+ }
+
+ @Test
+ public void testVariableFormatExpressionMultipleInputs() {
+ validateTestSetup();
+ invoke(mapboxMap, (uiController, mapboxMap) -> {
+ LatLng latLng = new LatLng(51, 17);
+ Feature feature = Feature.fromGeometry(Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude()));
+ feature.addStringProperty("test_property", "test");
+ feature.addNumberProperty("test_property_number", 1.5);
+ mapboxMap.addSource(new GeoJsonSource("source", feature));
+ SymbolLayer layer = new SymbolLayer("layer", "source");
+ mapboxMap.addLayer(layer);
+
+ Expression expression = format(
+ formatEntry(
+ get("test_property"),
+ fontScale(1.25),
+ textFont(new String[] {"Arial Unicode MS Regular", "DIN Offc Pro Regular"})
+ ),
+ formatEntry("\ntest2", fontScale(2))
+ );
+ layer.setProperties(textField(expression));
+ waitForLayer(uiController, mapboxMap, latLng);
+ assertFalse(mapboxMap.queryRenderedFeatures(mapboxMap.getProjection().toScreenLocation(latLng), "layer")
+ .isEmpty());
+
+ assertEquals(expression, layer.getTextField().getExpression());
+ assertNull(layer.getTextField().getValue());
+
+ assertEquals(expression, layer.getFormattedTextField().getExpression());
+ assertNull(layer.getFormattedTextField().getValue());
+ });
+ }
+
+ @Test
+ public void testFormatExpressionPlainTextCoercion() {
+ validateTestSetup();
+ invoke(mapboxMap, (uiController, mapboxMap) -> {
+ LatLng latLng = new LatLng(51, 17);
+ mapboxMap.addSource(new GeoJsonSource("source", Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())));
+ SymbolLayer layer = new SymbolLayer("layer", "source");
+ mapboxMap.addLayer(layer);
+
+ layer.setProperties(textField("test"));
+ waitForLayer(uiController, mapboxMap, latLng);
+ assertFalse(mapboxMap.queryRenderedFeatures(mapboxMap.getProjection().toScreenLocation(latLng), "layer")
+ .isEmpty());
+
+ assertNull(layer.getTextField().getExpression());
+ assertEquals("test", layer.getTextField().getValue());
+
+ assertNull(layer.getFormattedTextField().getExpression());
+ Formatted expected = new Formatted(new FormattedSection[] {new FormattedSection("test", 1.0f)});
+ assertEquals(expected, layer.getFormattedTextField().getValue());
+ });
+ }
+
+ private static final long WAIT_TIMEOUT = 5000;
+ private static final long WAIT_DELAY = 150;
+
+ private static void waitForLayer(UiController uiController, MapboxMap mapboxMap, LatLng latLng) {
+ int i = 0;
+ while (mapboxMap.queryRenderedFeatures(mapboxMap.getProjection().toScreenLocation(latLng), "layer").isEmpty()) {
+ i++;
+ assertFalse("Waiting for layer timed out", i * WAIT_DELAY > WAIT_TIMEOUT);
+ uiController.loopMainThreadForAtLeast(WAIT_DELAY);
+ }
+ }
+
private void setupStyle() {
invoke(mapboxMap, (uiController, mapboxMap) -> {
// Add a source
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillExtrusionLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillExtrusionLayerTest.java
index bbfc05229c..7e982786e0 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillExtrusionLayerTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillExtrusionLayerTest.java
@@ -190,7 +190,6 @@ public class FillExtrusionLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testFillExtrusionColorAsIntConstant() {
validateTestSetup();
@@ -292,7 +291,6 @@ public class FillExtrusionLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testFillExtrusionHeightTransition() {
validateTestSetup();
@@ -337,7 +335,6 @@ public class FillExtrusionLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testFillExtrusionBaseTransition() {
validateTestSetup();
@@ -381,5 +378,4 @@ public class FillExtrusionLayerTest extends BaseActivityTest {
assertEquals(layer.getFillExtrusionBase().getExpression(), expression);
});
}
-
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillLayerTest.java
index 66e943b64f..79140257c8 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillLayerTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/FillLayerTest.java
@@ -175,7 +175,6 @@ public class FillLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testFillColorTransition() {
validateTestSetup();
@@ -220,7 +219,6 @@ public class FillLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testFillColorAsIntConstant() {
validateTestSetup();
@@ -279,7 +277,6 @@ public class FillLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testFillOutlineColorAsIntConstant() {
validateTestSetup();
@@ -380,5 +377,4 @@ public class FillLayerTest extends BaseActivityTest {
assertEquals(layer.getFillPattern().getExpression(), expression);
});
}
-
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java
index b832c4eae7..40ca0d0b56 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java
@@ -161,7 +161,6 @@ public class HeatmapLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testHeatmapWeightAsConstant() {
validateTestSetup();
@@ -191,7 +190,6 @@ public class HeatmapLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testHeatmapIntensityTransition() {
validateTestSetup();
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/LineLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/LineLayerTest.java
index 29ba0fa16b..9d1978f513 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/LineLayerTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/LineLayerTest.java
@@ -160,7 +160,6 @@ public class LineLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testLineMiterLimitAsConstant() {
validateTestSetup();
@@ -233,7 +232,6 @@ public class LineLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testLineColorTransition() {
validateTestSetup();
@@ -278,7 +276,6 @@ public class LineLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testLineColorAsIntConstant() {
validateTestSetup();
@@ -380,7 +377,6 @@ public class LineLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testLineGapWidthTransition() {
validateTestSetup();
@@ -425,7 +421,6 @@ public class LineLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testLineOffsetTransition() {
validateTestSetup();
@@ -499,7 +494,6 @@ public class LineLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testLineDasharrayTransition() {
validateTestSetup();
@@ -572,5 +566,4 @@ public class LineLayerTest extends BaseActivityTest {
assertEquals(layer.getLinePattern().getExpression(), expression);
});
}
-
}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java
index d61397033c..bdc09801b2 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java
@@ -9,6 +9,8 @@ import timber.log.Timber;
import com.mapbox.mapboxsdk.style.expressions.Expression;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
+import com.mapbox.mapboxsdk.style.types.Formatted;
+import com.mapbox.mapboxsdk.style.types.FormattedSection;
import com.mapbox.mapboxsdk.testapp.activity.BaseActivityTest;
import org.junit.Test;
@@ -258,7 +260,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testIconTextFitAsConstant() {
validateTestSetup();
@@ -319,7 +320,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testIconRotateAsConstant() {
validateTestSetup();
@@ -349,7 +349,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testIconPaddingAsConstant() {
validateTestSetup();
@@ -421,7 +420,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testIconPitchAlignmentAsConstant() {
validateTestSetup();
@@ -477,8 +475,28 @@ public class SymbolLayerTest extends BaseActivityTest {
assertEquals((String) layer.getTextField().getValue(), (String) "");
layer.setProperties(textField("{token}"));
- assertEquals(layer.getTextField().getExpression(),
- Expression.format(Expression.toString(Expression.get("token"))));
+ assertEquals(layer.getTextField().getExpression(), Expression.format(Expression.formatEntry(Expression.toString(Expression.get("token")))));
+ });
+ }
+
+ @Test
+ public void testFormattedTextFieldAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("text-field-formatted");
+ invoke(mapboxMap, (uiController, mapboxMap) -> {
+ assertNotNull(layer);
+
+ Formatted expected = new Formatted(new FormattedSection[] {
+ new FormattedSection("", 1.0)
+ });
+
+ // Set and Get
+ layer.setProperties(textField(""));
+ assertEquals(layer.getFormattedTextField().getValue(), expected/*(String) ""*/);
+
+ layer.setProperties(textField("{token}"));
+ assertEquals(layer.getFormattedTextField().getExpression(), Expression.format(Expression.formatEntry(Expression.toString(Expression.get("token")))));
});
}
@@ -491,12 +509,26 @@ public class SymbolLayerTest extends BaseActivityTest {
assertNotNull(layer);
// Set and Get
- Expression expression = Expression.format(string(Expression.get("undefined")));
+ Expression expression = string(Expression.get("undefined"));
layer.setProperties(textField(expression));
- assertEquals(layer.getTextField().getExpression(), expression);
+ assertEquals(layer.getTextField().getExpression(), Expression.format(Expression.formatEntry(expression)));
});
}
+ @Test
+ public void testFormattedTextFieldAsExpression() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("text-field-formatted-expression");
+ invoke(mapboxMap, (uiController, mapboxMap) -> {
+ assertNotNull(layer);
+
+ // Set and Get
+ Expression expression = string(Expression.get("undefined"));
+ layer.setProperties(textField(expression));
+ assertEquals(layer.getFormattedTextField().getExpression(), Expression.format(Expression.formatEntry(Expression.string(Expression.get("undefined")))));
+ });
+ }
@Test
public void testTextFontAsConstant() {
@@ -541,7 +573,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testTextMaxWidthAsConstant() {
validateTestSetup();
@@ -571,7 +602,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testTextLineHeightAsConstant() {
validateTestSetup();
@@ -615,7 +645,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testTextJustifyAsConstant() {
validateTestSetup();
@@ -645,7 +674,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testTextAnchorAsConstant() {
validateTestSetup();
@@ -675,7 +703,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testTextMaxAngleAsConstant() {
validateTestSetup();
@@ -719,7 +746,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testTextPaddingAsConstant() {
validateTestSetup();
@@ -777,7 +803,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testTextOffsetAsConstant() {
validateTestSetup();
@@ -878,7 +903,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testIconColorTransition() {
validateTestSetup();
@@ -923,7 +947,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testIconColorAsIntConstant() {
validateTestSetup();
@@ -982,7 +1005,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testIconHaloColorAsIntConstant() {
validateTestSetup();
@@ -1041,7 +1063,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testIconHaloBlurTransition() {
validateTestSetup();
@@ -1086,7 +1107,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testIconTranslateTransition() {
validateTestSetup();
@@ -1174,7 +1194,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testTextColorTransition() {
validateTestSetup();
@@ -1219,7 +1238,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testTextColorAsIntConstant() {
validateTestSetup();
@@ -1278,7 +1296,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testTextHaloColorAsIntConstant() {
validateTestSetup();
@@ -1337,7 +1354,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testTextHaloBlurTransition() {
validateTestSetup();
@@ -1382,7 +1398,6 @@ public class SymbolLayerTest extends BaseActivityTest {
});
}
-
@Test
public void testTextTranslateTransition() {
validateTestSetup();
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/layer.junit.ejs b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/layer.junit.ejs
index ee2bd2477c..c9eded601f 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/layer.junit.ejs
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/layer.junit.ejs
@@ -13,6 +13,10 @@ import timber.log.Timber;
import com.mapbox.mapboxsdk.style.expressions.Expression;
import com.mapbox.mapboxsdk.style.layers.<%- camelize(type) %>Layer;
+<% if (type === 'symbol') { -%>
+import com.mapbox.mapboxsdk.style.types.Formatted;
+import com.mapbox.mapboxsdk.style.types.FormattedSection;
+<% } -%>
import com.mapbox.mapboxsdk.testapp.activity.BaseActivityTest;
import org.junit.Test;
@@ -166,14 +170,38 @@ public class <%- camelize(type) %>LayerTest extends BaseActivityTest {
layer.setProperties(<%- camelizeWithLeadingLowercase(property.name) %>("{token}"));
<% if (property.type === 'formatted') { -%>
- assertEquals(layer.get<%- camelize(property.name) %>().getExpression(),
- Expression.format(Expression.toString(Expression.get("token"))));
-<% } else { -%>
+ assertEquals(layer.getTextField().getExpression(), Expression.format(Expression.formatEntry(Expression.toString(Expression.get("token")))));
+<% } else {-%>
assertEquals(layer.get<%- camelize(property.name) %>().getExpression(), Expression.toString(Expression.get("token")));
<% } -%>
<% } -%>
});
}
+<% if (property.name === 'text-field' && property.type === 'formatted') { -%>
+
+ @Test
+ public void testFormatted<%- camelize(property.name) %>AsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("<%- property.name %>-formatted");
+ invoke(mapboxMap, (uiController, mapboxMap) -> {
+ assertNotNull(layer);
+
+ Formatted expected = new Formatted(new FormattedSection[] {
+ new FormattedSection("", 1.0)
+ });
+
+ // Set and Get
+ layer.setProperties(<%- camelizeWithLeadingLowercase(property.name) %>(<%- defaultValueJava(property) %>));
+ assertEquals(layer.getFormatted<%- camelize(property.name) %>().getValue(), expected/*(<%- propertyType(property) %>) <%- defaultValueJava(property) %>*/);
+<% if (property.tokens) { -%>
+
+ layer.setProperties(<%- camelizeWithLeadingLowercase(property.name) %>("{token}"));
+ assertEquals(layer.getFormattedTextField().getExpression(), Expression.format(Expression.formatEntry(Expression.toString(Expression.get("token")))));
+<% } -%>
+ });
+ }
+<% } -%>
<% if (property['property-type'] === 'data-driven' || property['property-type'] === 'cross-faded-data-driven') { -%>
<% if (!(property.name.endsWith("-font")||property.name.endsWith("-offset"))) { -%>
@@ -186,16 +214,32 @@ public class <%- camelize(type) %>LayerTest extends BaseActivityTest {
assertNotNull(layer);
// Set and Get
-<% if (property.type === 'formatted') { -%>
- Expression expression = Expression.format(<%- defaultExpressionJava(property) %>(Expression.get("undefined")));
-<% } else { -%>
Expression expression = <%- defaultExpressionJava(property) %>(Expression.get("undefined"));
-<% } -%>
layer.setProperties(<%- camelizeWithLeadingLowercase(property.name) %>(expression));
+<% if (property.type === 'formatted') { -%>
+ assertEquals(layer.getTextField().getExpression(), Expression.format(Expression.formatEntry(expression)));
+<% } else { -%>
assertEquals(layer.get<%- camelize(property.name) %>().getExpression(), expression);
+<% } -%>
});
}
+<% if (property.name === 'text-field' && property.type === 'formatted') { -%>
+
+ @Test
+ public void testFormatted<%- camelize(property.name) %>AsExpression() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("<%- property.name %>-formatted-expression");
+ invoke(mapboxMap, (uiController, mapboxMap) -> {
+ assertNotNull(layer);
+ // Set and Get
+ Expression expression = <%- defaultExpressionJava(property) %>(Expression.get("undefined"));
+ layer.setProperties(<%- camelizeWithLeadingLowercase(property.name) %>(expression));
+ assertEquals(layer.getFormattedTextField().getExpression(), Expression.format(Expression.formatEntry(Expression.string(Expression.get("undefined")))));
+ });
+ }
+<% } -%>
<% } -%>
<% } -%>
<% if (property.type == 'color') { -%>
diff --git a/platform/android/core-files.txt b/platform/android/core-files.txt
index 2e32355dcf..5736276364 100644
--- a/platform/android/core-files.txt
+++ b/platform/android/core-files.txt
@@ -125,6 +125,10 @@ platform/android/src/style/position.cpp
platform/android/src/style/position.hpp
platform/android/src/style/light.cpp
platform/android/src/style/light.hpp
+platform/android/src/style/formatted.cpp
+platform/android/src/style/formatted.hpp
+platform/android/src/style/formatted_section.cpp
+platform/android/src/style/formatted_section.hpp
# Native map
platform/android/src/native_map_view.cpp
diff --git a/platform/android/src/conversion/constant.cpp b/platform/android/src/conversion/constant.cpp
index a609dc7c60..0671fa6a32 100644
--- a/platform/android/src/conversion/constant.cpp
+++ b/platform/android/src/conversion/constant.cpp
@@ -1,5 +1,6 @@
#include "constant.hpp"
#include "collection.hpp"
+#include "../style/formatted.hpp"
#include <mbgl/util/string.hpp>
@@ -39,7 +40,7 @@ Result<jni::Local<jni::Object<>>> Converter<jni::Local<jni::Object<>>, Color>::o
}
Result<jni::Local<jni::Object<>>> Converter<jni::Local<jni::Object<>>, style::expression::Formatted>::operator()(jni::JNIEnv& env, const style::expression::Formatted& value) const {
- return jni::Make<jni::String>(env, value.toString());
+ return Formatted::New(env, value);
}
Result<jni::Local<jni::Object<>>> Converter<jni::Local<jni::Object<>>, std::vector<std::string>>::operator()(jni::JNIEnv& env, const std::vector<std::string>& value) const {
diff --git a/platform/android/src/jni.cpp b/platform/android/src/jni.cpp
index e323dc83b3..bb28bf6ea4 100755
--- a/platform/android/src/jni.cpp
+++ b/platform/android/src/jni.cpp
@@ -1,6 +1,8 @@
#include "jni.hpp"
#include <mbgl/util/logging.hpp>
+#include <src/style/formatted.hpp>
+#include <src/style/formatted_section.hpp>
#include "annotation/marker.hpp"
#include "annotation/polygon.hpp"
@@ -162,6 +164,8 @@ void registerNatives(JavaVM *vm) {
Source::registerNative(env);
Light::registerNative(env);
Position::registerNative(env);
+ Formatted::registerNative(env);
+ FormattedSection::registerNative(env);
// Map
CameraPosition::registerNative(env);
diff --git a/platform/android/src/style/formatted.cpp b/platform/android/src/style/formatted.cpp
new file mode 100644
index 0000000000..ecbc7be1c8
--- /dev/null
+++ b/platform/android/src/style/formatted.cpp
@@ -0,0 +1,43 @@
+#include "formatted.hpp"
+#include "formatted_section.hpp"
+
+namespace mbgl {
+namespace android {
+
+void Formatted::registerNative(jni::JNIEnv& env) {
+ jni::Class<Formatted>::Singleton(env);
+}
+
+jni::Local<jni::Object<Formatted>> Formatted::New(jni::JNIEnv& env, const style::expression::Formatted& value) {
+ static auto& formatted = jni::Class<Formatted>::Singleton(env);
+ static auto formattedConstructor = formatted.GetConstructor<jni::Array<jni::Object<FormattedSection>>>(env);
+ static auto& formattedSection = jni::Class<FormattedSection>::Singleton(env);
+
+ auto sections = jni::Array<jni::Object<FormattedSection>>::New(env, value.sections.size());
+ for (std::size_t i = 0; i < value.sections.size(); i++) {
+ auto section = value.sections.at(i);
+ auto text = jni::Make<jni::String>(env, section.text);
+
+ double fontScale = 1.0;
+ if (section.fontScale) {
+ fontScale = section.fontScale.value();
+ }
+
+ if (section.fontStack) {
+ auto fontStack = jni::Array<jni::String>::New(env, section.fontStack.value().size());
+ for (std::size_t j = 0; j < section.fontStack.value().size(); j++) {
+ fontStack.Set(env, j, jni::Make<jni::String>(env, section.fontStack.value().at(j)));
+ }
+ static auto formattedSectionConstructor = formattedSection.GetConstructor<jni::String, double, jni::Array<jni::String>>(env);
+ sections.Set(env, i, formattedSection.New(env, formattedSectionConstructor, text, fontScale, fontStack));
+ } else {
+ static auto formattedSectionConstructor = formattedSection.GetConstructor<jni::String, double>(env);
+ sections.Set(env, i, formattedSection.New(env, formattedSectionConstructor, text, fontScale));
+ }
+ }
+
+ return formatted.New(env, formattedConstructor, sections);
+}
+
+} // namespace android
+} // namespace mbgl \ No newline at end of file
diff --git a/platform/android/src/style/formatted.hpp b/platform/android/src/style/formatted.hpp
new file mode 100644
index 0000000000..ca91f6562c
--- /dev/null
+++ b/platform/android/src/style/formatted.hpp
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <mbgl/util/noncopyable.hpp>
+
+#include <jni/jni.hpp>
+#include <mbgl/style/expression/formatted.hpp>
+
+namespace mbgl {
+ namespace android {
+
+ using SuperTag = jni::ObjectTag;
+ class Formatted : private mbgl::util::noncopyable {
+ public:
+ static constexpr auto Name() { return "com/mapbox/mapboxsdk/style/types/Formatted"; };
+
+ static jni::Local<jni::Object<Formatted>>
+ New(jni::JNIEnv&, const style::expression::Formatted &value);
+
+ static void registerNative(jni::JNIEnv &);
+ };
+
+ } // namespace android
+} // namespace mbgl \ No newline at end of file
diff --git a/platform/android/src/style/formatted_section.cpp b/platform/android/src/style/formatted_section.cpp
new file mode 100644
index 0000000000..ddcf76f816
--- /dev/null
+++ b/platform/android/src/style/formatted_section.cpp
@@ -0,0 +1,11 @@
+#include "formatted_section.hpp"
+
+namespace mbgl {
+namespace android {
+
+void FormattedSection::registerNative(jni::JNIEnv& env) {
+ jni::Class<FormattedSection>::Singleton(env);
+}
+
+} // namespace android
+} // namespace mbgl \ No newline at end of file
diff --git a/platform/android/src/style/formatted_section.hpp b/platform/android/src/style/formatted_section.hpp
new file mode 100644
index 0000000000..f759a94e8e
--- /dev/null
+++ b/platform/android/src/style/formatted_section.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <mbgl/util/noncopyable.hpp>
+
+#include <jni/jni.hpp>
+
+namespace mbgl {
+namespace android {
+
+class FormattedSection : private mbgl::util::noncopyable {
+public:
+ static constexpr auto Name() { return "com/mapbox/mapboxsdk/style/types/FormattedSection"; };
+
+ static void registerNative(jni::JNIEnv&);
+};
+
+} // namespace android
+} // namespace mbgl \ No newline at end of file
diff --git a/platform/android/src/style/position.cpp b/platform/android/src/style/position.cpp
index 20a0c47dff..326310be07 100644
--- a/platform/android/src/style/position.cpp
+++ b/platform/android/src/style/position.cpp
@@ -31,5 +31,5 @@ float Position::getPolarAngle(jni::JNIEnv& env, const jni::Object<Position>& pos
return position.Get(env, field);
}
-} // namespace andr[oid
+} // namespace android
} // namespace mbgl \ No newline at end of file