Skip to content

Commit b97bb08

Browse files
committed
[annotation-plugin] add support for basic clustering
1 parent e032a40 commit b97bb08

File tree

18 files changed

+577
-13
lines changed

18 files changed

+577
-13
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,17 @@
159159
android:name="android.support.PARENT_ACTIVITY"
160160
android:value=".activity.FeatureOverviewActivity" />
161161
</activity>
162+
<activity
163+
android:name=".activity.annotation.ClusterSymbolActivity"
164+
android:description="@string/description_symbol_cluster"
165+
android:label="@string/title_symbol_cluster">
166+
<meta-data
167+
android:name="@string/category"
168+
android:value="@string/category_annotation" />
169+
<meta-data
170+
android:name="android.support.PARENT_ACTIVITY"
171+
android:value=".activity.FeatureOverviewActivity" />
172+
</activity>
162173
<activity
163174
android:name=".activity.annotation.DynamicSymbolChangeActivity"
164175
android:description="@string/description_symbol_change"
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
package com.mapbox.mapboxsdk.plugins.testapp.activity.annotation;
2+
3+
import android.content.Context;
4+
import android.graphics.Color;
5+
import android.os.AsyncTask;
6+
import android.os.Bundle;
7+
8+
import androidx.appcompat.app.AppCompatActivity;
9+
import androidx.core.util.Pair;
10+
11+
import com.mapbox.geojson.Feature;
12+
import com.mapbox.geojson.FeatureCollection;
13+
import com.mapbox.geojson.Point;
14+
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
15+
import com.mapbox.mapboxsdk.geometry.LatLng;
16+
import com.mapbox.mapboxsdk.maps.MapView;
17+
import com.mapbox.mapboxsdk.maps.MapboxMap;
18+
import com.mapbox.mapboxsdk.maps.Style;
19+
import com.mapbox.mapboxsdk.plugins.annotation.ClusterOptions;
20+
import com.mapbox.mapboxsdk.plugins.annotation.Symbol;
21+
import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager;
22+
import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions;
23+
import com.mapbox.mapboxsdk.plugins.testapp.R;
24+
25+
import java.io.BufferedReader;
26+
import java.io.IOException;
27+
import java.io.InputStream;
28+
import java.io.InputStreamReader;
29+
import java.io.Reader;
30+
import java.lang.ref.WeakReference;
31+
import java.nio.charset.Charset;
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
import java.util.Random;
35+
36+
import timber.log.Timber;
37+
38+
/**
39+
* Test activity showcasing adding a large amount of Symbols with a cluster configuration.
40+
*/
41+
public class ClusterSymbolActivity extends AppCompatActivity {
42+
43+
private SymbolManager symbolManager;
44+
private List<Symbol> symbols = new ArrayList<>();
45+
46+
private MapboxMap mapboxMap;
47+
private MapView mapView;
48+
private FeatureCollection locations;
49+
50+
@Override
51+
protected void onCreate(Bundle savedInstanceState) {
52+
super.onCreate(savedInstanceState);
53+
setContentView(R.layout.activity_cluster);
54+
55+
mapView = findViewById(R.id.mapView);
56+
mapView.onCreate(savedInstanceState);
57+
mapView.getMapAsync(this::initMap);
58+
}
59+
60+
private void initMap(MapboxMap mapboxMap) {
61+
this.mapboxMap = mapboxMap;
62+
mapboxMap.moveCamera(
63+
CameraUpdateFactory.newLatLngZoom(
64+
new LatLng(38.87031, -77.00897), 10
65+
)
66+
);
67+
68+
ClusterOptions clusterOptions = new ClusterOptions()
69+
.withColorLevels(new Pair[] {
70+
new Pair(100, Color.RED),
71+
new Pair(50, Color.BLUE),
72+
new Pair(0, Color.GREEN)
73+
});
74+
75+
mapboxMap.setStyle(new Style.Builder().fromUri(Style.MAPBOX_STREETS), style -> {
76+
symbolManager = new SymbolManager(mapView, mapboxMap, style, null, clusterOptions);
77+
symbolManager.setIconAllowOverlap(true);
78+
loadData();
79+
});
80+
}
81+
82+
private void loadData() {
83+
int amount = 10000;
84+
if (locations == null) {
85+
new LoadLocationTask(this, amount).execute();
86+
} else {
87+
showMarkers(amount);
88+
}
89+
}
90+
91+
private void onLatLngListLoaded(FeatureCollection featureCollection, int amount) {
92+
locations = featureCollection;
93+
showMarkers(amount);
94+
}
95+
96+
private void showMarkers(int amount) {
97+
if (mapboxMap == null || locations == null || locations.features() == null || mapView.isDestroyed()) {
98+
return;
99+
}
100+
// delete old symbols
101+
symbolManager.delete(symbols);
102+
if (locations.features().size() < amount) {
103+
amount = locations.features().size();
104+
}
105+
106+
showSymbols(amount);
107+
}
108+
109+
private void showSymbols(int amount) {
110+
List<SymbolOptions> options = new ArrayList<>();
111+
Random random = new Random();
112+
int randomIndex;
113+
114+
List<Feature> features = locations.features();
115+
if (features == null) {
116+
return;
117+
}
118+
119+
for (int i = 0; i < amount; i++) {
120+
randomIndex = random.nextInt(features.size());
121+
Feature feature = features.get(randomIndex);
122+
options.add(new SymbolOptions()
123+
.withGeometry((Point) feature.geometry())
124+
.withIconImage("fire-station-11")
125+
);
126+
}
127+
symbols = symbolManager.create(options);
128+
}
129+
130+
@Override
131+
protected void onStart() {
132+
super.onStart();
133+
mapView.onStart();
134+
}
135+
136+
@Override
137+
protected void onResume() {
138+
super.onResume();
139+
mapView.onResume();
140+
}
141+
142+
@Override
143+
protected void onPause() {
144+
super.onPause();
145+
mapView.onPause();
146+
}
147+
148+
@Override
149+
protected void onStop() {
150+
super.onStop();
151+
mapView.onStop();
152+
}
153+
154+
@Override
155+
protected void onSaveInstanceState(Bundle outState) {
156+
super.onSaveInstanceState(outState);
157+
mapView.onSaveInstanceState(outState);
158+
}
159+
160+
@Override
161+
protected void onDestroy() {
162+
super.onDestroy();
163+
164+
if (symbolManager != null) {
165+
symbolManager.onDestroy();
166+
}
167+
168+
mapView.onDestroy();
169+
}
170+
171+
@Override
172+
public void onLowMemory() {
173+
super.onLowMemory();
174+
mapView.onLowMemory();
175+
}
176+
177+
private static class LoadLocationTask extends AsyncTask<Void, Integer, FeatureCollection> {
178+
179+
private final WeakReference<ClusterSymbolActivity> activity;
180+
private final int amount;
181+
182+
private LoadLocationTask(ClusterSymbolActivity activity, int amount) {
183+
this.amount = amount;
184+
this.activity = new WeakReference<>(activity);
185+
}
186+
187+
@Override
188+
protected FeatureCollection doInBackground(Void... params) {
189+
ClusterSymbolActivity activity = this.activity.get();
190+
if (activity != null) {
191+
String json = null;
192+
try {
193+
json = GeoParseUtil.loadStringFromAssets(activity.getApplicationContext());
194+
} catch (IOException exception) {
195+
Timber.e(exception, "Could not add markers");
196+
}
197+
198+
if (json != null) {
199+
return FeatureCollection.fromJson(json);
200+
}
201+
}
202+
return null;
203+
}
204+
205+
@Override
206+
protected void onPostExecute(FeatureCollection locations) {
207+
super.onPostExecute(locations);
208+
ClusterSymbolActivity activity = this.activity.get();
209+
if (activity != null) {
210+
activity.onLatLngListLoaded(locations, amount);
211+
}
212+
}
213+
}
214+
215+
public static class GeoParseUtil {
216+
217+
static String loadStringFromAssets(final Context context) throws IOException {
218+
InputStream is = context.getAssets().open("points.geojson");
219+
BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
220+
return readAll(rd);
221+
}
222+
223+
private static String readAll(Reader rd) throws IOException {
224+
StringBuilder sb = new StringBuilder();
225+
int cp;
226+
while ((cp = rd.read()) != -1) {
227+
sb.append((char) cp);
228+
}
229+
return sb.toString();
230+
}
231+
}
232+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.constraintlayout.widget.ConstraintLayout
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
xmlns:app="http://schemas.android.com/apk/res-auto"
5+
android:id="@+id/coordinator_layout"
6+
android:layout_width="match_parent"
7+
android:layout_height="match_parent">
8+
9+
<com.mapbox.mapboxsdk.maps.MapView
10+
android:id="@+id/mapView"
11+
android:layout_width="match_parent"
12+
android:layout_height="match_parent"/>
13+
14+
</androidx.constraintlayout.widget.ConstraintLayout>

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<string name="title_symbol">Symbol</string>
2828
<string name="title_symbol_press">Press for Symbol</string>
2929
<string name="title_symbol_bulk">Bulk amount of Symbols</string>
30+
<string name="title_symbol_cluster">Cluster Symbols</string>
3031
<string name="title_symbol_change">Change Symbol</string>
3132
<string name="title_fill">Fill</string>
3233
<string name="title_fill_change">Change Fill</string>
@@ -51,6 +52,7 @@
5152
<string name="description_symbol_press">Show a symbol by pressing the map</string>
5253
<string name="description_symbol_change">Dynamically change a symbol on the map</string>
5354
<string name="description_symbol_bulk">Show fire hydrants in Washington DC area</string>
55+
<string name="description_symbol_cluster">Show fire hydrants in Washington DC area in a cluster.</string>
5456
<string name="description_line">Show lines on a map</string>
5557
<string name="description_line_change">Show changing lines on a map</string>
5658
<string name="description_fill">Show fills on a map</string>

gradle/dependencies.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
]
88

99
version = [
10-
mapboxMapSdk : '9.5.0',
10+
mapboxMapSdk : '9.6.0',
1111
mapboxJava : '5.5.0',
1212
mapboxTurf : '5.5.0',
1313
playLocation : '16.0.0',

plugin-annotation/scripts/annotation_element_provider.java.ejs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ class <%- camelize(type) %>ElementProvider implements CoreElementProvider<<%- ca
3737
return layerId;
3838
}
3939

40+
@Override
41+
public String getSourceId() {
42+
return sourceId;
43+
}
44+
4045
@Override
4146
public <%- camelize(type) %>Layer getLayer() {
4247
return new <%- camelize(type) %>Layer(layerId, sourceId);

plugin-annotation/scripts/annotation_manager.java.ejs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public class <%- camelize(type) %>Manager extends AnnotationManager<<%- camelize
4949
*/
5050
@UiThread
5151
public <%- camelize(type) %>Manager(@NonNull MapView mapView, @NonNull MapboxMap mapboxMap, @NonNull Style style) {
52-
this(mapView, mapboxMap, style, null, null);
52+
this(mapView, mapboxMap, style, null, (GeoJsonOptions) null);
5353
}
5454

5555
/**
@@ -61,7 +61,7 @@ public class <%- camelize(type) %>Manager extends AnnotationManager<<%- camelize
6161
*/
6262
@UiThread
6363
public <%- camelize(type) %>Manager(@NonNull MapView mapView, @NonNull MapboxMap mapboxMap, @NonNull Style style, @Nullable String belowLayerId) {
64-
this(mapView, mapboxMap, style, belowLayerId, null);
64+
this(mapView, mapboxMap, style, belowLayerId, (GeoJsonOptions) null);
6565
}
6666

6767
/**
@@ -76,6 +76,22 @@ public class <%- camelize(type) %>Manager extends AnnotationManager<<%- camelize
7676
public <%- camelize(type) %>Manager(@NonNull MapView mapView, @NonNull MapboxMap mapboxMap, @NonNull Style style, @Nullable String belowLayerId, @Nullable GeoJsonOptions geoJsonOptions) {
7777
this(mapView, mapboxMap, style, new <%- camelize(type) %>ElementProvider(), belowLayerId, geoJsonOptions, DraggableAnnotationController.getInstance(mapView, mapboxMap));
7878
}
79+
<% if (type === "symbol") { -%>
80+
81+
/**
82+
* Create a <%- type %> manager, used to manage <%- type %>s.
83+
*
84+
* @param mapboxMap the map object to add <%- type %>s to
85+
* @param style a valid a fully loaded style object
86+
* @param belowLayerId the id of the layer above the circle layer
87+
* @param clusterOptions options for the clustering configuration
88+
*/
89+
@UiThread
90+
public <%- camelize(type) %>Manager(@NonNull MapView mapView, @NonNull MapboxMap mapboxMap, @NonNull Style style, @Nullable String belowLayerId, @NonNull ClusterOptions clusterOptions) {
91+
this(mapView, mapboxMap, style, new SymbolElementProvider(), belowLayerId, new GeoJsonOptions().withCluster(true).withClusterRadius(clusterOptions.getClusterRadius()).withClusterMaxZoom(clusterOptions.getClusterMaxZoom()), DraggableAnnotationController.getInstance(mapView, mapboxMap));
92+
clusterOptions.apply(style, coreElementProvider.getSourceId());
93+
}
94+
<% } -%>
7995

8096
@VisibleForTesting
8197
<%- camelize(type) %>Manager(@NonNull MapView mapView, @NonNull MapboxMap mapboxMap, @NonNull Style style, @NonNull CoreElementProvider<<%- camelize(type) %>Layer> coreElementProvider, @Nullable String belowLayerId, @Nullable GeoJsonOptions geoJsonOptions, DraggableAnnotationController draggableAnnotationController) {

plugin-annotation/src/main/java/com/mapbox/mapboxsdk/plugins/annotation/AnnotationManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ public abstract class AnnotationManager<
5757
private long currentId;
5858

5959
protected L layer;
60-
private GeoJsonSource geoJsonSource;
60+
protected GeoJsonSource geoJsonSource;
6161
private final MapClickResolver mapClickResolver;
6262
private Style style;
6363
private String belowLayerId;
64-
private CoreElementProvider<L> coreElementProvider;
64+
protected CoreElementProvider<L> coreElementProvider;
6565
private DraggableAnnotationController draggableAnnotationController;
6666

6767
@UiThread

plugin-annotation/src/main/java/com/mapbox/mapboxsdk/plugins/annotation/CircleElementProvider.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ public String getLayerId() {
3232
return layerId;
3333
}
3434

35+
@Override
36+
public String getSourceId() {
37+
return sourceId;
38+
}
39+
3540
@Override
3641
public CircleLayer getLayer() {
3742
return new CircleLayer(layerId, sourceId);

plugin-annotation/src/main/java/com/mapbox/mapboxsdk/plugins/annotation/CircleManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class CircleManager extends AnnotationManager<CircleLayer, Circle, Circle
4343
*/
4444
@UiThread
4545
public CircleManager(@NonNull MapView mapView, @NonNull MapboxMap mapboxMap, @NonNull Style style) {
46-
this(mapView, mapboxMap, style, null, null);
46+
this(mapView, mapboxMap, style, null, (GeoJsonOptions) null);
4747
}
4848

4949
/**
@@ -55,7 +55,7 @@ public CircleManager(@NonNull MapView mapView, @NonNull MapboxMap mapboxMap, @No
5555
*/
5656
@UiThread
5757
public CircleManager(@NonNull MapView mapView, @NonNull MapboxMap mapboxMap, @NonNull Style style, @Nullable String belowLayerId) {
58-
this(mapView, mapboxMap, style, belowLayerId, null);
58+
this(mapView, mapboxMap, style, belowLayerId, (GeoJsonOptions) null);
5959
}
6060

6161
/**

0 commit comments

Comments
 (0)