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

[android] bindings for aggregated cluster properties #15425

Merged
merged 5 commits into from
Aug 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome! we finally have found an use-case for this.

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