diff options
8 files changed, 376 insertions, 123 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Style.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Style.java index d6bb0a9f01..f14e034816 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Style.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Style.java @@ -326,7 +326,7 @@ public class Style { */ public void addImage(@NonNull final String name, @NonNull final Bitmap bitmap, boolean sdf) { validateState("addImage"); - new BitmapImageConversionTask(nativeMap, sdf).execute(new Builder.ImageWrapper(name, bitmap, sdf)); + new BitmapImageConversionTask(nativeMap).execute(new Builder.ImageWrapper(name, bitmap, sdf)); } /** @@ -346,7 +346,7 @@ public class Style { */ public void addImages(@NonNull HashMap<String, Bitmap> images, boolean sdf) { validateState("addImages"); - new BitmapImageConversionTask(nativeMap, sdf).execute(Builder.ImageWrapper.convertToImageArray(images, sdf)); + new BitmapImageConversionTask(nativeMap).execute(Builder.ImageWrapper.convertToImageArray(images, sdf)); } /** @@ -905,11 +905,9 @@ public class Style { private static class BitmapImageConversionTask extends AsyncTask<Builder.ImageWrapper, Void, List<Image>> { private WeakReference<NativeMap> nativeMap; - private boolean sdf; - BitmapImageConversionTask(NativeMap nativeMap, boolean sdf) { + BitmapImageConversionTask(NativeMap nativeMap) { this.nativeMap = new WeakReference<>(nativeMap); - this.sdf = sdf; } @NonNull @@ -919,10 +917,12 @@ public class Style { ByteBuffer buffer; String name; Bitmap bitmap; + boolean sdf; for (Builder.ImageWrapper param : params) { name = param.id; bitmap = param.bitmap; + sdf = param.sdf; if (bitmap.getConfig() != Bitmap.Config.ARGB_8888) { bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false); 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 25ff819470..c3451845dd 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 @@ -2843,7 +2843,8 @@ public class Expression { * CircleLayer circleLayer = new CircleLayer("layer-id", "source-id"); * circleLayer.setProperties( * circleColor(switchCase( - * eq(literal("it"), resolvedLocale(collator(true, true, Locale.ITALY))), literal(ColorUtils.colorToRgbaString(Color.GREEN)), + * eq(literal("it"), resolvedLocale(collator(true, true, Locale.ITALY))), literal(ColorUtils.colorToRgbaString + * (Color.GREEN)), * literal(ColorUtils.colorToRgbaString(Color.RED)))) * ); * } @@ -3626,7 +3627,8 @@ public class Expression { * @return expression * @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-step">Style specification</a> */ - public static Expression step(@NonNull Expression input, @NonNull Expression defaultOutput, @NonNull Expression... stops) { + public static Expression step(@NonNull Expression input, @NonNull Expression defaultOutput, + @NonNull Expression... stops) { return new Expression("step", join(new Expression[] {input, defaultOutput}, stops)); } @@ -4411,7 +4413,7 @@ public class 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. + * "text-font" is required to be 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>. @@ -4427,7 +4429,7 @@ public class 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. + * "text-font" is required to be 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>. @@ -4439,6 +4441,28 @@ public class Expression { public static FormatOption formatTextFont(@NonNull String[] fontStack) { return new FormatOption("text-font", literal(fontStack)); } + + /** + * If set, the text-color argument overrides the color specified by the root paint properties. + * + * @param expression expression + * @return format option + */ + @NonNull + public static FormatOption formatTextColor(@NonNull Expression expression) { + return new FormatOption("text-color", expression); + } + + /** + * If set, the text-color argument overrides the color specified by the root paint properties. + * + * @param color value + * @return format option + */ + @NonNull + public static FormatOption formatTextColor(@ColorInt int color) { + return new FormatOption("text-color", color(color)); + } } /** 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 index c96a104afd..a5b0dfbfe8 100644 --- 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 @@ -1,9 +1,12 @@ package com.mapbox.mapboxsdk.style.types; +import android.support.annotation.ColorInt; import android.support.annotation.Keep; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.mapbox.mapboxsdk.utils.ColorUtils; + import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -13,9 +16,19 @@ import java.util.Map; */ @Keep public class FormattedSection { - private final String text; - private final Number fontScale; - private final String[] fontStack; + private String text; + private Number fontScale; + private String[] fontStack; + private String textColor; + + /** + * Creates a formatted section. + * + * @param text displayed string + */ + public FormattedSection(@NonNull String text) { + this(text, null, null, null); + } /** * Creates a formatted section. @@ -23,12 +36,23 @@ public class FormattedSection { * @param text displayed string * @param fontScale scale of the font, setting to null will fall back to style's default settings * @param fontStack main and fallback fonts that are a part of the style, - * setting null will fall back to style's default settings + * setting null will fall back to style's default settings. + * 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 textColor text color, setting to null will fall back to style's default settings. + * Value of red, green, blue components must range between 0 and 255, + * an alpha component must range between 0 and 1. + * <p> + * For more information see + * <a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/#types-color">the documentation</a>. */ - public FormattedSection(@NonNull String text, @Nullable Number fontScale, @Nullable String[] fontStack) { + public FormattedSection(@NonNull String text, @Nullable Number fontScale, @Nullable String[] fontStack, + @Nullable String textColor) { this.text = text; this.fontScale = fontScale; this.fontStack = fontStack; + this.textColor = textColor; } /** @@ -36,18 +60,27 @@ public class FormattedSection { * * @param text displayed string * @param fontScale scale of the font, setting to null will fall back to style's default settings + * @param fontStack main and fallback fonts that are a part of the style, + * setting null will fall back to style's default settings + * @deprecated use {@link #FormattedSection(String)} and setters + * or {@link #FormattedSection(String, Number, String[], String)} instead */ - public FormattedSection(@NonNull String text, @Nullable Number fontScale) { - this(text, fontScale, null); + @Deprecated + public FormattedSection(@NonNull String text, @Nullable Number fontScale, @Nullable String[] fontStack) { + this(text, fontScale, fontStack, null); } /** * Creates a formatted section. * - * @param text displayed string + * @param text displayed string + * @param fontScale scale of the font, setting to null will fall back to style's default settings + * @deprecated use {@link #FormattedSection(String)} and setters + * or {@link #FormattedSection(String, Number, String[], String)} instead */ - public FormattedSection(@NonNull String text) { - this(text, null, null); + @Deprecated + public FormattedSection(@NonNull String text, @Nullable Number fontScale) { + this(text, fontScale, null, null); } /** @@ -56,9 +89,12 @@ public class FormattedSection { * @param text displayed string * @param fontStack main and fallback fonts that are a part of the style, * setting null will fall back to style's default settings + * @deprecated use {@link #FormattedSection(String)} and setters + * or {@link #FormattedSection(String, Number, String[], String)} instead */ + @Deprecated public FormattedSection(@NonNull String text, @Nullable String[] fontStack) { - this(text, null, fontStack); + this(text, null, fontStack, null); } /** @@ -91,6 +127,67 @@ public class FormattedSection { return fontStack; } + /** + * Returns the text color. + * + * @return text color + */ + public String getTextColor() { + return textColor; + } + + /** + * Set font scale. Setting to null will fall back to style's default settings. + * + * @param fontScale fontScale + */ + public void setFontScale(@Nullable Number fontScale) { + // called from JNI + this.fontScale = fontScale; + } + + /** + * Set main and fallback fonts that are a part of the style. Setting null will fall back to style's default settings. + * <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 fontStack + */ + public void setFontStack(@Nullable String[] fontStack) { + // called from JNI + this.fontStack = fontStack; + } + + /** + * Set text color. Setting to null will fall back to style's default settings. + * Value of red, green, blue components must range between 0 and 255, + * an alpha component must range between 0 and 1. + * <p> + * For more information see + * <a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/#types-color">the documentation</a>. + * + * @param textColor text color + */ + public void setTextColor(@Nullable String textColor) { + this.textColor = textColor; + } + + /** + * Set the text color. + * + * @param textColor text color. + */ + public void setTextColor(@ColorInt int textColor) { + this.textColor = ColorUtils.colorToRgbaString(textColor); + } + + void setTextColor(@NonNull Object textColor) { + // called from JNI + // because core is returning R, G and B components in range of 0 to 1, we need to convert them to be in 0 to 255. + setTextColor(ColorUtils.colorToRgbaString(ColorUtils.rgbaToColor((String) textColor))); + } + @Override public boolean equals(Object o) { if (this == o) { @@ -109,7 +206,10 @@ public class FormattedSection { return false; } // Probably incorrect - comparing Object[] arrays with Arrays.equals - return Arrays.equals(fontStack, that.fontStack); + if (!Arrays.equals(fontStack, that.fontStack)) { + return false; + } + return textColor != null ? textColor.equals(that.textColor) : that.textColor == null; } @Override @@ -117,6 +217,7 @@ public class FormattedSection { int result = text != null ? text.hashCode() : 0; result = 31 * result + (fontScale != null ? fontScale.hashCode() : 0); result = 31 * result + Arrays.hashCode(fontStack); + result = 31 * result + (textColor != null ? textColor.hashCode() : 0); return result; } @@ -124,6 +225,7 @@ public class FormattedSection { Map<String, Object> params = new HashMap<>(); params.put("font-scale", fontScale); params.put("text-font", fontStack); + params.put("text-color", textColor); return new Object[] {text, params}; } } 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 e044dd1f16..fe91da8d58 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 @@ -15,6 +15,7 @@ import java.util.Locale; import java.util.Map; import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.formatFontScale; +import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.formatTextColor; import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.formatTextFont; import static com.mapbox.mapboxsdk.style.expressions.Expression.abs; import static com.mapbox.mapboxsdk.style.expressions.Expression.acos; @@ -1416,12 +1417,17 @@ public class ExpressionTest { { put("font-scale", 1.5f); put("text-font", new Object[] {"literal", new String[] {"awesome"}}); + put("text-color", new Object[] {"rgb", 255f, 0f, 0f}); } } }; Object[] actual = format( formatEntry( - literal("test"), formatFontScale(literal(1.5)), formatTextFont(literal(new String[] {"awesome"}))) + literal("test"), + formatFontScale(literal(1.5)), + formatTextFont(literal(new String[] {"awesome"})), + formatTextColor(rgb(255, 0, 0)) + ) ).toArray(); assertTrue("expression should match", Arrays.deepEquals(expected, actual)); } @@ -1450,8 +1456,15 @@ public class ExpressionTest { "test4", new TestableExpressionHashMap() { { + put("text-color", new Object[] {"rgb", 255f, 0f, 0f}); + } + }, + "test5", + new TestableExpressionHashMap() { + { put("font-scale", 1.5f); put("text-font", new Object[] {"literal", new String[] {"awesome"}}); + put("text-color", new Object[] {"rgb", 255f, 0f, 0f}); } } }; @@ -1459,8 +1472,13 @@ public class ExpressionTest { formatEntry(literal("test"), formatTextFont(new String[] {"awesome"})), formatEntry("test2", formatFontScale(1.5)), formatEntry(literal("test3")), + formatEntry(literal("test4"), formatTextColor(rgb(255, 0, 0))), formatEntry( - literal("test4"), formatFontScale(literal(1.5)), formatTextFont(new String[] {"awesome"})) + literal("test5"), + formatFontScale(literal(1.5)), + formatTextFont(new String[] {"awesome"}), + formatTextColor(rgb(255, 0, 0)) + ) ).toArray(); assertTrue("expression should match", Arrays.deepEquals(expected, actual)); } 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 cf7bedeaea..75dcbf1209 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 @@ -17,8 +17,8 @@ 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.EspressoTest; -import com.mapbox.mapboxsdk.testapp.utils.TestingAsyncUtils; import com.mapbox.mapboxsdk.testapp.utils.ResourceUtils; +import com.mapbox.mapboxsdk.testapp.utils.TestingAsyncUtils; import com.mapbox.mapboxsdk.utils.ColorUtils; import org.junit.Test; @@ -29,6 +29,7 @@ import java.io.IOException; import timber.log.Timber; import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.formatFontScale; +import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.formatTextColor; import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.formatTextFont; import static com.mapbox.mapboxsdk.style.expressions.Expression.collator; import static com.mapbox.mapboxsdk.style.expressions.Expression.eq; @@ -52,6 +53,7 @@ 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.textColor; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textField; import static com.mapbox.mapboxsdk.testapp.action.MapboxMapAction.invoke; import static org.junit.Assert.assertEquals; @@ -353,6 +355,35 @@ public class ExpressionTest extends EspressoTest { } @Test + public void testConstFormatExpressionTextColorParam() { + validateTestSetup(); + invoke(mapboxMap, (uiController, mapboxMap) -> { + LatLng latLng = new LatLng(51, 17); + mapboxMap.getStyle() + .addSource(new GeoJsonSource("source", Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude()))); + SymbolLayer layer = new SymbolLayer("layer", "source"); + mapboxMap.getStyle().addLayer(layer); + + Expression expression = format( + formatEntry( + literal("test"), + formatTextColor(literal("yellow")) + ) + ); + layer.setProperties(textField(expression)); + TestingAsyncUtils.INSTANCE.waitForLayer(uiController, idlingResource.getMapView()); + + assertFalse( + mapboxMap.queryRenderedFeatures(mapboxMap.getProjection().toScreenLocation(latLng), "layer").isEmpty() + ); + assertNull(layer.getTextField().getExpression()); + assertEquals(new Formatted( + new FormattedSection("test", null, null, "rgba(255, 255, 0, 1)") + ), layer.getTextField().getValue()); + }); + } + + @Test public void testConstFormatExpressionAllParams() { validateTestSetup(); invoke(mapboxMap, (uiController, mapboxMap) -> { @@ -366,7 +397,8 @@ public class ExpressionTest extends EspressoTest { formatEntry( "test", formatFontScale(0.5), - formatTextFont(new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"}) + formatTextFont(new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"}), + formatTextColor(rgb(126.7, 0, 0)) ) ); layer.setProperties(textField(expression)); @@ -379,7 +411,8 @@ public class ExpressionTest extends EspressoTest { assertEquals(new Formatted( new FormattedSection("test", 0.5, - new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"}) + new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"}, + "rgba(126, 0, 0, 1)") ), layer.getTextField().getValue()); }); } @@ -400,7 +433,7 @@ public class ExpressionTest extends EspressoTest { formatFontScale(1.5), formatTextFont(new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"}) ), - formatEntry("\ntest2", formatFontScale(2)) + formatEntry("\ntest2", formatFontScale(2), formatTextColor(Color.BLUE)) ); layer.setProperties(textField(expression)); TestingAsyncUtils.INSTANCE.waitForLayer(uiController, idlingResource.getMapView()); @@ -412,7 +445,7 @@ public class ExpressionTest extends EspressoTest { assertEquals(new Formatted( new FormattedSection("test", 1.5, new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"}), - new FormattedSection("\ntest2", 2.0) + new FormattedSection("\ntest2", 2.0, null, "rgba(0, 0, 255, 1)") ), layer.getTextField().getValue()); }); } @@ -425,6 +458,7 @@ public class ExpressionTest extends EspressoTest { Feature feature = Feature.fromGeometry(Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())); feature.addStringProperty("test_property", "test"); feature.addNumberProperty("test_property_number", 1.5); + feature.addStringProperty("test_property_color", "green"); mapboxMap.getStyle().addSource(new GeoJsonSource("source", feature)); SymbolLayer layer = new SymbolLayer("layer", "source"); mapboxMap.getStyle().addLayer(layer); @@ -433,7 +467,8 @@ public class ExpressionTest extends EspressoTest { formatEntry( get("test_property"), Expression.FormatOption.formatFontScale(number(get("test_property_number"))), - formatTextFont(new String[] {"Arial Unicode MS Regular", "DIN Offc Pro Regular"}) + formatTextFont(new String[] {"Arial Unicode MS Regular", "DIN Offc Pro Regular"}), + formatTextColor(toColor(get("test_property_color"))) ) ); layer.setProperties(textField(expression)); @@ -454,6 +489,7 @@ public class ExpressionTest extends EspressoTest { Feature feature = Feature.fromGeometry(Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())); feature.addStringProperty("test_property", "test"); feature.addNumberProperty("test_property_number", 1.5); + feature.addStringProperty("test_property_color", "rgba(0, 255, 0, 1)"); mapboxMap.getStyle().addSource(new GeoJsonSource("source", feature)); SymbolLayer layer = new SymbolLayer("layer", "source"); mapboxMap.getStyle().addLayer(layer); @@ -462,11 +498,12 @@ public class ExpressionTest extends EspressoTest { formatEntry( get("test_property"), formatFontScale(1.25), - formatTextFont(new String[] {"Arial Unicode MS Regular", "DIN Offc Pro Regular"}) + formatTextFont(new String[] {"Arial Unicode MS Regular", "DIN Offc Pro Regular"}), + formatTextColor(toColor(get("test_property_color"))) ), formatEntry("\ntest2", formatFontScale(2)) ); - layer.setProperties(textField(expression)); + layer.setProperties(textField(expression), textColor("rgba(128, 0, 0, 1)")); TestingAsyncUtils.INSTANCE.waitForLayer(uiController, idlingResource.getMapView()); assertFalse(mapboxMap.queryRenderedFeatures(mapboxMap.getProjection().toScreenLocation(latLng), "layer") @@ -509,9 +546,10 @@ public class ExpressionTest extends EspressoTest { Formatted formatted = new Formatted( new FormattedSection("test", 1.5), - new FormattedSection("\ntest", 0.5, new String[] {"Arial Unicode MS Regular", "DIN Offc Pro Regular"}) + new FormattedSection("\ntest", 0.5, new String[] {"Arial Unicode MS Regular", "DIN Offc Pro Regular"}), + new FormattedSection("test", null, null, "rgba(0, 255, 0, 1)") ); - layer.setProperties(textField(formatted)); + layer.setProperties(textField(formatted), textColor("rgba(128, 0, 0, 1)")); TestingAsyncUtils.INSTANCE.waitForLayer(uiController, idlingResource.getMapView()); assertFalse(mapboxMap.queryRenderedFeatures(mapboxMap.getProjection().toScreenLocation(latLng), "layer") diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java index 498dc6c0d0..1a2b6c3030 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolGeneratorActivity.java @@ -14,6 +14,7 @@ import android.view.MenuItem; import android.view.View; import android.widget.TextView; import android.widget.Toast; + import com.mapbox.geojson.Feature; import com.mapbox.geojson.FeatureCollection; import com.mapbox.mapboxsdk.maps.MapView; @@ -26,13 +27,14 @@ import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; import com.mapbox.mapboxsdk.style.sources.Source; import com.mapbox.mapboxsdk.testapp.R; import com.mapbox.mapboxsdk.testapp.utils.ResourceUtils; -import timber.log.Timber; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.List; +import timber.log.Timber; + import static com.mapbox.mapboxsdk.style.expressions.Expression.concat; import static com.mapbox.mapboxsdk.style.expressions.Expression.division; import static com.mapbox.mapboxsdk.style.expressions.Expression.downcase; @@ -55,6 +57,7 @@ import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAnchor; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconOffset; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconOpacity; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconSize; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textAnchor; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textColor; @@ -102,6 +105,12 @@ public class SymbolGeneratorActivity extends AppCompatActivity implements OnMapR List<Feature> features = mapboxMap.queryRenderedFeatures(screenPoint, LAYER_ID); if (!features.isEmpty()) { Feature feature = features.get(0); + // validate symbol flicker regression for #13407 + SymbolLayer layer = mapboxMap.getStyle().getLayerAs(LAYER_ID); + layer.setProperties(iconOpacity(match( + get(FEATURE_ID), literal(1.0f), + stop(feature.getStringProperty(FEATURE_ID), 0.3f) + ))); Timber.v("Feature was clicked with data: %s", feature.toJson()); Toast.makeText( SymbolGeneratorActivity.this, diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolLayerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolLayerActivity.java index e6bcbe2bfc..244101746b 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolLayerActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/SymbolLayerActivity.java @@ -3,13 +3,13 @@ package com.mapbox.mapboxsdk.testapp.activity.style; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PointF; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; import android.view.ViewGroup; + import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import com.mapbox.geojson.Feature; @@ -22,25 +22,28 @@ import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.MapboxMapOptions; import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.maps.Style; +import com.mapbox.mapboxsdk.style.expressions.Expression; import com.mapbox.mapboxsdk.style.layers.Property; 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.testapp.R; import com.mapbox.mapboxsdk.utils.BitmapUtils; +import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Random; import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.formatFontScale; +import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.formatTextColor; import static com.mapbox.mapboxsdk.style.expressions.Expression.FormatOption.formatTextFont; import static com.mapbox.mapboxsdk.style.expressions.Expression.concat; 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.id; 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.stop; +import static com.mapbox.mapboxsdk.style.expressions.Expression.rgb; import static com.mapbox.mapboxsdk.style.expressions.Expression.switchCase; import static com.mapbox.mapboxsdk.style.expressions.Expression.toBool; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap; @@ -48,7 +51,6 @@ import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAnchor; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconColor; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconIgnorePlacement; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage; -import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconOpacity; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconSize; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textAllowOverlap; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textAnchor; @@ -56,6 +58,7 @@ import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textColor; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textField; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textFont; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textIgnorePlacement; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textRotationAlignment; import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textSize; /** @@ -68,18 +71,42 @@ public class SymbolLayerActivity extends AppCompatActivity implements MapboxMap. private static final String MARKER_SOURCE = "marker-source"; private static final String MARKER_LAYER = "marker-layer"; - private static final String MARKER_ICON = "my-layers-image"; - public static final String ID_FEATURE_PROPERTY = "id"; - public static final String SELECTED_FEATURE_PROPERTY = "selected"; - public static final String TITLE_FEATURE_PROPERTY = "title"; + private static final String ID_FEATURE_PROPERTY = "id"; + private static final String SELECTED_FEATURE_PROPERTY = "selected"; + private static final String TITLE_FEATURE_PROPERTY = "title"; + + private static final String[] NORMAL_FONT_STACK = new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"}; + private static final String[] BOLD_FONT_STACK = new String[] {"DIN Offc Pro Bold", "Arial Unicode MS Regular"}; + + private static final String MAPBOX_SIGN_SOURCE = "mapbox-sign-source"; + private static final String MAPBOX_SIGN_LAYER = "mapbox-sign-layer"; + + private static final Expression TEXT_FIELD_EXPRESSION = + switchCase(toBool(get(SELECTED_FEATURE_PROPERTY)), + format( + formatEntry( + get(TITLE_FEATURE_PROPERTY), + formatTextFont(BOLD_FONT_STACK) + ), + formatEntry("\nis fun!", formatFontScale(0.75)) + ), + format( + formatEntry("This is", formatFontScale(0.75)), + formatEntry( + concat(literal("\n"), get(TITLE_FEATURE_PROPERTY)), + formatFontScale(1.25), + formatTextFont(BOLD_FONT_STACK) + ) + ) + ); - private GeoJsonSource geoJsonSource; + private final Random random = new Random(); + private GeoJsonSource markerSource; private FeatureCollection markerCollection; - private SymbolLayer symbolLayer; + private SymbolLayer markerSymbolLayer; + private SymbolLayer mapboxSignSymbolLayer; private MapboxMap mapboxMap; private MapView mapView; - private boolean initialFont; - private boolean styleLoaded; @Override public void onCreate(Bundle savedInstanceState) { @@ -104,50 +131,47 @@ public class SymbolLayerActivity extends AppCompatActivity implements MapboxMap. @Override public void onMapReady(@NonNull MapboxMap mapboxMap) { this.mapboxMap = mapboxMap; - // image - Drawable icLayersDrawable = getResources().getDrawable(R.drawable.ic_layers); - Bitmap icLayersBitmap = BitmapUtils.getBitmapFromDrawable(icLayersDrawable); + // marker image + Bitmap androidBitmap = BitmapUtils.getBitmapFromDrawable(getResources().getDrawable(R.drawable.ic_android_2)); + Bitmap carBitmap = BitmapUtils.getBitmapFromDrawable( + getResources().getDrawable(R.drawable.ic_directions_car_black)); - // source + // marker source markerCollection = FeatureCollection.fromFeatures(new Feature[] { - Feature.fromGeometry(Point.fromLngLat(4.91638, 52.35673), featureProperties("1", "Marker 1")), - Feature.fromGeometry(Point.fromLngLat(4.91638, 52.34673), featureProperties("2", "Marker 2")) + Feature.fromGeometry(Point.fromLngLat(4.91638, 52.35673), featureProperties("1", "Android")), + Feature.fromGeometry(Point.fromLngLat(4.91638, 52.34673), featureProperties("2", "Car")) }); - geoJsonSource = new GeoJsonSource(MARKER_SOURCE, markerCollection); + markerSource = new GeoJsonSource(MARKER_SOURCE, markerCollection); - // layer - symbolLayer = new SymbolLayer(MARKER_LAYER, MARKER_SOURCE) + // marker layer + markerSymbolLayer = new SymbolLayer(MARKER_LAYER, MARKER_SOURCE) .withProperties( - iconImage(MARKER_ICON), + iconImage(get(TITLE_FEATURE_PROPERTY)), iconIgnorePlacement(true), iconAllowOverlap(true), - iconSize(switchCase(toBool(get(SELECTED_FEATURE_PROPERTY)), literal(3.0f), literal(1.0f))), + iconSize(switchCase(toBool(get(SELECTED_FEATURE_PROPERTY)), literal(1.5f), literal(1.0f))), iconAnchor(Property.ICON_ANCHOR_BOTTOM), - iconColor(Color.RED), - textField( - format( - formatEntry("this is", formatFontScale(0.75)), - formatEntry( - concat(literal("\n"), get(TITLE_FEATURE_PROPERTY)), - formatFontScale(1.25), - formatTextFont(new String[] {"DIN Offc Pro Bold", "Arial Unicode MS Regular"}) - ) - ) - ), - textFont(new String[] {"DIN Offc Pro Regular", "Arial Unicode MS Regular"}), - textColor(Color.RED), + iconColor(Color.BLUE), + textField(TEXT_FIELD_EXPRESSION), + textFont(NORMAL_FONT_STACK), + textColor(Color.BLUE), textAllowOverlap(true), textIgnorePlacement(true), textAnchor(Property.TEXT_ANCHOR_TOP), textSize(10f) ); + // mapbox sign layer + Source mapboxSignSource = new GeoJsonSource(MAPBOX_SIGN_SOURCE, Point.fromLngLat(4.91638, 52.3510)); + mapboxSignSymbolLayer = new SymbolLayer(MAPBOX_SIGN_LAYER, MAPBOX_SIGN_SOURCE); + shuffleMapboxSign(); + mapboxMap.setStyle(new Style.Builder() - .fromUrl("asset://streets.json") - .withImage(MARKER_ICON, Objects.requireNonNull(icLayersBitmap), true) - .withSource(geoJsonSource) - .withLayer(symbolLayer), - style -> styleLoaded = true + .fromUrl("asset://streets.json") + .withImage("Android", Objects.requireNonNull(androidBitmap), false) + .withImage("Car", Objects.requireNonNull(carBitmap), false) + .withSources(markerSource, mapboxSignSource) + .withLayers(markerSymbolLayer, mapboxSignSymbolLayer) ); // Set a click-listener so we can manipulate the map @@ -158,52 +182,90 @@ public class SymbolLayerActivity extends AppCompatActivity implements MapboxMap. public boolean onMapClick(@NonNull LatLng point) { // Query which features are clicked PointF screenLoc = mapboxMap.getProjection().toScreenLocation(point); - List<Feature> features = mapboxMap.queryRenderedFeatures(screenLoc, MARKER_LAYER); - if (!features.isEmpty()) { - for (Feature feature : markerCollection.features()) { - if (feature.getStringProperty(TITLE_FEATURE_PROPERTY) - .equals(features.get(0).getStringProperty(TITLE_FEATURE_PROPERTY))) { + List<Feature> markerFeatures = mapboxMap.queryRenderedFeatures(screenLoc, MARKER_LAYER); + if (!markerFeatures.isEmpty()) { + for (Feature feature : Objects.requireNonNull(markerCollection.features())) { + if (feature.getStringProperty(ID_FEATURE_PROPERTY) + .equals(markerFeatures.get(0).getStringProperty(ID_FEATURE_PROPERTY))) { // use DDS boolean selected = feature.getBooleanProperty(SELECTED_FEATURE_PROPERTY); feature.addBooleanProperty(SELECTED_FEATURE_PROPERTY, !selected); - - // validate symbol flicker regression for #13407 - symbolLayer.setProperties(iconOpacity(match( - get(ID_FEATURE_PROPERTY), literal(1.0f), - stop(feature.getStringProperty("id"), 0.3f) - ))); } } - geoJsonSource.setGeoJson(markerCollection); - - + markerSource.setGeoJson(markerCollection); + } else { + List<Feature> mapboxSignFeatures = mapboxMap.queryRenderedFeatures(screenLoc, MAPBOX_SIGN_LAYER); + if (!mapboxSignFeatures.isEmpty()) { + shuffleMapboxSign(); + } } return false; } private void toggleTextSize() { - SymbolLayer layer = mapboxMap.getStyle().getLayerAs(MARKER_LAYER); - layer.setProperties(layer.getTextSize().getValue() > 10 ? textSize(10f) : textSize(20f)); + if (markerSymbolLayer != null) { + Number size = markerSymbolLayer.getTextSize().getValue(); + if (size != null) { + markerSymbolLayer.setProperties((float) size > 10 ? textSize(10f) : textSize(20f)); + } + } } private void toggleTextField() { - SymbolLayer layer = mapboxMap.getStyle().getLayerAs(MARKER_LAYER); - layer.setProperties("{title}".equals(layer.getTextField().getValue()) ? textField("āA") : textField("{title}")); + if (markerSymbolLayer != null) { + if (TEXT_FIELD_EXPRESSION.equals(markerSymbolLayer.getTextField().getExpression())) { + markerSymbolLayer.setProperties(textField("āA")); + } else { + markerSymbolLayer.setProperties(textField(TEXT_FIELD_EXPRESSION)); + } + } } private void toggleTextFont() { - SymbolLayer layer = mapboxMap.getStyle().getLayerAs(MARKER_LAYER); - if (initialFont) { - layer.setProperties(textFont(new String[] {"DIN Offc Pro Bold", "Arial Unicode MS Bold"})); - } else { - layer.setProperties(textFont(new String[] {"DIN Offc Pro Medium", "Arial Unicode MS Regular"})); + if (markerSymbolLayer != null) { + if (Arrays.equals(markerSymbolLayer.getTextFont().getValue(), NORMAL_FONT_STACK)) { + markerSymbolLayer.setProperties(textFont(BOLD_FONT_STACK)); + } else { + markerSymbolLayer.setProperties(textFont(NORMAL_FONT_STACK)); + } } - initialFont = !initialFont; } - private JsonObject featureProperties(String id, String title) { + private void shuffleMapboxSign() { + if (mapboxSignSymbolLayer != null) { + mapboxSignSymbolLayer.setProperties( + textField( + format( + formatEntry("M", formatFontScale(2)), + getRandomColorEntryForString("a"), + getRandomColorEntryForString("p"), + getRandomColorEntryForString("b"), + getRandomColorEntryForString("o"), + getRandomColorEntryForString("x") + ) + ), + textColor(Color.BLACK), + textFont(BOLD_FONT_STACK), + textSize(25f), + textRotationAlignment(Property.TEXT_ROTATION_ALIGNMENT_MAP) + ); + } + } + + private Expression.FormatEntry getRandomColorEntryForString(@NonNull String string) { + return formatEntry(string, + formatTextColor( + rgb( + random.nextInt(256), + random.nextInt(256), + random.nextInt(256) + ) + )); + } + + private JsonObject featureProperties(@NonNull String id, @NonNull String title) { JsonObject object = new JsonObject(); object.add(ID_FEATURE_PROPERTY, new JsonPrimitive(id)); object.add(TITLE_FEATURE_PROPERTY, new JsonPrimitive(title)); @@ -264,10 +326,6 @@ public class SymbolLayerActivity extends AppCompatActivity implements MapboxMap. @Override public boolean onOptionsItemSelected(MenuItem item) { - if (!styleLoaded) { - return super.onOptionsItemSelected(item); - } - switch (item.getItemId()) { case R.id.action_toggle_text_size: toggleTextSize(); diff --git a/platform/android/src/style/formatted.cpp b/platform/android/src/style/formatted.cpp index 8659e6961e..69c3454e23 100644 --- a/platform/android/src/style/formatted.cpp +++ b/platform/android/src/style/formatted.cpp @@ -1,5 +1,7 @@ #include "formatted.hpp" #include "formatted_section.hpp" +#include "../conversion/conversion.hpp" +#include "../conversion/constant.hpp" namespace mbgl { namespace android { @@ -11,36 +13,38 @@ void Formatted::registerNative(jni::JNIEnv& 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); + static auto& formattedSectionClass = 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); + static auto formattedSectionConstructor = formattedSectionClass.GetConstructor<jni::String>(env); + auto formattedSection = formattedSectionClass.New(env, formattedSectionConstructor, text); - if (section.fontStack && section.fontScale) { + if (section.fontScale) { double fontScale = section.fontScale.value(); + static auto method = formattedSectionClass.GetMethod<void (jni::Number)>(env, "setFontScale"); + formattedSection.Call(env, method, jni::Box(env, fontScale)); + } + + 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, jni::Number, jni::Array<jni::String>>(env); - sections.Set(env, i, formattedSection.New(env, formattedSectionConstructor, text, jni::Box(env, fontScale), fontStack)); - } else if (section.fontScale) { - double fontScale = section.fontScale.value(); - static auto formattedSectionConstructor = formattedSection.GetConstructor<jni::String, jni::Number>(env); - sections.Set(env, i, formattedSection.New(env, formattedSectionConstructor, text, jni::Box(env, fontScale))); - } else 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, jni::Array<jni::String>>(env); - sections.Set(env, i, formattedSection.New(env, formattedSectionConstructor, text, fontStack)); - } else { - static auto formattedSectionConstructor = formattedSection.GetConstructor<jni::String>(env); - sections.Set(env, i, formattedSection.New(env, formattedSectionConstructor, text)); + static auto method = formattedSectionClass.GetMethod<void (jni::Array<jni::String>)>(env, "setFontStack"); + formattedSection.Call(env, method, fontStack); } + + if (section.textColor) { + using namespace mbgl::android::conversion; + auto textColor = std::move(*convert<jni::Local<jni::Object<>>>(env, *section.textColor)); + static auto method = formattedSectionClass.GetMethod<void (jni::Object<>)>(env, "setTextColor"); + formattedSection.Call(env, method, textColor); + } + + sections.Set(env, i, formattedSection); } return formatted.New(env, formattedConstructor, sections); |