Skip to content

Commit

Permalink
Demo clustering with ViewModel (#506)
Browse files Browse the repository at this point in the history
* Differentiate between initial start and configuration change restore

Only move the camera to the starting location when not restoring

* Add removeItems() to ClusterManager and Algorithm

Avoid locking and unlocking the algorithm in a tight loop when removing many cluster items

* Move algorithm lock from ClusterManager to Algorithm

Allow the algorithm to handle its own locking, so it can be reused in another ClusterManager instance. Also allow adding/removing items with proper locking outside of ClusterManager. For best ViewModel support.
  • Loading branch information
jeffdgr8 authored and jpoehnelt committed Oct 21, 2019
1 parent 75a2956 commit bb3587f
Show file tree
Hide file tree
Showing 13 changed files with 221 additions and 30 deletions.
1 change: 1 addition & 0 deletions demo/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies {

implementation 'com.google.android.gms:play-services-maps:17.0.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
}

task startDemo(type: Exec) {
Expand Down
1 change: 1 addition & 0 deletions demo/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
<activity android:name=".BigClusteringDemoActivity" />
<activity android:name=".VisibleClusteringDemoActivity" />
<activity android:name=".CustomMarkerClusteringDemoActivity" />
<activity android:name=".ClusteringViewModelDemoActivity"/>
<activity android:name=".TileProviderAndProjectionDemo" />
<activity android:name=".HeatmapsDemoActivity" />
<activity android:name=".HeatmapsPlacesDemoActivity" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2019 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.maps.android.utils.demo;

import androidx.lifecycle.ViewModel;
import android.content.res.Resources;

import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.clustering.algo.NonHierarchicalViewBasedAlgorithm;
import com.google.maps.android.utils.demo.model.MyItem;

import org.json.JSONException;

import java.io.InputStream;
import java.util.List;

public class ClusteringViewModel extends ViewModel {

private NonHierarchicalViewBasedAlgorithm<MyItem> mAlgorithm = new NonHierarchicalViewBasedAlgorithm<>(0, 0);

NonHierarchicalViewBasedAlgorithm<MyItem> getAlgorithm() {
return mAlgorithm;
}

void readItems(Resources resources) throws JSONException {
InputStream inputStream = resources.openRawResource(R.raw.radar_search);
List<MyItem> items = new MyItemReader().read(inputStream);
mAlgorithm.lock();
try {
for (int i = 0; i < 100; i++) {
double offset = i / 60d;
for (MyItem item : items) {
LatLng position = item.getPosition();
double lat = position.latitude + offset;
double lng = position.longitude + offset;
MyItem offsetItem = new MyItem(lat, lng);
mAlgorithm.addItem(offsetItem);
}
}
} finally {
mAlgorithm.unlock();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2019 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.maps.android.utils.demo;

import androidx.lifecycle.ViewModelProviders;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.widget.Toast;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.clustering.ClusterManager;
import com.google.maps.android.utils.demo.model.MyItem;

import org.json.JSONException;

public class ClusteringViewModelDemoActivity extends BaseDemoActivity {
private ClusterManager<MyItem> mClusterManager;
private ClusteringViewModel mViewModel;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewModel = ViewModelProviders.of(this).get(ClusteringViewModel.class);
if (savedInstanceState == null) {
try {
mViewModel.readItems(getResources());
} catch (JSONException e) {
Toast.makeText(this, "Problem reading list of markers.", Toast.LENGTH_LONG).show();
}
}
}

@Override
protected void startDemo(boolean isRestore) {
if (!isRestore) {
getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 10));
}

DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
mViewModel.getAlgorithm().updateViewSize(metrics.widthPixels, metrics.heightPixels);

mClusterManager = new ClusterManager<>(this, getMap());
mClusterManager.setAlgorithm(mViewModel.getAlgorithm());

getMap().setOnCameraIdleListener(mClusterManager);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ protected void onCreate(Bundle savedInstanceState) {
addDemo("Clustering", ClusteringDemoActivity.class);
addDemo("Clustering: Custom Look", CustomMarkerClusteringDemoActivity.class);
addDemo("Clustering: 2K markers", BigClusteringDemoActivity.class);
addDemo("Clustering: 20k only visible markers", VisibleClusteringDemoActivity.class);
addDemo("Clustering: 20K only visible markers", VisibleClusteringDemoActivity.class);
addDemo("Clustering: ViewModel", ClusteringViewModelDemoActivity.class);
addDemo("PolyUtil.decode", PolyDecodeDemoActivity.class);
addDemo("PolyUtil.simplify", PolySimplifyDemoActivity.class);
addDemo("IconGenerator", IconGeneratorDemoActivity.class);
Expand Down
6 changes: 3 additions & 3 deletions demo/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
<string name="button_send">Go!</string>
<string name="police_stations">Police Stations in Victoria</string>
<string name="medicare">Medicare Offices</string>
<string name="attrib_format">Data from &lt;a href = "%s">data.gov.au&lt;/a>, modified under &lt;a href = \"http://creativecommons.org/licenses/by/3.0/au/\">CC BY 3.0 AU&lt;/a></string>
<string name="police_stations_url">http://data.gov.au/dataset/police-station-locations/resource/76110b24-0f0a-4c29-9583-3f99eaba486c</string>
<string name="medicare_url">http://data.gov.au/dataset/location-of-medicare-offices/resource/5d38e1be-4011-49c4-8b8b-75405eeb1088</string>
<string name="attrib_format">Data from &lt;a href = "%s">data.gov.au&lt;/a>, modified under &lt;a href = \"https://creativecommons.org/licenses/by/3.0/au/\">CC BY 3.0 AU&lt;/a></string>
<string name="police_stations_url">https://data.gov.au/dataset/police-station-locations/resource/76110b24-0f0a-4c29-9583-3f99eaba486c</string>
<string name="medicare_url">https://data.gov.au/dataset/location-of-medicare-offices/resource/5d38e1be-4011-49c4-8b8b-75405eeb1088</string>
<string name="kml_url">https://googlemaps.github.io/kml-samples/morekml/Polygons/Polygons.Google_Campus.kml</string>
<string name="geojson_url">https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson</string>
<string-array name="heatmaps_datasets_array">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ public class ClusterManager<T extends ClusterItem> implements
private final MarkerManager.Collection mClusterMarkers;

private ScreenBasedAlgorithm<T> mAlgorithm;
private final ReadWriteLock mAlgorithmLock = new ReentrantReadWriteLock();
private ClusterRenderer<T> mRenderer;

private GoogleMap mMap;
Expand Down Expand Up @@ -118,15 +117,15 @@ public void setAlgorithm(Algorithm<T> algorithm) {
}

public void setAlgorithm(ScreenBasedAlgorithm<T> algorithm) {
mAlgorithmLock.writeLock().lock();
algorithm.lock();
try {
if (mAlgorithm != null) {
algorithm.addItems(mAlgorithm.getItems());
}

mAlgorithm = algorithm;
} finally {
mAlgorithmLock.writeLock().unlock();
algorithm.unlock();
}

if (mAlgorithm.shouldReclusterOnMapMovement()) {
Expand All @@ -149,39 +148,47 @@ public Algorithm<T> getAlgorithm() {
}

public void clearItems() {
mAlgorithmLock.writeLock().lock();
mAlgorithm.lock();
try {
mAlgorithm.clearItems();
} finally {
mAlgorithmLock.writeLock().unlock();
mAlgorithm.unlock();
}
}

public void addItems(Collection<T> items) {
mAlgorithmLock.writeLock().lock();
mAlgorithm.lock();
try {
mAlgorithm.addItems(items);
} finally {
mAlgorithmLock.writeLock().unlock();
mAlgorithm.unlock();
}

}

public void addItem(T myItem) {
mAlgorithmLock.writeLock().lock();
mAlgorithm.lock();
try {
mAlgorithm.addItem(myItem);
} finally {
mAlgorithmLock.writeLock().unlock();
mAlgorithm.unlock();
}
}

public void removeItems(Collection<T> items) {
mAlgorithm.lock();
try {
mAlgorithm.removeItems(items);
} finally {
mAlgorithm.unlock();
}
}

public void removeItem(T item) {
mAlgorithmLock.writeLock().lock();
mAlgorithm.lock();
try {
mAlgorithm.removeItem(item);
} finally {
mAlgorithmLock.writeLock().unlock();
mAlgorithm.unlock();
}
}

Expand Down Expand Up @@ -238,11 +245,11 @@ public void onInfoWindowClick(Marker marker) {
private class ClusterTask extends AsyncTask<Float, Void, Set<? extends Cluster<T>>> {
@Override
protected Set<? extends Cluster<T>> doInBackground(Float... zoom) {
mAlgorithmLock.readLock().lock();
mAlgorithm.lock();
try {
return mAlgorithm.getClusters(zoom[0]);
} finally {
mAlgorithmLock.readLock().unlock();
mAlgorithm.unlock();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.google.maps.android.clustering.algo;

import com.google.maps.android.clustering.ClusterItem;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
* Base Algorithm class that implements lock/unlock functionality.
*/
public abstract class AbstractAlgorithm<T extends ClusterItem> implements Algorithm<T> {

private final ReadWriteLock mLock = new ReentrantReadWriteLock();

@Override
public void lock() {
mLock.writeLock().lock();
}

@Override
public void unlock() {
mLock.writeLock().unlock();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
* Logic for computing clusters
*/
public interface Algorithm<T extends ClusterItem> {

void addItem(T item);

void addItems(Collection<T> items);
Expand All @@ -34,11 +35,17 @@ public interface Algorithm<T extends ClusterItem> {

void removeItem(T item);

void removeItems(Collection<T> items);

Set<? extends Cluster<T>> getClusters(float zoom);

Collection<T> getItems();

void setMaxDistanceBetweenClusteredItems(int maxDistance);

int getMaxDistanceBetweenClusteredItems();

void lock();

void unlock();
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
/**
* Groups markers into a grid.
*/
public class GridBasedAlgorithm<T extends ClusterItem> implements Algorithm<T> {
public class GridBasedAlgorithm<T extends ClusterItem> extends AbstractAlgorithm<T> {
private static final int DEFAULT_GRID_SIZE = 100;

private int mGridSize = DEFAULT_GRID_SIZE;
Expand All @@ -58,6 +58,11 @@ public void removeItem(T item) {
mItems.remove(item);
}

@Override
public void removeItems(Collection<T> items) {
mItems.removeAll(items);
}

@Override
public void setMaxDistanceBetweenClusteredItems(int maxDistance) {
mGridSize = maxDistance;
Expand Down
Loading

0 comments on commit bb3587f

Please sign in to comment.