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

Commit

Permalink
[android] bindings for aggregated cluster properties (#15425)
Browse files Browse the repository at this point in the history
* [android] bindings for aggregated cluster properties

* [android] change the parameter type for adding clusterProperty

* [android] add changelog

* remove extra emplty line
  • Loading branch information
zmiao authored Aug 22, 2019
1 parent 4a5475b commit 2a8359b
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 5 deletions.
2 changes: 2 additions & 0 deletions platform/android/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,27 @@ public static Expression id() {
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.
* <p>
* Example usage:
* </p>
* <pre>
* {@code
* GeoJsonOptions options = new GeoJsonOptions()
* .withCluster(true)
* .withClusterProperty("max", max(accumulated(), get("max")).toArray(), get("mag").toArray());
* }
* </pre>
*
* @return expression
* @see <a href="https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-accumulated">Style specification</a>
*/
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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
Expand Down Expand Up @@ -109,4 +112,28 @@ public GeoJsonOptions withClusterRadius(int clusterRadius) {
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<String, Object[]> properties = containsKey("clusterProperties")
? (HashMap<String, Object[]>) get("clusterProperties") : new HashMap<String, Object[]>();
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
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;
Expand All @@ -39,6 +41,8 @@
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;
Expand Down Expand Up @@ -138,6 +142,9 @@ private GeoJsonSource createClusterSource() throws URISyntaxException {
.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")))
);
}

Expand Down Expand Up @@ -182,9 +189,9 @@ private CircleLayer createClusterLevelLayer(int level, int[][] layerColors) {
}

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),
Expand Down
8 changes: 8 additions & 0 deletions platform/android/src/java_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;");
}

}
Expand Down
6 changes: 6 additions & 0 deletions platform/android/src/java_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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&);
Expand Down
15 changes: 12 additions & 3 deletions platform/android/src/style/android_conversion.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,18 @@ class ConversionTraits<mbgl::android::Value> {
}

template <class Fn>
static optional<Error> eachMember(const mbgl::android::Value&, Fn&&) {
// TODO
mbgl::Log::Warning(mbgl::Event::Android, "eachMember not implemented");
static optional<Error> 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<Error> result = fn(k, std::move(v));
if (result) {
return result;
}
}
return {};
}

Expand Down
6 changes: 6 additions & 0 deletions platform/android/src/style/value.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ namespace android {
return Value(env, jni::Local<jni::Object<>>(env, member));
}

Value Value::keyArray() const{
jni::jobject* set = jni::CallMethod<jni::jobject*>(env, value.get(), *java::Map::keySetMethodId);
jni::jobject* array = jni::CallMethod<jni::jobject*>(env, set, *java::Set::toArrayMethodId);
return Value(env, jni::Local<jni::Object<>>(env, array));
}

int Value::getLength() const {
auto array = (jni::jarray<jni::jobject>*) value.get();
return jni::GetArrayLength(env, *array);
Expand Down
1 change: 1 addition & 0 deletions platform/android/src/style/value.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Value {
long toLong() const;
bool toBool() const;
Value get(const char* key) const;
Value keyArray() const;
int getLength() const;
Value get(const int index ) const;

Expand Down

0 comments on commit 2a8359b

Please sign in to comment.