diff options
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 |
commit | 12b496945498358b7afb4af1efe4ae0c52b9c7d3 (patch) | |
tree | c4d4c31cbccfb39bcc8a4fd6a2a09f627173a56b | |
parent | 78a1c30e80c70f36ffec8193753919277418a3ca (diff) | |
download | qtlocation-mapboxgl-12b496945498358b7afb4af1efe4ae0c52b9c7d3.tar.gz |
[android] "format" expression support
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 |