From bac2f45b3809b543e050de29b3d83cf4a430dfa0 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Wed, 22 Sep 2021 11:36:17 -0400 Subject: [PATCH 01/19] test: Reproduce #774 https://github.com/googlemaps/android-maps-utils/issues/774 --- demo/build.gradle | 4 +- .../CustomMarkerClusteringDemoActivity.java | 66 ++++++++++--------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/demo/build.gradle b/demo/build.gradle index 1300b4a27..89931039c 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -63,13 +63,13 @@ android { dependencies { // [START_EXCLUDE silent] - // implementation project(':library') + gmsImplementation project(':library') implementation 'androidx.appcompat:appcompat:1.4.0-alpha03' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' // GMS gmsImplementation 'com.google.android.gms:play-services-maps:17.0.1' - gmsImplementation 'com.google.maps.android:android-maps-utils:2.2.6' + //gmsImplementation 'com.google.maps.android:android-maps-utils:2.2.6' // V3 v3Implementation 'com.google.android.libraries.maps:maps:3.1.0-beta' diff --git a/demo/src/gms/java/com/google/maps/android/utils/demo/CustomMarkerClusteringDemoActivity.java b/demo/src/gms/java/com/google/maps/android/utils/demo/CustomMarkerClusteringDemoActivity.java index 61abb01b5..8c2fdc428 100644 --- a/demo/src/gms/java/com/google/maps/android/utils/demo/CustomMarkerClusteringDemoActivity.java +++ b/demo/src/gms/java/com/google/maps/android/utils/demo/CustomMarkerClusteringDemoActivity.java @@ -16,6 +16,7 @@ package com.google.maps.android.utils.demo; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.view.View; @@ -26,6 +27,7 @@ import androidx.annotation.NonNull; import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.BitmapDescriptor; import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.LatLng; @@ -196,15 +198,24 @@ public void onClusterItemInfoWindowClick(Person item) { @Override protected void startDemo(boolean isRestore) { + //point camera to desired location if (!isRestore) { - getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 9.5f)); + getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(18.550931, 74.115642), 9.5f)); } mClusterManager = new ClusterManager<>(this, getMap()); - mClusterManager.setRenderer(new PersonRenderer()); + //set max distance to form a single cluster country wise on max zoom out + mClusterManager.getAlgorithm().setMaxDistanceBetweenClusteredItems(300); + + //initialise renderer + ZoomBasedRenderer renderer = new ZoomBasedRenderer(this,getMap(),mClusterManager); + mClusterManager.setRenderer(renderer); getMap().setOnCameraIdleListener(mClusterManager); - getMap().setOnMarkerClickListener(mClusterManager); - getMap().setOnInfoWindowClickListener(mClusterManager); + mClusterManager.getMarkerCollection().setOnMarkerClickListener(mClusterManager); + mClusterManager.getMarkerCollection().setOnInfoWindowClickListener(mClusterManager); + + //set camera listener inside renderer + getMap().setOnCameraMoveListener(renderer); mClusterManager.setOnClusterClickListener(this); mClusterManager.setOnClusterInfoWindowClickListener(this); mClusterManager.setOnClusterItemClickListener(this); @@ -215,32 +226,8 @@ protected void startDemo(boolean isRestore) { } private void addItems() { - // http://www.flickr.com/photos/sdasmarchives/5036248203/ - mClusterManager.addItem(new Person(position(), "Walter", R.drawable.walter)); - - // http://www.flickr.com/photos/usnationalarchives/4726917149/ - mClusterManager.addItem(new Person(position(), "Gran", R.drawable.gran)); - - // http://www.flickr.com/photos/nypl/3111525394/ - mClusterManager.addItem(new Person(position(), "Ruth", R.drawable.ruth)); - - // http://www.flickr.com/photos/smithsonian/2887433330/ - mClusterManager.addItem(new Person(position(), "Stefan", R.drawable.stefan)); - - // http://www.flickr.com/photos/library_of_congress/2179915182/ - mClusterManager.addItem(new Person(position(), "Mechanic", R.drawable.mechanic)); - - // http://www.flickr.com/photos/nationalmediamuseum/7893552556/ - mClusterManager.addItem(new Person(position(), "Yeats", R.drawable.yeats)); - - // http://www.flickr.com/photos/sdasmarchives/5036231225/ - mClusterManager.addItem(new Person(position(), "John", R.drawable.john)); - - // http://www.flickr.com/photos/anmm_thecommons/7694202096/ - mClusterManager.addItem(new Person(position(), "Trevor the Turtle", R.drawable.turtle)); - - // http://www.flickr.com/photos/usnationalarchives/4726892651/ - mClusterManager.addItem(new Person(position(), "Teach", R.drawable.teacher)); + mClusterManager.addItem(new Person(new LatLng(18.528146, 73.797726), "Loc1", R.drawable.walter)); + mClusterManager.addItem(new Person(new LatLng(18.545723, 73.917202), "Loc2", R.drawable.gran)); } private LatLng position() { @@ -250,4 +237,23 @@ private LatLng position() { private double random(double min, double max) { return mRandom.nextDouble() * (max - min) + min; } + + private class ZoomBasedRenderer extends DefaultClusterRenderer implements GoogleMap.OnCameraMoveListener{ + private Float mapZoomLevel = 15f; + public ZoomBasedRenderer(Context context, GoogleMap map, ClusterManager clusterManager) { + super(context, map, clusterManager); + } + + @Override + public void onCameraMove() { + //map zoom level gets updated here on camera change + mapZoomLevel = getMap().getCameraPosition().zoom; + } + + @Override + protected boolean shouldRenderAsCluster(@NonNull Cluster cluster) { + // cluster when mapZoomLevel is less than 12f. Else show as marker + return mapZoomLevel < 12f; + } + } } From 2d04a6b1af03b3464ddacd0fae82f633562c6b51 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Wed, 29 Sep 2021 16:08:24 -0400 Subject: [PATCH 02/19] chore: Add overridable shouldRender(), update demo Activities * Restore CustomMarkerClusteringDemoActivity, add ZoomClusteringDemoActivity --- .../CustomMarkerClusteringDemoActivity.java | 68 +++--- .../maps/android/utils/demo/MainActivity.java | 1 + .../demo/ZoomClusteringDemoActivity.java | 198 ++++++++++++++++++ demo/src/main/AndroidManifest.xml | 1 + .../view/DefaultClusterRenderer.java | 27 ++- 5 files changed, 257 insertions(+), 38 deletions(-) create mode 100644 demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java diff --git a/demo/src/gms/java/com/google/maps/android/utils/demo/CustomMarkerClusteringDemoActivity.java b/demo/src/gms/java/com/google/maps/android/utils/demo/CustomMarkerClusteringDemoActivity.java index 8c2fdc428..6a27fd9fe 100644 --- a/demo/src/gms/java/com/google/maps/android/utils/demo/CustomMarkerClusteringDemoActivity.java +++ b/demo/src/gms/java/com/google/maps/android/utils/demo/CustomMarkerClusteringDemoActivity.java @@ -16,7 +16,6 @@ package com.google.maps.android.utils.demo; -import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.view.View; @@ -27,7 +26,6 @@ import androidx.annotation.NonNull; import com.google.android.gms.maps.CameraUpdateFactory; -import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.model.BitmapDescriptor; import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.LatLng; @@ -198,24 +196,15 @@ public void onClusterItemInfoWindowClick(Person item) { @Override protected void startDemo(boolean isRestore) { - //point camera to desired location if (!isRestore) { - getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(18.550931, 74.115642), 9.5f)); + getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(51.503186, -0.126446), 9.5f)); } mClusterManager = new ClusterManager<>(this, getMap()); - //set max distance to form a single cluster country wise on max zoom out - mClusterManager.getAlgorithm().setMaxDistanceBetweenClusteredItems(300); - - //initialise renderer - ZoomBasedRenderer renderer = new ZoomBasedRenderer(this,getMap(),mClusterManager); - mClusterManager.setRenderer(renderer); + mClusterManager.setRenderer(new PersonRenderer()); getMap().setOnCameraIdleListener(mClusterManager); - mClusterManager.getMarkerCollection().setOnMarkerClickListener(mClusterManager); - mClusterManager.getMarkerCollection().setOnInfoWindowClickListener(mClusterManager); - - //set camera listener inside renderer - getMap().setOnCameraMoveListener(renderer); + getMap().setOnMarkerClickListener(mClusterManager); + getMap().setOnInfoWindowClickListener(mClusterManager); mClusterManager.setOnClusterClickListener(this); mClusterManager.setOnClusterInfoWindowClickListener(this); mClusterManager.setOnClusterItemClickListener(this); @@ -226,8 +215,32 @@ protected void startDemo(boolean isRestore) { } private void addItems() { - mClusterManager.addItem(new Person(new LatLng(18.528146, 73.797726), "Loc1", R.drawable.walter)); - mClusterManager.addItem(new Person(new LatLng(18.545723, 73.917202), "Loc2", R.drawable.gran)); + // http://www.flickr.com/photos/sdasmarchives/5036248203/ + mClusterManager.addItem(new Person(position(), "Walter", R.drawable.walter)); + + // http://www.flickr.com/photos/usnationalarchives/4726917149/ + mClusterManager.addItem(new Person(position(), "Gran", R.drawable.gran)); + + // http://www.flickr.com/photos/nypl/3111525394/ + mClusterManager.addItem(new Person(position(), "Ruth", R.drawable.ruth)); + + // http://www.flickr.com/photos/smithsonian/2887433330/ + mClusterManager.addItem(new Person(position(), "Stefan", R.drawable.stefan)); + + // http://www.flickr.com/photos/library_of_congress/2179915182/ + mClusterManager.addItem(new Person(position(), "Mechanic", R.drawable.mechanic)); + + // http://www.flickr.com/photos/nationalmediamuseum/7893552556/ + mClusterManager.addItem(new Person(position(), "Yeats", R.drawable.yeats)); + + // http://www.flickr.com/photos/sdasmarchives/5036231225/ + mClusterManager.addItem(new Person(position(), "John", R.drawable.john)); + + // http://www.flickr.com/photos/anmm_thecommons/7694202096/ + mClusterManager.addItem(new Person(position(), "Trevor the Turtle", R.drawable.turtle)); + + // http://www.flickr.com/photos/usnationalarchives/4726892651/ + mClusterManager.addItem(new Person(position(), "Teach", R.drawable.teacher)); } private LatLng position() { @@ -237,23 +250,4 @@ private LatLng position() { private double random(double min, double max) { return mRandom.nextDouble() * (max - min) + min; } - - private class ZoomBasedRenderer extends DefaultClusterRenderer implements GoogleMap.OnCameraMoveListener{ - private Float mapZoomLevel = 15f; - public ZoomBasedRenderer(Context context, GoogleMap map, ClusterManager clusterManager) { - super(context, map, clusterManager); - } - - @Override - public void onCameraMove() { - //map zoom level gets updated here on camera change - mapZoomLevel = getMap().getCameraPosition().zoom; - } - - @Override - protected boolean shouldRenderAsCluster(@NonNull Cluster cluster) { - // cluster when mapZoomLevel is less than 12f. Else show as marker - return mapZoomLevel < 12f; - } - } -} +} \ No newline at end of file diff --git a/demo/src/gms/java/com/google/maps/android/utils/demo/MainActivity.java b/demo/src/gms/java/com/google/maps/android/utils/demo/MainActivity.java index cc22b6e23..360258f9a 100644 --- a/demo/src/gms/java/com/google/maps/android/utils/demo/MainActivity.java +++ b/demo/src/gms/java/com/google/maps/android/utils/demo/MainActivity.java @@ -45,6 +45,7 @@ protected void onCreate(Bundle savedInstanceState) { addDemo("Clustering: 2K markers", BigClusteringDemoActivity.class); addDemo("Clustering: 20K only visible markers", VisibleClusteringDemoActivity.class); addDemo("Clustering: ViewModel", ClusteringViewModelDemoActivity.class); + addDemo("Clustering: Force On Zoom", ZoomClusteringDemoActivity.class); addDemo("PolyUtil.decode", PolyDecodeDemoActivity.class); addDemo("PolyUtil.simplify", PolySimplifyDemoActivity.class); addDemo("IconGenerator", IconGeneratorDemoActivity.class); diff --git a/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java new file mode 100644 index 000000000..490910b60 --- /dev/null +++ b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java @@ -0,0 +1,198 @@ +/* + * Copyright 2013 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 android.content.Context; +import android.widget.Toast; + +import androidx.annotation.NonNull; + +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.LatLngBounds; +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.collections.MarkerManager; +import com.google.maps.android.utils.demo.model.MyItem; + +import java.util.Set; + +/** + * Demonstrates heavy customisation of the look of rendered clusters. + */ +public class ZoomClusteringDemoActivity extends BaseDemoActivity implements ClusterManager.OnClusterClickListener, ClusterManager.OnClusterInfoWindowClickListener, ClusterManager.OnClusterItemClickListener, ClusterManager.OnClusterItemInfoWindowClickListener { + + @Override + public boolean onClusterClick(Cluster cluster) { + // Show a toast with some info when the cluster is clicked. + String title = cluster.getItems().iterator().next().getTitle(); + Toast.makeText(this, cluster.getSize() + " (including " + title + ")", Toast.LENGTH_SHORT).show(); + + // Zoom in the cluster. Need to create LatLngBounds and including all the cluster items + // inside of bounds, then animate to center of the bounds. + + // Create the builder to collect all essential cluster items for the bounds. + LatLngBounds.Builder builder = LatLngBounds.builder(); + for (ClusterItem item : cluster.getItems()) { + builder.include(item.getPosition()); + } + + // Animate camera to the bounds + try { + getMap().animateCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), 100)); + } catch (Exception e) { + e.printStackTrace(); + } + + return true; + } + + @Override + public void onClusterInfoWindowClick(Cluster cluster) { + // Does nothing, but you could go to a list of the users. + } + + @Override + public boolean onClusterItemClick(MyItem item) { + // Does nothing, but you could go into the user's profile page, for example. + return false; + } + + @Override + public void onClusterItemInfoWindowClick(MyItem item) { + // Does nothing, but you could go into the user's profile page, for example. + } + + @Override + protected void startDemo(boolean isRestore) { + if (!isRestore) { + getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(18.550931, 74.115642), 9.5f)); + } + + // Shared object manager - used to support multiple layers if needed (e.g., if we want to add our own non-clustered items) + MarkerManager markerManager = new MarkerManager(getMap()); + + ClusterManager clusterManager = new ClusterManager<>(this, getMap(), markerManager); + clusterManager.getAlgorithm().setMaxDistanceBetweenClusteredItems(300); + + // Initialize renderer + ZoomBasedRenderer renderer = new ZoomBasedRenderer(this, getMap(), clusterManager); + clusterManager.setRenderer(renderer); + + getMap().setOnCameraIdleListener(clusterManager); + clusterManager.getMarkerCollection().setOnMarkerClickListener(clusterManager); + clusterManager.getMarkerCollection().setOnInfoWindowClickListener(clusterManager); + + // Set camera listener inside renderer + clusterManager.setOnClusterClickListener(this); + clusterManager.setOnClusterInfoWindowClickListener(this); + clusterManager.setOnClusterItemClickListener(this); + clusterManager.setOnClusterItemInfoWindowClickListener(this); + + clusterManager.addItem(new MyItem(18.528146, 73.797726, "Loc1", "1st location")); + clusterManager.addItem(new MyItem(18.545723, 73.917202, "Loc2", "2nd location")); + + clusterManager.cluster(); + } + + private class ZoomBasedRenderer extends DefaultClusterRenderer implements GoogleMap.OnCameraIdleListener { + private Float mapZoomLevel = 15f; + private Float oldZoom; + private static final float ZOOM_THRESHOLD = 12f; + + public ZoomBasedRenderer(Context context, GoogleMap map, ClusterManager clusterManager) { + super(context, map, clusterManager); + } + + /** + * The ClusterManager will call the onCameraIdle() implementation of any Renderer *before* + * clustering and rendering takes place. This allows us to capture metrics that may be + * useful for clustering, such as the zoom level. + */ + @Override + public void onCameraIdle() { + // Remember the previous zoom level, capture the new zoom level. + oldZoom = mapZoomLevel; + mapZoomLevel = getMap().getCameraPosition().zoom; + } + + /** + * You can override this method to control when the cluster manager renders a group of + * items as a cluster (vs. as a set of individual markers). + *

+ * In this case, we want single markers to show up as a cluster when zoomed out, but + * individual markers when zoomed in. + * + * @param cluster cluster to examine for rendering + * @return true when zoom level is less than the threshold (show as cluster when zoomed out), + * and false when the the zoom level is more than or equal to the threshold (show as marker + * when zoomed in) + */ + @Override + protected boolean shouldRenderAsCluster(@NonNull Cluster cluster) { + // Show cluster when mapZoomLevel is less than the threshold, otherwise show as marker + return mapZoomLevel < ZOOM_THRESHOLD; + } + + /** + * You can override this method to control optimizations surrounding rendering. The default + * implementation in the library simply checks if the new clusters are equal to the old + * clusters, and if so, it returns false to avoid re-rendering the same content. + *

+ * However, in our case we need to change this behavior. As defined in + * {@link this.shouldRenderAsCluster()}, we want an item to render as a cluster above a + * certain zoom level and as a marker below a certain zoom level even if the contents of + * the clusters themselves did not change. In this case, we need to override this method + * to implement this new optimization behavior. + * + * @param oldClusters The clusters from the previous iteration of the clustering algorithm + * @param newClusters The clusters from the current iteration of the clustering algorithm + * @return true if the new clusters should be rendered on the map, and false if they should + * not. + */ + @Override + protected boolean shouldRender(@NonNull Set> oldClusters, @NonNull Set> newClusters) { + if (crossedZoomThreshold(oldZoom, mapZoomLevel)) { + // Render when the zoom level crosses the threshold, even if the clusters don't change + return true; + } else { + // If clusters didn't change, skip render for optimization + return !newClusters.equals(oldClusters); + } + } + + /** + * Returns true if the transition between the two zoom levels crossed a defined threshold, + * false if it did not. + * + * @param oldZoom zoom level from the previous time the camera stopped moving + * @param newZoom zoom level from the most recent time the camera stopped moving + * @return true if the transition between the two zoom levels crossed a defined threshold, + * false if it did not. + */ + private boolean crossedZoomThreshold(Float oldZoom, Float newZoom) { + if (oldZoom == null || newZoom == null) { + return true; + } + return (oldZoom < ZOOM_THRESHOLD && newZoom > ZOOM_THRESHOLD) || + (oldZoom > ZOOM_THRESHOLD && newZoom < ZOOM_THRESHOLD); + } + } +} diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index f253c6897..6bb2bacd0 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -59,6 +59,7 @@ + diff --git a/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java b/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java index b56683c78..018767a42 100644 --- a/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java +++ b/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java @@ -356,6 +356,31 @@ protected boolean shouldRenderAsCluster(@NonNull Cluster cluster) { return cluster.getSize() >= mMinClusterSize; } + /** + * Determines if the new clusters should be rendered on the map, given the old clusters. This + * method is primarily for optimization of performance, and the default implementation simply + * checks if the new clusters are equal to the old clusters, and if so, it returns false. + * + * However, there are cases where you may want to re-render the clusters even if they didn't + * change. For example, if you want a cluster with one item to render as a cluster above + * a certain zoom level and as a marker below a certain zoom level (even if the contents of the + * clusters themselves did not change). In this case, you could check the zoom level in an + * implementation of this method and if that zoom level threshold is crossed return true, else + * return {@code !newClusters.equals(oldClusters)}. + * + * Note that always returning true from this method could potentially have negative performance + * implications as clusters will be re-rendered on each pass even if they don't change. + * + * @param oldClusters The clusters from the previous iteration of the clustering algorithm + * @param newClusters The clusters from the current iteration of the clustering algorithm + * @return true if the new clusters should be rendered on the map, and false if they should not. This + * method is primarily for optimization of performance, and the default implementation simply + * checks if the new clusters are equal to the old clusters, and if so, it returns false. + */ + protected boolean shouldRender(@NonNull Set> oldClusters, @NonNull Set> newClusters) { + return !newClusters.equals(oldClusters); + } + /** * Transforms the current view (represented by DefaultClusterRenderer.mClusters and DefaultClusterRenderer.mZoom) to a * new zoom level and set of clusters. @@ -405,7 +430,7 @@ public void setMapZoom(float zoom) { @SuppressLint("NewApi") public void run() { - if (clusters.equals(DefaultClusterRenderer.this.mClusters)) { + if (!shouldRender(DefaultClusterRenderer.this.mClusters, clusters)) { mCallback.run(); return; } From 01912e9c1627be108fa403245f68709c9e0b6526 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Wed, 29 Sep 2021 16:55:01 -0400 Subject: [PATCH 03/19] chore: Refactoring and cleanup --- .../demo/ZoomClusteringDemoActivity.java | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java index 490910b60..c35dd5ae0 100644 --- a/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java +++ b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java @@ -29,7 +29,6 @@ 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.collections.MarkerManager; import com.google.maps.android.utils.demo.model.MyItem; import java.util.Set; @@ -71,49 +70,41 @@ public void onClusterInfoWindowClick(Cluster cluster) { @Override public boolean onClusterItemClick(MyItem item) { - // Does nothing, but you could go into the user's profile page, for example. + // Does nothing, but you could go into a user's profile page, for example. return false; } @Override public void onClusterItemInfoWindowClick(MyItem item) { - // Does nothing, but you could go into the user's profile page, for example. + // Does nothing, but you could go into a user's profile page, for example. } @Override protected void startDemo(boolean isRestore) { if (!isRestore) { - getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(18.550931, 74.115642), 9.5f)); + getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(18.528146, 73.797726), 9.5f)); } - // Shared object manager - used to support multiple layers if needed (e.g., if we want to add our own non-clustered items) - MarkerManager markerManager = new MarkerManager(getMap()); - - ClusterManager clusterManager = new ClusterManager<>(this, getMap(), markerManager); - clusterManager.getAlgorithm().setMaxDistanceBetweenClusteredItems(300); + ClusterManager clusterManager = new ClusterManager<>(this, getMap()); + getMap().setOnCameraIdleListener(clusterManager); // Initialize renderer ZoomBasedRenderer renderer = new ZoomBasedRenderer(this, getMap(), clusterManager); clusterManager.setRenderer(renderer); - getMap().setOnCameraIdleListener(clusterManager); - clusterManager.getMarkerCollection().setOnMarkerClickListener(clusterManager); - clusterManager.getMarkerCollection().setOnInfoWindowClickListener(clusterManager); - - // Set camera listener inside renderer + // Set click listeners clusterManager.setOnClusterClickListener(this); clusterManager.setOnClusterInfoWindowClickListener(this); clusterManager.setOnClusterItemClickListener(this); clusterManager.setOnClusterItemInfoWindowClickListener(this); + // Add items clusterManager.addItem(new MyItem(18.528146, 73.797726, "Loc1", "1st location")); clusterManager.addItem(new MyItem(18.545723, 73.917202, "Loc2", "2nd location")); - - clusterManager.cluster(); } private class ZoomBasedRenderer extends DefaultClusterRenderer implements GoogleMap.OnCameraIdleListener { - private Float mapZoomLevel = 15f; + private Float zoom = 15f; private Float oldZoom; private static final float ZOOM_THRESHOLD = 12f; @@ -122,15 +113,15 @@ public ZoomBasedRenderer(Context context, GoogleMap map, ClusterManager } /** - * The ClusterManager will call the onCameraIdle() implementation of any Renderer *before* - * clustering and rendering takes place. This allows us to capture metrics that may be - * useful for clustering, such as the zoom level. + * The {@link ClusterManager} will call the {@link this.onCameraIdle()} implementation of + * any Renderer before clustering and rendering takes place. This allows us to + * capture metrics that may be useful for clustering, such as the zoom level. */ @Override public void onCameraIdle() { // Remember the previous zoom level, capture the new zoom level. - oldZoom = mapZoomLevel; - mapZoomLevel = getMap().getCameraPosition().zoom; + oldZoom = zoom; + zoom = getMap().getCameraPosition().zoom; } /** @@ -147,8 +138,8 @@ public void onCameraIdle() { */ @Override protected boolean shouldRenderAsCluster(@NonNull Cluster cluster) { - // Show cluster when mapZoomLevel is less than the threshold, otherwise show as marker - return mapZoomLevel < ZOOM_THRESHOLD; + // Show cluster when zoom is less than the threshold, otherwise show as marker + return zoom < ZOOM_THRESHOLD; } /** @@ -162,6 +153,10 @@ protected boolean shouldRenderAsCluster(@NonNull Cluster cluster) { * the clusters themselves did not change. In this case, we need to override this method * to implement this new optimization behavior. * + * Note that always returning true from this method could potentially have negative + * performance implications as clusters will be re-rendered on each pass even if they don't + * change. + * * @param oldClusters The clusters from the previous iteration of the clustering algorithm * @param newClusters The clusters from the current iteration of the clustering algorithm * @return true if the new clusters should be rendered on the map, and false if they should @@ -169,7 +164,7 @@ protected boolean shouldRenderAsCluster(@NonNull Cluster cluster) { */ @Override protected boolean shouldRender(@NonNull Set> oldClusters, @NonNull Set> newClusters) { - if (crossedZoomThreshold(oldZoom, mapZoomLevel)) { + if (crossedZoomThreshold(oldZoom, zoom)) { // Render when the zoom level crosses the threshold, even if the clusters don't change return true; } else { From bcecaae69883a5d92fee3def359eed6b988dcd99 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Wed, 29 Sep 2021 17:23:47 -0400 Subject: [PATCH 04/19] chore: Change dependencies back, document commented code --- demo/build.gradle | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/demo/build.gradle b/demo/build.gradle index 89931039c..63b5cbcf0 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -63,18 +63,19 @@ android { dependencies { // [START_EXCLUDE silent] - gmsImplementation project(':library') implementation 'androidx.appcompat:appcompat:1.4.0-alpha03' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' // GMS gmsImplementation 'com.google.android.gms:play-services-maps:17.0.1' - //gmsImplementation 'com.google.maps.android:android-maps-utils:2.2.6' + gmsImplementation 'com.google.maps.android:android-maps-utils:2.2.6' + // gmsImplementation project(':library') // Uncomment this and comment above lines to use the library project code instead of artifact // V3 v3Implementation 'com.google.android.libraries.maps:maps:3.1.0-beta' v3Implementation 'com.android.volley:volley:1.2.1' // TODO - Remove this after Maps SDK v3 beta includes Volley versions on Maven Central v3Implementation 'com.google.maps.android:android-maps-utils-v3:2.2.6' + // v3Implementation project(':library') // Uncomment this and comment above lines to use the library project code instead of artifact v3Implementation 'androidx.multidex:multidex:2.0.1' implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" From df5a151dc7398c14ef821f593c74b60e069fb335 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Wed, 29 Sep 2021 17:26:22 -0400 Subject: [PATCH 05/19] docs: Tweak docs --- .../maps/android/clustering/view/DefaultClusterRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java b/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java index 018767a42..317897c04 100644 --- a/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java +++ b/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java @@ -366,7 +366,7 @@ protected boolean shouldRenderAsCluster(@NonNull Cluster cluster) { * a certain zoom level and as a marker below a certain zoom level (even if the contents of the * clusters themselves did not change). In this case, you could check the zoom level in an * implementation of this method and if that zoom level threshold is crossed return true, else - * return {@code !newClusters.equals(oldClusters)}. + * {@code return !newClusters.equals(oldClusters)}. * * Note that always returning true from this method could potentially have negative performance * implications as clusters will be re-rendered on each pass even if they don't change. From 9d247bb8ce1d110fe65967da14425e57a6bed248 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Wed, 29 Sep 2021 18:12:15 -0400 Subject: [PATCH 06/19] chore: Add v3 demo code --- .../android/utils/demo/CustomMarkerClusteringDemoActivity.java | 2 +- .../java/com/google/maps/android/utils/demo/MainActivity.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/src/v3/java/com/google/maps/android/utils/demo/CustomMarkerClusteringDemoActivity.java b/demo/src/v3/java/com/google/maps/android/utils/demo/CustomMarkerClusteringDemoActivity.java index a606db300..eafb5241a 100644 --- a/demo/src/v3/java/com/google/maps/android/utils/demo/CustomMarkerClusteringDemoActivity.java +++ b/demo/src/v3/java/com/google/maps/android/utils/demo/CustomMarkerClusteringDemoActivity.java @@ -257,4 +257,4 @@ private LatLng position() { private double random(double min, double max) { return mRandom.nextDouble() * (max - min) + min; } -} +} \ No newline at end of file diff --git a/demo/src/v3/java/com/google/maps/android/utils/demo/MainActivity.java b/demo/src/v3/java/com/google/maps/android/utils/demo/MainActivity.java index 33db20aaa..a400cef83 100644 --- a/demo/src/v3/java/com/google/maps/android/utils/demo/MainActivity.java +++ b/demo/src/v3/java/com/google/maps/android/utils/demo/MainActivity.java @@ -52,6 +52,7 @@ protected void onCreate(Bundle savedInstanceState) { addDemo("Clustering: 2K markers", BigClusteringDemoActivity.class); addDemo("Clustering: 20K only visible markers", VisibleClusteringDemoActivity.class); addDemo("Clustering: ViewModel", ClusteringViewModelDemoActivity.class); + addDemo("Clustering: Force On Zoom", ZoomClusteringDemoActivity.class); addDemo("PolyUtil.decode", PolyDecodeDemoActivity.class); addDemo("PolyUtil.simplify", PolySimplifyDemoActivity.class); addDemo("IconGenerator", IconGeneratorDemoActivity.class); From 8419783f3513ab5f2f00967fbb508ddca44f8407 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Wed, 29 Sep 2021 18:16:02 -0400 Subject: [PATCH 07/19] chore: Tweak text in demo app --- .../java/com/google/maps/android/utils/demo/MainActivity.java | 2 +- .../java/com/google/maps/android/utils/demo/MainActivity.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/src/gms/java/com/google/maps/android/utils/demo/MainActivity.java b/demo/src/gms/java/com/google/maps/android/utils/demo/MainActivity.java index 360258f9a..fba59bbde 100644 --- a/demo/src/gms/java/com/google/maps/android/utils/demo/MainActivity.java +++ b/demo/src/gms/java/com/google/maps/android/utils/demo/MainActivity.java @@ -45,7 +45,7 @@ protected void onCreate(Bundle savedInstanceState) { addDemo("Clustering: 2K markers", BigClusteringDemoActivity.class); addDemo("Clustering: 20K only visible markers", VisibleClusteringDemoActivity.class); addDemo("Clustering: ViewModel", ClusteringViewModelDemoActivity.class); - addDemo("Clustering: Force On Zoom", ZoomClusteringDemoActivity.class); + addDemo("Clustering: Force on Zoom", ZoomClusteringDemoActivity.class); addDemo("PolyUtil.decode", PolyDecodeDemoActivity.class); addDemo("PolyUtil.simplify", PolySimplifyDemoActivity.class); addDemo("IconGenerator", IconGeneratorDemoActivity.class); diff --git a/demo/src/v3/java/com/google/maps/android/utils/demo/MainActivity.java b/demo/src/v3/java/com/google/maps/android/utils/demo/MainActivity.java index a400cef83..84778d1d9 100644 --- a/demo/src/v3/java/com/google/maps/android/utils/demo/MainActivity.java +++ b/demo/src/v3/java/com/google/maps/android/utils/demo/MainActivity.java @@ -52,7 +52,7 @@ protected void onCreate(Bundle savedInstanceState) { addDemo("Clustering: 2K markers", BigClusteringDemoActivity.class); addDemo("Clustering: 20K only visible markers", VisibleClusteringDemoActivity.class); addDemo("Clustering: ViewModel", ClusteringViewModelDemoActivity.class); - addDemo("Clustering: Force On Zoom", ZoomClusteringDemoActivity.class); + addDemo("Clustering: Force on Zoom", ZoomClusteringDemoActivity.class); addDemo("PolyUtil.decode", PolyDecodeDemoActivity.class); addDemo("PolyUtil.simplify", PolySimplifyDemoActivity.class); addDemo("IconGenerator", IconGeneratorDemoActivity.class); From d1f2ac178c4aa68412980924bc21f16232f6c818 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Wed, 29 Sep 2021 18:17:17 -0400 Subject: [PATCH 08/19] chore: Update copyright --- .../maps/android/utils/demo/ZoomClusteringDemoActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java index c35dd5ae0..3bf56f4fa 100644 --- a/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java +++ b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 Google Inc. + * Copyright 2021 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 12e6f360c0ad94167770445391c2ead0b9813899 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Wed, 29 Sep 2021 18:18:23 -0400 Subject: [PATCH 09/19] chore: Update comments --- .../maps/android/utils/demo/ZoomClusteringDemoActivity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java index 3bf56f4fa..96408875e 100644 --- a/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java +++ b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java @@ -34,7 +34,8 @@ import java.util.Set; /** - * Demonstrates heavy customisation of the look of rendered clusters. + * Demonstrates how to force re-rendering of clusters even when the contents don't change. For + * example, when changing zoom levels. */ public class ZoomClusteringDemoActivity extends BaseDemoActivity implements ClusterManager.OnClusterClickListener, ClusterManager.OnClusterInfoWindowClickListener, ClusterManager.OnClusterItemClickListener, ClusterManager.OnClusterItemInfoWindowClickListener { From dc28092bc0c7ed0aa15bc052b85ce8ff24f96149 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Wed, 29 Sep 2021 18:19:02 -0400 Subject: [PATCH 10/19] chore: Add v3 code again --- .../demo/ZoomClusteringDemoActivity.java | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java diff --git a/demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java b/demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java new file mode 100644 index 000000000..d2420be3c --- /dev/null +++ b/demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java @@ -0,0 +1,201 @@ +/** + * DO NOT EDIT THIS FILE. + * + * This source code was autogenerated from source code within the `demo/src/gms` directory + * and is not intended for modifications. If any edits should be made, please do so in the + * corresponding file under the `demo/src/gms` directory. + */ +/* + * Copyright 2021 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 android.content.Context; +import android.widget.Toast; + +import androidx.annotation.NonNull; + +import com.google.android.libraries.maps.CameraUpdateFactory; +import com.google.android.libraries.maps.GoogleMap; +import com.google.android.libraries.maps.model.LatLng; +import com.google.android.libraries.maps.model.LatLngBounds; +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 java.util.Set; + +/** + * Demonstrates how to force re-rendering of clusters even when the contents don't change. For + * example, when changing zoom levels. + */ +public class ZoomClusteringDemoActivity extends BaseDemoActivity implements ClusterManager.OnClusterClickListener, ClusterManager.OnClusterInfoWindowClickListener, ClusterManager.OnClusterItemClickListener, ClusterManager.OnClusterItemInfoWindowClickListener { + + @Override + public boolean onClusterClick(Cluster cluster) { + // Show a toast with some info when the cluster is clicked. + String title = cluster.getItems().iterator().next().getTitle(); + Toast.makeText(this, cluster.getSize() + " (including " + title + ")", Toast.LENGTH_SHORT).show(); + + // Zoom in the cluster. Need to create LatLngBounds and including all the cluster items + // inside of bounds, then animate to center of the bounds. + + // Create the builder to collect all essential cluster items for the bounds. + LatLngBounds.Builder builder = LatLngBounds.builder(); + for (ClusterItem item : cluster.getItems()) { + builder.include(item.getPosition()); + } + + // Animate camera to the bounds + try { + getMap().animateCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), 100)); + } catch (Exception e) { + e.printStackTrace(); + } + + return true; + } + + @Override + public void onClusterInfoWindowClick(Cluster cluster) { + // Does nothing, but you could go to a list of the users. + } + + @Override + public boolean onClusterItemClick(MyItem item) { + // Does nothing, but you could go into a user's profile page, for example. + return false; + } + + @Override + public void onClusterItemInfoWindowClick(MyItem item) { + // Does nothing, but you could go into a user's profile page, for example. + } + + @Override + protected void startDemo(boolean isRestore) { + if (!isRestore) { + getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(18.528146, 73.797726), 9.5f)); + } + + ClusterManager clusterManager = new ClusterManager<>(this, getMap()); + getMap().setOnCameraIdleListener(clusterManager); + + // Initialize renderer + ZoomBasedRenderer renderer = new ZoomBasedRenderer(this, getMap(), clusterManager); + clusterManager.setRenderer(renderer); + + // Set click listeners + clusterManager.setOnClusterClickListener(this); + clusterManager.setOnClusterInfoWindowClickListener(this); + clusterManager.setOnClusterItemClickListener(this); + clusterManager.setOnClusterItemInfoWindowClickListener(this); + + // Add items + clusterManager.addItem(new MyItem(18.528146, 73.797726, "Loc1", "1st location")); + clusterManager.addItem(new MyItem(18.545723, 73.917202, "Loc2", "2nd location")); + } + + private class ZoomBasedRenderer extends DefaultClusterRenderer implements GoogleMap.OnCameraIdleListener { + private Float zoom = 15f; + private Float oldZoom; + private static final float ZOOM_THRESHOLD = 12f; + + public ZoomBasedRenderer(Context context, GoogleMap map, ClusterManager clusterManager) { + super(context, map, clusterManager); + } + + /** + * The {@link ClusterManager} will call the {@link this.onCameraIdle()} implementation of + * any Renderer before clustering and rendering takes place. This allows us to + * capture metrics that may be useful for clustering, such as the zoom level. + */ + @Override + public void onCameraIdle() { + // Remember the previous zoom level, capture the new zoom level. + oldZoom = zoom; + zoom = getMap().getCameraPosition().zoom; + } + + /** + * You can override this method to control when the cluster manager renders a group of + * items as a cluster (vs. as a set of individual markers). + *

+ * In this case, we want single markers to show up as a cluster when zoomed out, but + * individual markers when zoomed in. + * + * @param cluster cluster to examine for rendering + * @return true when zoom level is less than the threshold (show as cluster when zoomed out), + * and false when the the zoom level is more than or equal to the threshold (show as marker + * when zoomed in) + */ + @Override + protected boolean shouldRenderAsCluster(@NonNull Cluster cluster) { + // Show cluster when zoom is less than the threshold, otherwise show as marker + return zoom < ZOOM_THRESHOLD; + } + + /** + * You can override this method to control optimizations surrounding rendering. The default + * implementation in the library simply checks if the new clusters are equal to the old + * clusters, and if so, it returns false to avoid re-rendering the same content. + *

+ * However, in our case we need to change this behavior. As defined in + * {@link this.shouldRenderAsCluster()}, we want an item to render as a cluster above a + * certain zoom level and as a marker below a certain zoom level even if the contents of + * the clusters themselves did not change. In this case, we need to override this method + * to implement this new optimization behavior. + * + * Note that always returning true from this method could potentially have negative + * performance implications as clusters will be re-rendered on each pass even if they don't + * change. + * + * @param oldClusters The clusters from the previous iteration of the clustering algorithm + * @param newClusters The clusters from the current iteration of the clustering algorithm + * @return true if the new clusters should be rendered on the map, and false if they should + * not. + */ + @Override + protected boolean shouldRender(@NonNull Set> oldClusters, @NonNull Set> newClusters) { + if (crossedZoomThreshold(oldZoom, zoom)) { + // Render when the zoom level crosses the threshold, even if the clusters don't change + return true; + } else { + // If clusters didn't change, skip render for optimization + return !newClusters.equals(oldClusters); + } + } + + /** + * Returns true if the transition between the two zoom levels crossed a defined threshold, + * false if it did not. + * + * @param oldZoom zoom level from the previous time the camera stopped moving + * @param newZoom zoom level from the most recent time the camera stopped moving + * @return true if the transition between the two zoom levels crossed a defined threshold, + * false if it did not. + */ + private boolean crossedZoomThreshold(Float oldZoom, Float newZoom) { + if (oldZoom == null || newZoom == null) { + return true; + } + return (oldZoom < ZOOM_THRESHOLD && newZoom > ZOOM_THRESHOLD) || + (oldZoom > ZOOM_THRESHOLD && newZoom < ZOOM_THRESHOLD); + } + } +} From e3fab0fcde178143d03e37606c1cba0afaf94ff6 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Wed, 29 Sep 2021 20:55:24 -0400 Subject: [PATCH 11/19] docs: Update text --- demo/build.gradle | 4 ++-- .../android/utils/demo/ZoomClusteringDemoActivity.java | 8 +++++--- .../android/utils/demo/ZoomClusteringDemoActivity.java | 8 +++++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/demo/build.gradle b/demo/build.gradle index 63b5cbcf0..dc6e1a3b8 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -68,8 +68,8 @@ dependencies { // GMS gmsImplementation 'com.google.android.gms:play-services-maps:17.0.1' - gmsImplementation 'com.google.maps.android:android-maps-utils:2.2.6' - // gmsImplementation project(':library') // Uncomment this and comment above lines to use the library project code instead of artifact + // gmsImplementation 'com.google.maps.android:android-maps-utils:2.2.6' + gmsImplementation project(':library') // Uncomment this and comment above lines to use the library project code instead of artifact // V3 v3Implementation 'com.google.android.libraries.maps:maps:3.1.0-beta' diff --git a/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java index 96408875e..5456d6c59 100644 --- a/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java +++ b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java @@ -99,9 +99,11 @@ protected void startDemo(boolean isRestore) { clusterManager.setOnClusterItemClickListener(this); clusterManager.setOnClusterItemInfoWindowClickListener(this); + String snippet = "This item wouldn't have changed to a marker if we didn't override shouldRenderAsCluster() and shouldRender()"; + // Add items - clusterManager.addItem(new MyItem(18.528146, 73.797726, "Loc1", "1st location")); - clusterManager.addItem(new MyItem(18.545723, 73.917202, "Loc2", "2nd location")); + clusterManager.addItem(new MyItem(18.528146, 73.797726, "Loc1", snippet)); + clusterManager.addItem(new MyItem(18.545723, 73.917202, "Loc2", snippet)); } private class ZoomBasedRenderer extends DefaultClusterRenderer implements GoogleMap.OnCameraIdleListener { @@ -139,7 +141,7 @@ public void onCameraIdle() { */ @Override protected boolean shouldRenderAsCluster(@NonNull Cluster cluster) { - // Show cluster when zoom is less than the threshold, otherwise show as marker + // Show as cluster when zoom is less than the threshold, otherwise show as marker return zoom < ZOOM_THRESHOLD; } diff --git a/demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java b/demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java index d2420be3c..f8f1c7eb0 100644 --- a/demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java +++ b/demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java @@ -106,9 +106,11 @@ protected void startDemo(boolean isRestore) { clusterManager.setOnClusterItemClickListener(this); clusterManager.setOnClusterItemInfoWindowClickListener(this); + String snippet = "This item wouldn't have changed to a marker if we didn't override shouldRenderAsCluster() and shouldRender()"; + // Add items - clusterManager.addItem(new MyItem(18.528146, 73.797726, "Loc1", "1st location")); - clusterManager.addItem(new MyItem(18.545723, 73.917202, "Loc2", "2nd location")); + clusterManager.addItem(new MyItem(18.528146, 73.797726, "Loc1", snippet)); + clusterManager.addItem(new MyItem(18.545723, 73.917202, "Loc2", snippet)); } private class ZoomBasedRenderer extends DefaultClusterRenderer implements GoogleMap.OnCameraIdleListener { @@ -146,7 +148,7 @@ public void onCameraIdle() { */ @Override protected boolean shouldRenderAsCluster(@NonNull Cluster cluster) { - // Show cluster when zoom is less than the threshold, otherwise show as marker + // Show as cluster when zoom is less than the threshold, otherwise show as marker return zoom < ZOOM_THRESHOLD; } From 81c75dbf2d134b66ca35dff0554da3253d72deac Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Wed, 29 Sep 2021 20:56:16 -0400 Subject: [PATCH 12/19] chore: Revert dependency changes --- demo/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/build.gradle b/demo/build.gradle index dc6e1a3b8..8efde489d 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -68,14 +68,14 @@ dependencies { // GMS gmsImplementation 'com.google.android.gms:play-services-maps:17.0.1' - // gmsImplementation 'com.google.maps.android:android-maps-utils:2.2.6' - gmsImplementation project(':library') // Uncomment this and comment above lines to use the library project code instead of artifact + gmsImplementation 'com.google.maps.android:android-maps-utils:2.2.6' + // gmsImplementation project(':library') // Uncomment this and comment above line to use the library project code instead of artifact // V3 v3Implementation 'com.google.android.libraries.maps:maps:3.1.0-beta' v3Implementation 'com.android.volley:volley:1.2.1' // TODO - Remove this after Maps SDK v3 beta includes Volley versions on Maven Central v3Implementation 'com.google.maps.android:android-maps-utils-v3:2.2.6' - // v3Implementation project(':library') // Uncomment this and comment above lines to use the library project code instead of artifact + // v3Implementation project(':library') // Uncomment this and comment above line to use the library project code instead of artifact v3Implementation 'androidx.multidex:multidex:2.0.1' implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" From 270c8c253bbc04c603b8549ec1437f665ae3dc6e Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Thu, 30 Sep 2021 13:58:54 -0400 Subject: [PATCH 13/19] chore: Tweak tutorial text --- .../maps/android/utils/demo/ZoomClusteringDemoActivity.java | 2 +- .../maps/android/utils/demo/ZoomClusteringDemoActivity.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java index 5456d6c59..ffc1e4303 100644 --- a/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java +++ b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java @@ -99,7 +99,7 @@ protected void startDemo(boolean isRestore) { clusterManager.setOnClusterItemClickListener(this); clusterManager.setOnClusterItemInfoWindowClickListener(this); - String snippet = "This item wouldn't have changed to a marker if we didn't override shouldRenderAsCluster() and shouldRender()"; + String snippet = "This item wouldn't have changed to a marker if we didn't override shouldRenderAsCluster() AND shouldRender()"; // Add items clusterManager.addItem(new MyItem(18.528146, 73.797726, "Loc1", snippet)); diff --git a/demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java b/demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java index f8f1c7eb0..63bbed005 100644 --- a/demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java +++ b/demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java @@ -106,7 +106,7 @@ protected void startDemo(boolean isRestore) { clusterManager.setOnClusterItemClickListener(this); clusterManager.setOnClusterItemInfoWindowClickListener(this); - String snippet = "This item wouldn't have changed to a marker if we didn't override shouldRenderAsCluster() and shouldRender()"; + String snippet = "This item wouldn't have changed to a marker if we didn't override shouldRenderAsCluster() AND shouldRender()"; // Add items clusterManager.addItem(new MyItem(18.528146, 73.797726, "Loc1", snippet)); From b04c39090be08829e824e9d52af4e67b138423a6 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Thu, 30 Sep 2021 14:57:41 -0400 Subject: [PATCH 14/19] docs: Update Javadocs in demo app --- .../maps/android/utils/demo/ZoomClusteringDemoActivity.java | 5 +++-- .../maps/android/utils/demo/ZoomClusteringDemoActivity.java | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java index ffc1e4303..9b21d3e09 100644 --- a/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java +++ b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java @@ -117,8 +117,9 @@ public ZoomBasedRenderer(Context context, GoogleMap map, ClusterManager /** * The {@link ClusterManager} will call the {@link this.onCameraIdle()} implementation of - * any Renderer before clustering and rendering takes place. This allows us to - * capture metrics that may be useful for clustering, such as the zoom level. + * any Renderer that implements {@link GoogleMap.OnCameraIdleListener} before + * clustering and rendering takes place. This allows us to capture metrics that may be + * useful for clustering, such as the zoom level. */ @Override public void onCameraIdle() { diff --git a/demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java b/demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java index 63bbed005..c6adb83e2 100644 --- a/demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java +++ b/demo/src/v3/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java @@ -124,8 +124,9 @@ public ZoomBasedRenderer(Context context, GoogleMap map, ClusterManager /** * The {@link ClusterManager} will call the {@link this.onCameraIdle()} implementation of - * any Renderer before clustering and rendering takes place. This allows us to - * capture metrics that may be useful for clustering, such as the zoom level. + * any Renderer that implements {@link GoogleMap.OnCameraIdleListener} before + * clustering and rendering takes place. This allows us to capture metrics that may be + * useful for clustering, such as the zoom level. */ @Override public void onCameraIdle() { From fa80a458dcf73749a5e35311aa6ea78bb27ed484 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Thu, 14 Oct 2021 17:53:52 -0400 Subject: [PATCH 15/19] chore: Depend on local library code instead of release artifact --- demo/build.gradle | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/demo/build.gradle b/demo/build.gradle index 8efde489d..b5261b1ea 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -68,14 +68,12 @@ dependencies { // GMS gmsImplementation 'com.google.android.gms:play-services-maps:17.0.1' - gmsImplementation 'com.google.maps.android:android-maps-utils:2.2.6' - // gmsImplementation project(':library') // Uncomment this and comment above line to use the library project code instead of artifact + gmsImplementation project(':library') // V3 v3Implementation 'com.google.android.libraries.maps:maps:3.1.0-beta' v3Implementation 'com.android.volley:volley:1.2.1' // TODO - Remove this after Maps SDK v3 beta includes Volley versions on Maven Central - v3Implementation 'com.google.maps.android:android-maps-utils-v3:2.2.6' - // v3Implementation project(':library') // Uncomment this and comment above line to use the library project code instead of artifact + v3Implementation project(':library') v3Implementation 'androidx.multidex:multidex:2.0.1' implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" From de0ada74be31f4ab920e45114e398fe199d041a3 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Thu, 14 Oct 2021 18:12:25 -0400 Subject: [PATCH 16/19] chore: Fix v3 library reference --- demo/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/build.gradle b/demo/build.gradle index b5261b1ea..1ec5a9527 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -73,7 +73,7 @@ dependencies { // V3 v3Implementation 'com.google.android.libraries.maps:maps:3.1.0-beta' v3Implementation 'com.android.volley:volley:1.2.1' // TODO - Remove this after Maps SDK v3 beta includes Volley versions on Maven Central - v3Implementation project(':library') + v3Implementation project(':library-v3') v3Implementation 'androidx.multidex:multidex:2.0.1' implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" From 1cc0dd300ef32cddc6c11a42a09aa7752abf5a59 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Fri, 15 Oct 2021 15:06:13 -0400 Subject: [PATCH 17/19] Update library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java Co-authored-by: Chris Arriola --- .../maps/android/clustering/view/DefaultClusterRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java b/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java index 317897c04..2b7ffcf9b 100644 --- a/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java +++ b/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java @@ -366,7 +366,7 @@ protected boolean shouldRenderAsCluster(@NonNull Cluster cluster) { * a certain zoom level and as a marker below a certain zoom level (even if the contents of the * clusters themselves did not change). In this case, you could check the zoom level in an * implementation of this method and if that zoom level threshold is crossed return true, else - * {@code return !newClusters.equals(oldClusters)}. + * {@code return super.shouldRender(oldClusters, newClusters)}. * * Note that always returning true from this method could potentially have negative performance * implications as clusters will be re-rendered on each pass even if they don't change. From d2fcc79803b12e49dc6cd58f00420a99229e4012 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Fri, 15 Oct 2021 15:09:24 -0400 Subject: [PATCH 18/19] chore: Call super() in demo app in case library impl changes Per https://github.com/googlemaps/android-maps-utils/pull/996#discussion_r728489536 --- .../maps/android/utils/demo/ZoomClusteringDemoActivity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java index 9b21d3e09..83c5a3b29 100644 --- a/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java +++ b/demo/src/gms/java/com/google/maps/android/utils/demo/ZoomClusteringDemoActivity.java @@ -172,8 +172,8 @@ protected boolean shouldRender(@NonNull Set> oldCluste // Render when the zoom level crosses the threshold, even if the clusters don't change return true; } else { - // If clusters didn't change, skip render for optimization - return !newClusters.equals(oldClusters); + // If clusters didn't change, skip render for optimization using default super implementation + return super.shouldRender(oldClusters, newClusters); } } From f61c81bc14fe56967fa29bab2c9d922ec2af99ff Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Fri, 15 Oct 2021 17:32:28 -0400 Subject: [PATCH 19/19] chore: Enforce immutability for sets passed to overridable method --- .../android/clustering/view/DefaultClusterRenderer.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java b/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java index 2b7ffcf9b..5854486be 100644 --- a/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java +++ b/library/src/main/java/com/google/maps/android/clustering/view/DefaultClusterRenderer.java @@ -430,7 +430,7 @@ public void setMapZoom(float zoom) { @SuppressLint("NewApi") public void run() { - if (!shouldRender(DefaultClusterRenderer.this.mClusters, clusters)) { + if (!shouldRender(immutableOf(DefaultClusterRenderer.this.mClusters), immutableOf(clusters))) { mCallback.run(); return; } @@ -575,6 +575,10 @@ public void setAnimation(boolean animate) { mAnimate = animate; } + private Set> immutableOf(Set> clusters) { + return clusters != null ? Collections.unmodifiableSet(clusters) : Collections.emptySet(); + } + private static double distanceSquared(Point a, Point b) { return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y); }