From e3926d5a9ad1a766f99f155849e7b54b0bd43999 Mon Sep 17 00:00:00 2001 From: "israel@cobaas.com" Date: Sun, 9 Jul 2017 22:30:52 +0200 Subject: [PATCH 01/12] Added new demo activity showing 5 markers in the same location. --- demo/AndroidManifest.xml | 1 + demo/res/raw/markers_same_location.json | 7 ++++ .../demo/ClusteringSameLocationActivity.java | 38 +++++++++++++++++++ .../maps/android/utils/demo/MainActivity.java | 1 + 4 files changed, 47 insertions(+) create mode 100644 demo/res/raw/markers_same_location.json create mode 100644 demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java diff --git a/demo/AndroidManifest.xml b/demo/AndroidManifest.xml index a22e10dc4..f849c0cbe 100644 --- a/demo/AndroidManifest.xml +++ b/demo/AndroidManifest.xml @@ -56,6 +56,7 @@ + diff --git a/demo/res/raw/markers_same_location.json b/demo/res/raw/markers_same_location.json new file mode 100644 index 000000000..1b5c33026 --- /dev/null +++ b/demo/res/raw/markers_same_location.json @@ -0,0 +1,7 @@ +[ +{ "lat" : 51.503186, "lng" : -0.126446, "title" : "Marker 1", "snippet": "Marker 1"}, +{ "lat" : 51.503186, "lng" : -0.126446, "title" : "Marker 2", "snippet" : "Marker 2"}, +{ "lat" : 51.503186, "lng" : -0.126446, "title" : "Marker 3", "snippet": "Marker 3"}, +{ "lat" : 51.503186, "lng" : -0.126446, "title" : "Marker 4", "snippet": "Marker 4" }, +{ "lat" : 51.503186, "lng" : -0.126446, "title" : "Marker 5", "snippet": "Marker 5"} +] \ No newline at end of file diff --git a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java new file mode 100644 index 000000000..ef0557f2a --- /dev/null +++ b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java @@ -0,0 +1,38 @@ +package com.google.maps.android.utils.demo; + +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; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class ClusteringSameLocationActivity extends BaseDemoActivity { + private ClusterManager mClusterManager; + + @Override + protected void startDemo() { + getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 10)); + + mClusterManager = new ClusterManager(this, getMap()); + getMap().setOnCameraIdleListener(mClusterManager); + + try { + readItems(); + } catch (JSONException e) { + Toast.makeText(this, "Problem reading list of markers.", Toast.LENGTH_LONG).show(); + } + } + + private void readItems() throws JSONException { + InputStream inputStream = getResources().openRawResource(R.raw.markers_same_location); + List items = new MyItemReader().read(inputStream); + mClusterManager.addItems(items); + } +} diff --git a/demo/src/com/google/maps/android/utils/demo/MainActivity.java b/demo/src/com/google/maps/android/utils/demo/MainActivity.java index 7f8a7f7fe..2800cb648 100644 --- a/demo/src/com/google/maps/android/utils/demo/MainActivity.java +++ b/demo/src/com/google/maps/android/utils/demo/MainActivity.java @@ -38,6 +38,7 @@ protected void onCreate(Bundle savedInstanceState) { addDemo("Clustering", ClusteringDemoActivity.class); addDemo("Clustering: Custom Look", CustomMarkerClusteringDemoActivity.class); addDemo("Clustering: 2K markers", BigClusteringDemoActivity.class); + addDemo("Clustering: Markers in same location", ClusteringSameLocationActivity.class); addDemo("PolyUtil.decode", PolyDecodeDemoActivity.class); addDemo("PolyUtil.simplify", PolySimplifyDemoActivity.class); addDemo("IconGenerator", IconGeneratorDemoActivity.class); From e4f4e2b13415a4751b9dfa7a813387a4cec20baf Mon Sep 17 00:00:00 2001 From: "israel@cobaas.com" Date: Mon, 17 Jul 2017 23:48:48 +0200 Subject: [PATCH 02/12] Added dummy OnClusterClickListener. --- .../demo/ClusteringSameLocationActivity.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java index ef0557f2a..e0d3044ef 100644 --- a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java +++ b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java @@ -4,13 +4,13 @@ import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.model.LatLng; +import com.google.maps.android.clustering.Cluster; import com.google.maps.android.clustering.ClusterManager; import com.google.maps.android.utils.demo.model.MyItem; import org.json.JSONException; import java.io.InputStream; -import java.util.ArrayList; import java.util.List; public class ClusteringSameLocationActivity extends BaseDemoActivity { @@ -20,11 +20,23 @@ public class ClusteringSameLocationActivity extends BaseDemoActivity { protected void startDemo() { getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 10)); - mClusterManager = new ClusterManager(this, getMap()); - getMap().setOnCameraIdleListener(mClusterManager); + mClusterManager = new ClusterManager<>(this, getMap()); + + getMap().setOnMarkerClickListener(mClusterManager); + + mClusterManager.setOnClusterClickListener(new ClusterManager.OnClusterClickListener() { + + @Override + public boolean onClusterClick(Cluster cluster) { + + return false; + } + }); try { readItems(); + + mClusterManager.cluster(); } catch (JSONException e) { Toast.makeText(this, "Problem reading list of markers.", Toast.LENGTH_LONG).show(); } From 7784a78abaaafed913e29e3635aa415ce62ef379 Mon Sep 17 00:00:00 2001 From: "israel@cobaas.com" Date: Fri, 21 Jul 2017 16:37:36 +0200 Subject: [PATCH 03/12] Adding logic to detect if markers in a cluster share the same location. --- .../demo/ClusteringSameLocationActivity.java | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java index e0d3044ef..a0b166a2f 100644 --- a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java +++ b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java @@ -1,26 +1,33 @@ package com.google.maps.android.utils.demo; +import android.content.Context; +import android.util.Log; import android.widget.Toast; import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.LatLng; +import com.google.maps.android.MarkerManager; import com.google.maps.android.clustering.Cluster; +import com.google.maps.android.clustering.ClusterItem; import com.google.maps.android.clustering.ClusterManager; import com.google.maps.android.utils.demo.model.MyItem; import org.json.JSONException; import java.io.InputStream; +import java.util.LinkedList; import java.util.List; public class ClusteringSameLocationActivity extends BaseDemoActivity { - private ClusterManager mClusterManager; + + private CustomClusterManager mClusterManager; @Override protected void startDemo() { getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 10)); - mClusterManager = new ClusterManager<>(this, getMap()); + mClusterManager = new CustomClusterManager<>(this, getMap()); getMap().setOnMarkerClickListener(mClusterManager); @@ -28,8 +35,21 @@ protected void startDemo() { @Override public boolean onClusterClick(Cluster cluster) { + float maxZoomLevel = getMap().getMaxZoomLevel(); + float currentZoomLevel = getMap().getCameraPosition().zoom; + + // only show markers if users is in the max zoom level + if (currentZoomLevel != maxZoomLevel) { + return false; + } + + if (!mClusterManager.hasMarkersSameLocation(cluster)) { + return false; + } + + // TODO: show markers included in cluster with the same location - return false; + return true; } }); @@ -47,4 +67,31 @@ private void readItems() throws JSONException { List items = new MyItemReader().read(inputStream); mClusterManager.addItems(items); } + + class CustomClusterManager extends ClusterManager { + + public CustomClusterManager(Context context, GoogleMap map) { + super(context, map); + } + + public CustomClusterManager(Context context, GoogleMap map, MarkerManager markerManager) { + super(context, map, markerManager); + } + + public boolean hasMarkersSameLocation(Cluster cluster) { + LinkedList items = new LinkedList<>(cluster.getItems()); + T item = items.remove(0); + + double longitude = item.getPosition().longitude; + double latitude = item.getPosition().latitude; + + for (T t : items) { + if (longitude != t.getPosition().longitude && latitude != t.getPosition().latitude) { + return false; + } + } + + return true; + } + } } From a34d45f387bbd7957355e4570b52d5ce44d20095 Mon Sep 17 00:00:00 2001 From: "israel@cobaas.com" Date: Fri, 21 Jul 2017 17:20:08 +0200 Subject: [PATCH 04/12] Added a custom Info Window to the cluster when it contains markers with the same location. --- .../utils/demo/ClusteringSameLocationActivity.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java index a0b166a2f..206e7c04c 100644 --- a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java +++ b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java @@ -1,16 +1,17 @@ package com.google.maps.android.utils.demo; import android.content.Context; -import android.util.Log; import android.widget.Toast; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.Marker; import com.google.maps.android.MarkerManager; import com.google.maps.android.clustering.Cluster; import com.google.maps.android.clustering.ClusterItem; import com.google.maps.android.clustering.ClusterManager; +import com.google.maps.android.clustering.view.DefaultClusterRenderer; import com.google.maps.android.utils.demo.model.MyItem; import org.json.JSONException; @@ -47,7 +48,9 @@ public boolean onClusterClick(Cluster cluster) { return false; } - // TODO: show markers included in cluster with the same location + Marker marker = ((DefaultClusterRenderer) mClusterManager.getRenderer()).getMarker(cluster); + marker.setTitle("Cluster with " + cluster.getItems().size() + " items"); + marker.showInfoWindow(); return true; } @@ -68,9 +71,9 @@ private void readItems() throws JSONException { mClusterManager.addItems(items); } - class CustomClusterManager extends ClusterManager { + private class CustomClusterManager extends ClusterManager { - public CustomClusterManager(Context context, GoogleMap map) { + CustomClusterManager(Context context, GoogleMap map) { super(context, map); } @@ -78,7 +81,7 @@ public CustomClusterManager(Context context, GoogleMap map, MarkerManager marker super(context, map, markerManager); } - public boolean hasMarkersSameLocation(Cluster cluster) { + boolean hasMarkersSameLocation(Cluster cluster) { LinkedList items = new LinkedList<>(cluster.getItems()); T item = items.remove(0); From 4ac29d93147bac0d492276a29867fbdbd33f7558 Mon Sep 17 00:00:00 2001 From: "israel@cobaas.com" Date: Thu, 27 Jul 2017 18:49:51 +0200 Subject: [PATCH 05/12] Added a logic to distribute items in the same location around the real location when user taps in the cluster and the zoom is the maximum allowed. Cluster gets back to previous state when user zooms out the map. --- .../demo/ClusteringSameLocationActivity.java | 59 +++++++++++++++++-- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java index 206e7c04c..d6ddb38af 100644 --- a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java +++ b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java @@ -6,24 +6,39 @@ import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.LatLng; -import com.google.android.gms.maps.model.Marker; import com.google.maps.android.MarkerManager; import com.google.maps.android.clustering.Cluster; import com.google.maps.android.clustering.ClusterItem; import com.google.maps.android.clustering.ClusterManager; -import com.google.maps.android.clustering.view.DefaultClusterRenderer; import com.google.maps.android.utils.demo.model.MyItem; import org.json.JSONException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; public class ClusteringSameLocationActivity extends BaseDemoActivity { + private static final double DEFAULT_RADIUS = 0.00003; + + private static final String DEFAULT_DELETE_LIST = "itemsDeleted"; + + private static final String DEFAULT_ADDED_LIST = "itemsAdded"; + private CustomClusterManager mClusterManager; + private Map> mItemsCache; + + public ClusteringSameLocationActivity() { + mItemsCache = new HashMap<>(); + mItemsCache.put(DEFAULT_ADDED_LIST, new ArrayList()); + mItemsCache.put(DEFAULT_DELETE_LIST, new ArrayList()); + } + @Override protected void startDemo() { getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 10)); @@ -31,6 +46,21 @@ protected void startDemo() { mClusterManager = new CustomClusterManager<>(this, getMap()); getMap().setOnMarkerClickListener(mClusterManager); + getMap().setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() { + + @Override + public void onCameraMove() { + + if (getMap().getCameraPosition().zoom < getMap().getMaxZoomLevel()) { + mClusterManager.removeItems(mItemsCache.get(DEFAULT_ADDED_LIST)); + mClusterManager.addItems(mItemsCache.get(DEFAULT_DELETE_LIST)); + mClusterManager.cluster(); + + mItemsCache.get(DEFAULT_ADDED_LIST).clear(); + mItemsCache.get(DEFAULT_DELETE_LIST).clear(); + } + } + }); mClusterManager.setOnClusterClickListener(new ClusterManager.OnClusterClickListener() { @@ -48,9 +78,21 @@ public boolean onClusterClick(Cluster cluster) { return false; } - Marker marker = ((DefaultClusterRenderer) mClusterManager.getRenderer()).getMarker(cluster); - marker.setTitle("Cluster with " + cluster.getItems().size() + " items"); - marker.showInfoWindow(); + // distribute the markers around the center + int counter = 0; + float rotateFactor = (360 / cluster.getItems().size()); + for (MyItem item : cluster.getItems()) { + double lat = item.getPosition().latitude + (DEFAULT_RADIUS * Math.cos(++counter * rotateFactor)); + double lng = item.getPosition().longitude + (DEFAULT_RADIUS * Math.sin(counter * rotateFactor)); + MyItem copy = new MyItem(lat, lng, item.getTitle(), item.getSnippet()); + + mClusterManager.removeItem(item); + mClusterManager.addItem(copy); + mClusterManager.cluster(); + + mItemsCache.get(DEFAULT_ADDED_LIST).add(copy); + mItemsCache.get(DEFAULT_DELETE_LIST).add(item); + } return true; } @@ -96,5 +138,12 @@ boolean hasMarkersSameLocation(Cluster cluster) { return true; } + + void removeItems(List items) { + + for (T item : items) { + removeItem(item); + } + } } } From a97896092d7ea10dc67b3d39ecbf0cd90c340aea Mon Sep 17 00:00:00 2001 From: "israel@cobaas.com" Date: Fri, 28 Jul 2017 01:05:55 +0200 Subject: [PATCH 06/12] Refactoring the main activity and classes, basically names and comments. --- .../utils/demo/ClusteringSameLocationActivity.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java index d6ddb38af..607f9be9a 100644 --- a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java +++ b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java @@ -51,6 +51,7 @@ protected void startDemo() { @Override public void onCameraMove() { + // get markesr back to the original position if they were relocated if (getMap().getCameraPosition().zoom < getMap().getMaxZoomLevel()) { mClusterManager.removeItems(mItemsCache.get(DEFAULT_ADDED_LIST)); mClusterManager.addItems(mItemsCache.get(DEFAULT_DELETE_LIST)); @@ -74,11 +75,11 @@ public boolean onClusterClick(Cluster cluster) { return false; } - if (!mClusterManager.hasMarkersSameLocation(cluster)) { + if (!mClusterManager.itemsInSameLocation(cluster)) { return false; } - // distribute the markers around the center + // relocate the markers around the current markers position int counter = 0; float rotateFactor = (360 / cluster.getItems().size()); for (MyItem item : cluster.getItems()) { @@ -110,6 +111,7 @@ public boolean onClusterClick(Cluster cluster) { private void readItems() throws JSONException { InputStream inputStream = getResources().openRawResource(R.raw.markers_same_location); List items = new MyItemReader().read(inputStream); + mClusterManager.addItems(items); } @@ -119,11 +121,7 @@ private class CustomClusterManager extends ClusterManager super(context, map); } - public CustomClusterManager(Context context, GoogleMap map, MarkerManager markerManager) { - super(context, map, markerManager); - } - - boolean hasMarkersSameLocation(Cluster cluster) { + boolean itemsInSameLocation(Cluster cluster) { LinkedList items = new LinkedList<>(cluster.getItems()); T item = items.remove(0); From 175e7a016c4318f4fa6b48c766e6c2f9031249bf Mon Sep 17 00:00:00 2001 From: "israel@cobaas.com" Date: Fri, 28 Jul 2017 01:17:34 +0200 Subject: [PATCH 07/12] Using Double.compare java method to compare double when checking if markes are in the same location. --- .../maps/android/utils/demo/ClusteringSameLocationActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java index 607f9be9a..9f833023c 100644 --- a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java +++ b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java @@ -129,7 +129,7 @@ boolean itemsInSameLocation(Cluster cluster) { double latitude = item.getPosition().latitude; for (T t : items) { - if (longitude != t.getPosition().longitude && latitude != t.getPosition().latitude) { + if (Double.compare(longitude, t.getPosition().longitude) != 0 && Double.compare(latitude, t.getPosition().latitude) != 0) { return false; } } From 2e62ead790300704d8403f90582761790e161f8c Mon Sep 17 00:00:00 2001 From: menismu Date: Tue, 1 Aug 2017 17:12:27 +0200 Subject: [PATCH 08/12] Added a method in ClusterManager which checks if the items in a cluster have the same lat/lng. Added to the demo activity ClusteringSameLocationActivity the use of this new method. --- .../demo/ClusteringSameLocationActivity.java | 16 ------------ .../android/clustering/ClusterManager.java | 25 +++++++++++++++++++ 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java index 9f833023c..508968a77 100644 --- a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java +++ b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java @@ -121,22 +121,6 @@ private class CustomClusterManager extends ClusterManager super(context, map); } - boolean itemsInSameLocation(Cluster cluster) { - LinkedList items = new LinkedList<>(cluster.getItems()); - T item = items.remove(0); - - double longitude = item.getPosition().longitude; - double latitude = item.getPosition().latitude; - - for (T t : items) { - if (Double.compare(longitude, t.getPosition().longitude) != 0 && Double.compare(latitude, t.getPosition().latitude) != 0) { - return false; - } - } - - return true; - } - void removeItems(List items) { for (T item : items) { diff --git a/library/src/com/google/maps/android/clustering/ClusterManager.java b/library/src/com/google/maps/android/clustering/ClusterManager.java index 83b5d25f1..e1725bb67 100644 --- a/library/src/com/google/maps/android/clustering/ClusterManager.java +++ b/library/src/com/google/maps/android/clustering/ClusterManager.java @@ -31,6 +31,7 @@ import com.google.maps.android.clustering.view.DefaultClusterRenderer; import java.util.Collection; +import java.util.Iterator; import java.util.Set; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -216,6 +217,30 @@ public void onInfoWindowClick(Marker marker) { getMarkerManager().onInfoWindowClick(marker); } + public boolean itemsInSameLocation(Cluster cluster) { + Collection items = cluster.getItems(); + + if (items.size() < 2) { + return false; + } + + Iterator iterator = items.iterator(); + T item = iterator.next(); + + double longitude = item.getPosition().longitude; + double latitude = item.getPosition().latitude; + + while (iterator.hasNext()) { + T t = iterator.next(); + + if (Double.compare(longitude, t.getPosition().longitude) != 0 && Double.compare(latitude, t.getPosition().latitude) != 0) { + return false; + } + } + + return true; + } + /** * Runs the clustering algorithm in a background thread, then re-paints when results come back. */ From 35ce16952b77c3775c550a9a8cb7085ddccce65e Mon Sep 17 00:00:00 2001 From: menismu Date: Tue, 1 Aug 2017 19:09:50 +0200 Subject: [PATCH 09/12] Added all the logic to distribute and collect items when a cluster has all the items in the exact same lat/lng. --- .../demo/ClusteringSameLocationActivity.java | 95 +------------------ .../maps/android/utils/demo/model/MyItem.java | 9 ++ .../maps/android/utils/demo/model/Person.java | 5 + .../maps/android/clustering/ClusterItem.java | 7 ++ .../android/clustering/ClusterManager.java | 49 ++++++++++ .../view/ClusterItemsDistributor.java | 20 ++++ .../view/DefaultClusterItemsDistributor.java | 65 +++++++++++++ .../maps/android/clustering/QuadItemTest.java | 5 + 8 files changed, 164 insertions(+), 91 deletions(-) create mode 100644 library/src/com/google/maps/android/clustering/view/ClusterItemsDistributor.java create mode 100644 library/src/com/google/maps/android/clustering/view/DefaultClusterItemsDistributor.java diff --git a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java index 508968a77..ddeb6dc43 100644 --- a/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java +++ b/demo/src/com/google/maps/android/utils/demo/ClusteringSameLocationActivity.java @@ -1,103 +1,30 @@ package com.google.maps.android.utils.demo; -import android.content.Context; +import android.util.Log; import android.widget.Toast; import com.google.android.gms.maps.CameraUpdateFactory; -import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.LatLng; -import com.google.maps.android.MarkerManager; -import com.google.maps.android.clustering.Cluster; -import com.google.maps.android.clustering.ClusterItem; import com.google.maps.android.clustering.ClusterManager; import com.google.maps.android.utils.demo.model.MyItem; import org.json.JSONException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; import java.util.List; -import java.util.Map; public class ClusteringSameLocationActivity extends BaseDemoActivity { - private static final double DEFAULT_RADIUS = 0.00003; - - private static final String DEFAULT_DELETE_LIST = "itemsDeleted"; - - private static final String DEFAULT_ADDED_LIST = "itemsAdded"; - - private CustomClusterManager mClusterManager; - - private Map> mItemsCache; - - public ClusteringSameLocationActivity() { - mItemsCache = new HashMap<>(); - mItemsCache.put(DEFAULT_ADDED_LIST, new ArrayList()); - mItemsCache.put(DEFAULT_DELETE_LIST, new ArrayList()); - } + private ClusterManager mClusterManager; @Override protected void startDemo() { getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 10)); - mClusterManager = new CustomClusterManager<>(this, getMap()); + mClusterManager = new ClusterManager<>(this, getMap()); getMap().setOnMarkerClickListener(mClusterManager); - getMap().setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() { - - @Override - public void onCameraMove() { - - // get markesr back to the original position if they were relocated - if (getMap().getCameraPosition().zoom < getMap().getMaxZoomLevel()) { - mClusterManager.removeItems(mItemsCache.get(DEFAULT_ADDED_LIST)); - mClusterManager.addItems(mItemsCache.get(DEFAULT_DELETE_LIST)); - mClusterManager.cluster(); - - mItemsCache.get(DEFAULT_ADDED_LIST).clear(); - mItemsCache.get(DEFAULT_DELETE_LIST).clear(); - } - } - }); - - mClusterManager.setOnClusterClickListener(new ClusterManager.OnClusterClickListener() { - - @Override - public boolean onClusterClick(Cluster cluster) { - float maxZoomLevel = getMap().getMaxZoomLevel(); - float currentZoomLevel = getMap().getCameraPosition().zoom; - - // only show markers if users is in the max zoom level - if (currentZoomLevel != maxZoomLevel) { - return false; - } - - if (!mClusterManager.itemsInSameLocation(cluster)) { - return false; - } - - // relocate the markers around the current markers position - int counter = 0; - float rotateFactor = (360 / cluster.getItems().size()); - for (MyItem item : cluster.getItems()) { - double lat = item.getPosition().latitude + (DEFAULT_RADIUS * Math.cos(++counter * rotateFactor)); - double lng = item.getPosition().longitude + (DEFAULT_RADIUS * Math.sin(counter * rotateFactor)); - MyItem copy = new MyItem(lat, lng, item.getTitle(), item.getSnippet()); - - mClusterManager.removeItem(item); - mClusterManager.addItem(copy); - mClusterManager.cluster(); - - mItemsCache.get(DEFAULT_ADDED_LIST).add(copy); - mItemsCache.get(DEFAULT_DELETE_LIST).add(item); - } - - return true; - } - }); + getMap().setOnCameraMoveListener(mClusterManager); try { readItems(); @@ -114,18 +41,4 @@ private void readItems() throws JSONException { mClusterManager.addItems(items); } - - private class CustomClusterManager extends ClusterManager { - - CustomClusterManager(Context context, GoogleMap map) { - super(context, map); - } - - void removeItems(List items) { - - for (T item : items) { - removeItem(item); - } - } - } } diff --git a/demo/src/com/google/maps/android/utils/demo/model/MyItem.java b/demo/src/com/google/maps/android/utils/demo/model/MyItem.java index 86abad829..43ff20088 100644 --- a/demo/src/com/google/maps/android/utils/demo/model/MyItem.java +++ b/demo/src/com/google/maps/android/utils/demo/model/MyItem.java @@ -47,6 +47,15 @@ public LatLng getPosition() { @Override public String getSnippet() { return mSnippet; } + @Override + public ClusterItem copy(double lat, double lng) { + MyItem item = new MyItem(lat, lng); + item.setSnippet(getSnippet()); + item.setTitle(getTitle()); + + return item; + } + /** * Set the title of the marker * @param title string to be set as title diff --git a/demo/src/com/google/maps/android/utils/demo/model/Person.java b/demo/src/com/google/maps/android/utils/demo/model/Person.java index e69cc3494..54d0a458a 100644 --- a/demo/src/com/google/maps/android/utils/demo/model/Person.java +++ b/demo/src/com/google/maps/android/utils/demo/model/Person.java @@ -44,4 +44,9 @@ public String getTitle() { public String getSnippet() { return null; } + + @Override + public ClusterItem copy(double lat, double lng) { + return new Person(new LatLng(lat, lng), name, profilePhoto); + } } diff --git a/library/src/com/google/maps/android/clustering/ClusterItem.java b/library/src/com/google/maps/android/clustering/ClusterItem.java index 746482490..21f8c82c9 100644 --- a/library/src/com/google/maps/android/clustering/ClusterItem.java +++ b/library/src/com/google/maps/android/clustering/ClusterItem.java @@ -37,4 +37,11 @@ public interface ClusterItem { * The description of this marker. */ String getSnippet(); + + /** + * Produces a copy of the same object but setting the given location. + * + * @return The new object copied. + */ + ClusterItem copy(double lat, double lng); } \ No newline at end of file diff --git a/library/src/com/google/maps/android/clustering/ClusterManager.java b/library/src/com/google/maps/android/clustering/ClusterManager.java index e1725bb67..910379dfb 100644 --- a/library/src/com/google/maps/android/clustering/ClusterManager.java +++ b/library/src/com/google/maps/android/clustering/ClusterManager.java @@ -27,11 +27,14 @@ import com.google.maps.android.clustering.algo.Algorithm; import com.google.maps.android.clustering.algo.NonHierarchicalDistanceBasedAlgorithm; import com.google.maps.android.clustering.algo.PreCachingAlgorithmDecorator; +import com.google.maps.android.clustering.view.ClusterItemsDistributor; import com.google.maps.android.clustering.view.ClusterRenderer; +import com.google.maps.android.clustering.view.DefaultClusterItemsDistributor; import com.google.maps.android.clustering.view.DefaultClusterRenderer; import java.util.Collection; import java.util.Iterator; +import java.util.List; import java.util.Set; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -44,6 +47,7 @@ */ public class ClusterManager implements GoogleMap.OnCameraIdleListener, + GoogleMap.OnCameraMoveListener, GoogleMap.OnMarkerClickListener, GoogleMap.OnInfoWindowClickListener { @@ -54,6 +58,7 @@ public class ClusterManager implements private Algorithm mAlgorithm; private final ReadWriteLock mAlgorithmLock = new ReentrantReadWriteLock(); private ClusterRenderer mRenderer; + private ClusterItemsDistributor mClusterItemsDistributor; private GoogleMap mMap; private CameraPosition mPreviousCameraPosition; @@ -77,7 +82,31 @@ public ClusterManager(Context context, GoogleMap map, MarkerManager markerManage mRenderer = new DefaultClusterRenderer(context, map, this); mAlgorithm = new PreCachingAlgorithmDecorator(new NonHierarchicalDistanceBasedAlgorithm()); mClusterTask = new ClusterTask(); + mClusterItemsDistributor = new DefaultClusterItemsDistributor(this); mRenderer.onAdd(); + + setOnClusterClickListener(new ClusterManager.OnClusterClickListener() { + + @Override + public boolean onClusterClick(Cluster cluster) { + float maxZoomLevel = mMap.getMaxZoomLevel(); + float currentZoomLevel = mMap.getCameraPosition().zoom; + + // only show markers if users is in the max zoom level + if (currentZoomLevel != maxZoomLevel) { + return false; + } + + if (!itemsInSameLocation(cluster)) { + return false; + } + + // relocate the markers as defined in the distributor + mClusterItemsDistributor.distribute(cluster); + + return true; + } + }); } public MarkerManager.Collection getMarkerCollection() { @@ -124,6 +153,10 @@ public void setAnimation(boolean animate) { mRenderer.setAnimation(animate); } + public void setClusterItemsDistributor(ClusterItemsDistributor clusterItemsDistributor) { + this.mClusterItemsDistributor = clusterItemsDistributor; + } + public ClusterRenderer getRenderer() { return mRenderer; } @@ -160,6 +193,13 @@ public void addItem(T myItem) { } } + public void removeItems(List items) { + + for (T item : items) { + removeItem(item); + } + } + public void removeItem(T item) { mAlgorithmLock.writeLock().lock(); try { @@ -207,6 +247,15 @@ public void onCameraIdle() { cluster(); } + @Override + public void onCameraMove() { + + // collect markers to the original position if they were relocated + if (mMap.getCameraPosition().zoom < mMap.getMaxZoomLevel()) { + mClusterItemsDistributor.collect(); + } + } + @Override public boolean onMarkerClick(Marker marker) { return getMarkerManager().onMarkerClick(marker); diff --git a/library/src/com/google/maps/android/clustering/view/ClusterItemsDistributor.java b/library/src/com/google/maps/android/clustering/view/ClusterItemsDistributor.java new file mode 100644 index 000000000..50a374dd6 --- /dev/null +++ b/library/src/com/google/maps/android/clustering/view/ClusterItemsDistributor.java @@ -0,0 +1,20 @@ +package com.google.maps.android.clustering.view; + +import com.google.maps.android.clustering.Cluster; +import com.google.maps.android.clustering.ClusterItem; + +/** + * It distributes the items in a cluster. + */ +public interface ClusterItemsDistributor { + + /** + * Proceed with the distribution of the items in a cluster. + */ + void distribute(Cluster cluster); + + /** + * Proceed to collect the items back to their previous state. + */ + void collect(); +} diff --git a/library/src/com/google/maps/android/clustering/view/DefaultClusterItemsDistributor.java b/library/src/com/google/maps/android/clustering/view/DefaultClusterItemsDistributor.java new file mode 100644 index 000000000..b55931a3f --- /dev/null +++ b/library/src/com/google/maps/android/clustering/view/DefaultClusterItemsDistributor.java @@ -0,0 +1,65 @@ +package com.google.maps.android.clustering.view; + +import com.google.maps.android.clustering.Cluster; +import com.google.maps.android.clustering.ClusterItem; +import com.google.maps.android.clustering.ClusterManager; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The default distributor of items included in a cluster. It distributes the items around the original lat/lng in a given radius. + * + * @param Cluster item type. + */ +public class DefaultClusterItemsDistributor implements ClusterItemsDistributor { + + private static final double DEFAULT_RADIUS = 0.00003; + + private static final String DEFAULT_DELETE_LIST = "itemsDeleted"; + + private static final String DEFAULT_ADDED_LIST = "itemsAdded"; + + private ClusterManager mClusterManager; + + private Map> mItemsCache; + + public DefaultClusterItemsDistributor(ClusterManager clusterManager) { + mClusterManager = clusterManager; + mItemsCache = new HashMap<>(); + mItemsCache.put(DEFAULT_ADDED_LIST, new ArrayList()); + mItemsCache.put(DEFAULT_DELETE_LIST, new ArrayList()); + } + + @Override + public void distribute(Cluster cluster) { + // relocate the markers around the original markers position + int counter = 0; + float rotateFactor = (360 / cluster.getItems().size()); + + for (T item : cluster.getItems()) { + double lat = item.getPosition().latitude + (DEFAULT_RADIUS * Math.cos(++counter * rotateFactor)); + double lng = item.getPosition().longitude + (DEFAULT_RADIUS * Math.sin(counter * rotateFactor)); + T copy = (T) item.copy(lat, lng); + + mClusterManager.removeItem(item); + mClusterManager.addItem(copy); + mClusterManager.cluster(); + + mItemsCache.get(DEFAULT_ADDED_LIST).add(copy); + mItemsCache.get(DEFAULT_DELETE_LIST).add(item); + } + } + + public void collect() { + // collect the items + mClusterManager.removeItems(mItemsCache.get(DEFAULT_ADDED_LIST)); + mClusterManager.addItems(mItemsCache.get(DEFAULT_DELETE_LIST)); + mClusterManager.cluster(); + + mItemsCache.get(DEFAULT_ADDED_LIST).clear(); + mItemsCache.get(DEFAULT_DELETE_LIST).clear(); + } +} diff --git a/library/tests/src/com/google/maps/android/clustering/QuadItemTest.java b/library/tests/src/com/google/maps/android/clustering/QuadItemTest.java index d3096549d..8928db3d6 100644 --- a/library/tests/src/com/google/maps/android/clustering/QuadItemTest.java +++ b/library/tests/src/com/google/maps/android/clustering/QuadItemTest.java @@ -44,6 +44,11 @@ public String getTitle() { public String getSnippet() { return null; } + + @Override + public TestingItem copy(double lat, double lng) { + return new TestingItem(lat, lng); + } } public void setUp() { From ae50d997cf890d0a1a9fde4f5644b198385c62d1 Mon Sep 17 00:00:00 2001 From: menismu Date: Fri, 4 Aug 2017 00:43:56 +0200 Subject: [PATCH 10/12] Isolating cluster click handler. --- .../android/clustering/ClusterManager.java | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/library/src/com/google/maps/android/clustering/ClusterManager.java b/library/src/com/google/maps/android/clustering/ClusterManager.java index 910379dfb..63170687f 100644 --- a/library/src/com/google/maps/android/clustering/ClusterManager.java +++ b/library/src/com/google/maps/android/clustering/ClusterManager.java @@ -89,22 +89,7 @@ public ClusterManager(Context context, GoogleMap map, MarkerManager markerManage @Override public boolean onClusterClick(Cluster cluster) { - float maxZoomLevel = mMap.getMaxZoomLevel(); - float currentZoomLevel = mMap.getCameraPosition().zoom; - - // only show markers if users is in the max zoom level - if (currentZoomLevel != maxZoomLevel) { - return false; - } - - if (!itemsInSameLocation(cluster)) { - return false; - } - - // relocate the markers as defined in the distributor - mClusterItemsDistributor.distribute(cluster); - - return true; + return handleClusterClick(cluster); } }); } @@ -290,6 +275,25 @@ public boolean itemsInSameLocation(Cluster cluster) { return true; } + public boolean handleClusterClick(Cluster cluster) { + float maxZoomLevel = mMap.getMaxZoomLevel(); + float currentZoomLevel = mMap.getCameraPosition().zoom; + + // only show markers if users is in the max zoom level + if (currentZoomLevel != maxZoomLevel) { + return false; + } + + if (!itemsInSameLocation(cluster)) { + return false; + } + + // relocate the markers as defined in the distributor + mClusterItemsDistributor.distribute(cluster); + + return true; + } + /** * Runs the clustering algorithm in a background thread, then re-paints when results come back. */ From 9d58ea94c8c1650856d6e79514eae9a8942f1074 Mon Sep 17 00:00:00 2001 From: menismu Date: Fri, 4 Aug 2017 00:47:26 +0200 Subject: [PATCH 11/12] Refactoring warnings reported. --- .../google/maps/android/clustering/ClusterManager.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/src/com/google/maps/android/clustering/ClusterManager.java b/library/src/com/google/maps/android/clustering/ClusterManager.java index 63170687f..0cfd2a032 100644 --- a/library/src/com/google/maps/android/clustering/ClusterManager.java +++ b/library/src/com/google/maps/android/clustering/ClusterManager.java @@ -58,7 +58,7 @@ public class ClusterManager implements private Algorithm mAlgorithm; private final ReadWriteLock mAlgorithmLock = new ReentrantReadWriteLock(); private ClusterRenderer mRenderer; - private ClusterItemsDistributor mClusterItemsDistributor; + private ClusterItemsDistributor mClusterItemsDistributor; private GoogleMap mMap; private CameraPosition mPreviousCameraPosition; @@ -82,7 +82,7 @@ public ClusterManager(Context context, GoogleMap map, MarkerManager markerManage mRenderer = new DefaultClusterRenderer(context, map, this); mAlgorithm = new PreCachingAlgorithmDecorator(new NonHierarchicalDistanceBasedAlgorithm()); mClusterTask = new ClusterTask(); - mClusterItemsDistributor = new DefaultClusterItemsDistributor(this); + mClusterItemsDistributor = new DefaultClusterItemsDistributor<>(this); mRenderer.onAdd(); setOnClusterClickListener(new ClusterManager.OnClusterClickListener() { @@ -138,7 +138,7 @@ public void setAnimation(boolean animate) { mRenderer.setAnimation(animate); } - public void setClusterItemsDistributor(ClusterItemsDistributor clusterItemsDistributor) { + public void setClusterItemsDistributor(ClusterItemsDistributor clusterItemsDistributor) { this.mClusterItemsDistributor = clusterItemsDistributor; } @@ -275,7 +275,7 @@ public boolean itemsInSameLocation(Cluster cluster) { return true; } - public boolean handleClusterClick(Cluster cluster) { + public boolean handleClusterClick(Cluster cluster) { float maxZoomLevel = mMap.getMaxZoomLevel(); float currentZoomLevel = mMap.getCameraPosition().zoom; From 185e2fb959ba68fd56012d5b1a4d59094edb7e0a Mon Sep 17 00:00:00 2001 From: menismu Date: Tue, 8 Aug 2017 23:49:37 +0200 Subject: [PATCH 12/12] Setting the distribution radius configurable. --- .../view/DefaultClusterItemsDistributor.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/library/src/com/google/maps/android/clustering/view/DefaultClusterItemsDistributor.java b/library/src/com/google/maps/android/clustering/view/DefaultClusterItemsDistributor.java index b55931a3f..7796e0865 100644 --- a/library/src/com/google/maps/android/clustering/view/DefaultClusterItemsDistributor.java +++ b/library/src/com/google/maps/android/clustering/view/DefaultClusterItemsDistributor.java @@ -22,12 +22,19 @@ public class DefaultClusterItemsDistributor implements Cl private static final String DEFAULT_ADDED_LIST = "itemsAdded"; + private double mDistributionRadius; + private ClusterManager mClusterManager; private Map> mItemsCache; public DefaultClusterItemsDistributor(ClusterManager clusterManager) { + this(clusterManager, DEFAULT_RADIUS); + } + + public DefaultClusterItemsDistributor(ClusterManager clusterManager, double distributionRadius) { mClusterManager = clusterManager; + mDistributionRadius = distributionRadius; mItemsCache = new HashMap<>(); mItemsCache.put(DEFAULT_ADDED_LIST, new ArrayList()); mItemsCache.put(DEFAULT_DELETE_LIST, new ArrayList()); @@ -40,8 +47,8 @@ public void distribute(Cluster cluster) { float rotateFactor = (360 / cluster.getItems().size()); for (T item : cluster.getItems()) { - double lat = item.getPosition().latitude + (DEFAULT_RADIUS * Math.cos(++counter * rotateFactor)); - double lng = item.getPosition().longitude + (DEFAULT_RADIUS * Math.sin(counter * rotateFactor)); + double lat = item.getPosition().latitude + (mDistributionRadius * Math.cos(++counter * rotateFactor)); + double lng = item.getPosition().longitude + (mDistributionRadius * Math.sin(counter * rotateFactor)); T copy = (T) item.copy(lat, lng); mClusterManager.removeItem(item);