From 2a8359b0280e861450f9bdf390a01e0363202b3a Mon Sep 17 00:00:00 2001 From: zmiao Date: Thu, 22 Aug 2019 18:54:31 +0300 Subject: [android] bindings for aggregated cluster properties (#15425) * [android] bindings for aggregated cluster properties * [android] change the parameter type for adding clusterProperty * [android] add changelog * remove extra emplty line --- platform/android/CHANGELOG.md | 2 ++ .../mapboxsdk/style/expressions/Expression.java | 21 +++++++++++++++++ .../mapboxsdk/style/sources/GeoJsonOptions.java | 27 ++++++++++++++++++++++ .../activity/style/GeoJsonClusteringActivity.java | 11 +++++++-- platform/android/src/java_types.cpp | 8 +++++++ platform/android/src/java_types.hpp | 6 +++++ platform/android/src/style/android_conversion.hpp | 15 +++++++++--- platform/android/src/style/value.cpp | 6 +++++ platform/android/src/style/value.hpp | 1 + 9 files changed, 92 insertions(+), 5 deletions(-) diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index 54729fbcc5..ca57e48378 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -3,6 +3,8 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to do so please see the [`Contributing Guide`](https://github.com/mapbox/mapbox-gl-native/blob/master/CONTRIBUTING.md) first to get started. ## master +### Features + - Introduce `clusterProperties` option for aggregated cluster properties. [#15425](https://github.com/mapbox/mapbox-gl-native/pull/15425) ## 8.3.0 This release changes how offline tile requests are billed — they are now billed on a pay-as-you-go basis and all developers are able raise the offline tile limit for their users. Offline requests were previously exempt from monthly active user (MAU) billing and increasing the offline per-user tile limit to more than 6,000 tiles required the purchase of an enterprise license. By upgrading to this release, you are opting into the changes outlined in [this blog post](https://blog.mapbox.com/offline-maps-for-all-bb0fc51827be) and [#15380](https://github.com/mapbox/mapbox-gl-native/pull/15380). 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 0f5ce76725..d6cddea066 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 @@ -1435,6 +1435,27 @@ public class Expression { return new Expression("id"); } + /** + * Gets the value of a cluster property accumulated so far. Can only be used in the clusterProperties + * option of a clustered GeoJSON source. + *

+ * Example usage: + *

+ *
+   * {@code
+   *  GeoJsonOptions options = new GeoJsonOptions()
+   *                              .withCluster(true)
+   *                              .withClusterProperty("max", max(accumulated(), get("max")).toArray(), get("mag").toArray());
+   * }
+   * 
+ * + * @return expression + * @see Style specification + */ + public static Expression accumulated() { + return new Expression("accumulated"); + } + /** * Gets the kernel density estimation of a pixel in a heatmap layer, * which is a relative measure of how many data points are crowded around a particular pixel. diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonOptions.java index 1e1b9bafa6..091079ab92 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonOptions.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonOptions.java @@ -1,9 +1,12 @@ package com.mapbox.mapboxsdk.style.sources; import android.support.annotation.NonNull; +import com.mapbox.mapboxsdk.style.expressions.Expression; import java.util.HashMap; +import static com.mapbox.mapboxsdk.style.expressions.Expression.ExpressionLiteral; + /** * Builder class for composing GeoJsonSource objects. * @@ -109,4 +112,28 @@ public class GeoJsonOptions extends HashMap { this.put("clusterRadius", clusterRadius); return this; } + + /** + * An object defining custom properties on the generated clusters if clustering is enabled, + * aggregating values from clustered points. Has the form {"property_name": [operator, [map_expression]]} or + * {"property_name": [[operator, accumulated, expression], [map_expression]]} + * + * @param propertyName name of the property + * @param operatorExpr operatorExpr is any expression function that accepts at least 2 operands (e.g. "+" or "max"). + * It accumulates the property value from clusters/points the cluster contains. It can either be + * a literal with single operator, or be a valid expression + * @param mapExpr map expression produces the value of a single point, it shall be a valid expression + * @return the current instance for chaining + */ + @NonNull + public GeoJsonOptions withClusterProperty(String propertyName, Expression operatorExpr, Expression mapExpr) { + HashMap properties = containsKey("clusterProperties") + ? (HashMap) get("clusterProperties") : new HashMap(); + Object operator = (operatorExpr instanceof ExpressionLiteral) + ? ((ExpressionLiteral)operatorExpr).toValue() : operatorExpr.toArray(); + Object map = mapExpr.toArray(); + properties.put(propertyName, new Object[]{operator, map}); + this.put("clusterProperties", properties); + return this; + } } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/GeoJsonClusteringActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/GeoJsonClusteringActivity.java index 732a7929b8..cb2701d436 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/GeoJsonClusteringActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/GeoJsonClusteringActivity.java @@ -30,6 +30,8 @@ import java.util.List; import java.util.Objects; import static com.mapbox.mapboxsdk.style.expressions.Expression.all; +import static com.mapbox.mapboxsdk.style.expressions.Expression.accumulated; +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.exponential; import static com.mapbox.mapboxsdk.style.expressions.Expression.get; @@ -39,6 +41,8 @@ import static com.mapbox.mapboxsdk.style.expressions.Expression.has; 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.lt; +import static com.mapbox.mapboxsdk.style.expressions.Expression.max; +import static com.mapbox.mapboxsdk.style.expressions.Expression.neq; import static com.mapbox.mapboxsdk.style.expressions.Expression.rgb; import static com.mapbox.mapboxsdk.style.expressions.Expression.stop; import static com.mapbox.mapboxsdk.style.expressions.Expression.toNumber; @@ -138,6 +142,9 @@ public class GeoJsonClusteringActivity extends AppCompatActivity { .withCluster(true) .withClusterMaxZoom(14) .withClusterRadius(50) + .withClusterProperty("max", max(accumulated(), get("max")), get("mag")) + .withClusterProperty("sum", literal("+"), get("mag")) + .withClusterProperty("felt", literal("any"), neq(get("felt"), literal("null"))) ); } @@ -182,9 +189,9 @@ public class GeoJsonClusteringActivity extends AppCompatActivity { } private SymbolLayer createClusterTextLayer() { - return new SymbolLayer("count", "earthquakes") + return new SymbolLayer("property", "earthquakes") .withProperties( - textField(Expression.toString(get("point_count"))), + textField(concat(get("point_count"), literal(", "), get("max"))), textSize(12f), textColor(Color.WHITE), textIgnorePlacement(true), diff --git a/platform/android/src/java_types.cpp b/platform/android/src/java_types.cpp index dd165470cf..7a1ba93a58 100644 --- a/platform/android/src/java_types.cpp +++ b/platform/android/src/java_types.cpp @@ -18,6 +18,10 @@ namespace java { jni::jclass* Map::jclass; jni::jmethodID* Map::getMethodId; + jni::jmethodID* Map::keySetMethodId; + + jni::jclass* Set::jclass; + jni::jmethodID* Set::toArrayMethodId; void registerNatives(JNIEnv& env) { ObjectArray::jclass = jni::NewGlobalRef(env, &jni::FindClass(env, "[Ljava/lang/Object;")).release(); @@ -34,6 +38,10 @@ namespace java { Map::jclass = jni::NewGlobalRef(env, &jni::FindClass(env, "java/util/Map")).release(); Map::getMethodId = &jni::GetMethodID(env, *Map::jclass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); + Map::keySetMethodId = &jni::GetMethodID(env, *Map::jclass, "keySet", "()Ljava/util/Set;"); + + Set::jclass = jni::NewGlobalRef(env, &jni::FindClass(env, "java/util/Set")).release(); + Set::toArrayMethodId = &jni::GetMethodID(env, *Set::jclass, "toArray", "()[Ljava/lang/Object;"); } } diff --git a/platform/android/src/java_types.hpp b/platform/android/src/java_types.hpp index edec5cb550..c7c93ce71b 100644 --- a/platform/android/src/java_types.hpp +++ b/platform/android/src/java_types.hpp @@ -29,6 +29,12 @@ namespace java { struct Map { static jni::jclass* jclass; static jni::jmethodID* getMethodId; + static jni::jmethodID* keySetMethodId; + }; + + struct Set { + static jni::jclass* jclass; + static jni::jmethodID* toArrayMethodId; }; void registerNatives(JNIEnv&); diff --git a/platform/android/src/style/android_conversion.hpp b/platform/android/src/style/android_conversion.hpp index 8559720b2f..d38dbfa684 100644 --- a/platform/android/src/style/android_conversion.hpp +++ b/platform/android/src/style/android_conversion.hpp @@ -47,9 +47,18 @@ public: } template - static optional eachMember(const mbgl::android::Value&, Fn&&) { - // TODO - mbgl::Log::Warning(mbgl::Event::Android, "eachMember not implemented"); + static optional eachMember(const mbgl::android::Value& value, Fn&& fn) { + assert(value.isObject()); + mbgl::android::Value keys = value.keyArray(); + std::size_t length = arrayLength(keys); + for(std::size_t i = 0; i < length; ++i){ + const auto k = keys.get(i).toString(); + auto v = value.get(k.c_str()); + optional result = fn(k, std::move(v)); + if (result) { + return result; + } + } return {}; } diff --git a/platform/android/src/style/value.cpp b/platform/android/src/style/value.cpp index f916909687..2f04840729 100644 --- a/platform/android/src/style/value.cpp +++ b/platform/android/src/style/value.cpp @@ -63,6 +63,12 @@ namespace android { return Value(env, jni::Local>(env, member)); } + Value Value::keyArray() const{ + jni::jobject* set = jni::CallMethod(env, value.get(), *java::Map::keySetMethodId); + jni::jobject* array = jni::CallMethod(env, set, *java::Set::toArrayMethodId); + return Value(env, jni::Local>(env, array)); + } + int Value::getLength() const { auto array = (jni::jarray*) value.get(); return jni::GetArrayLength(env, *array); diff --git a/platform/android/src/style/value.hpp b/platform/android/src/style/value.hpp index 0c702bb465..b507c5ed11 100644 --- a/platform/android/src/style/value.hpp +++ b/platform/android/src/style/value.hpp @@ -31,6 +31,7 @@ public: long toLong() const; bool toBool() const; Value get(const char* key) const; + Value keyArray() const; int getLength() const; Value get(const int index ) const; -- cgit v1.2.1