From ac53c335ca0e33f608c82d71f121dcbc66a3d03a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Tue, 26 Aug 2025 15:29:45 +0200 Subject: [PATCH 1/3] docs: adding sample with Clustering Algorithms --- demo/src/main/AndroidManifest.xml | 3 + .../demo/ClusterAlgorithmsDemoActivity.kt | 152 ++++++++++++++++++ .../maps/android/utils/demo/MainActivity.java | 3 +- .../activity_cluster_algorithms_demo.xml | 20 +++ demo/src/main/res/values/arrays.xml | 27 ++++ 5 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 demo/src/main/java/com/google/maps/android/utils/demo/ClusterAlgorithmsDemoActivity.kt create mode 100644 demo/src/main/res/layout/activity_cluster_algorithms_demo.xml create mode 100644 demo/src/main/res/values/arrays.xml diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index a504a7619..b1b387dce 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -124,6 +124,9 @@ + diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/ClusterAlgorithmsDemoActivity.kt b/demo/src/main/java/com/google/maps/android/utils/demo/ClusterAlgorithmsDemoActivity.kt new file mode 100644 index 000000000..b6ddc2e7e --- /dev/null +++ b/demo/src/main/java/com/google/maps/android/utils/demo/ClusterAlgorithmsDemoActivity.kt @@ -0,0 +1,152 @@ +/* + * Copyright 2025 Google LLC + * + * 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.view.View +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Spinner +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.MapView +import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.clustering.ClusterManager +import com.google.maps.android.clustering.algo.CentroidNonHierarchicalDistanceBasedAlgorithm +import com.google.maps.android.clustering.algo.ContinuousZoomEuclideanCentroidAlgorithm +import com.google.maps.android.clustering.algo.GridBasedAlgorithm +import com.google.maps.android.clustering.algo.NonHierarchicalDistanceBasedAlgorithm +import com.google.maps.android.clustering.algo.NonHierarchicalViewBasedAlgorithm +import com.google.maps.android.utils.demo.model.MyItem +import kotlin.random.Random + +/** + * A demo activity that showcases the various clustering algorithms + * available in the library. + */ +class ClusterAlgorithmsDemoActivity : BaseDemoActivity() { + + private var clusterManager: ClusterManager? = null + private lateinit var mapView: MapView + + override fun getLayoutId(): Int { + return R.layout.activity_cluster_algorithms_demo + } + + override fun startDemo(isRestore: Boolean) { + // The MapView is needed for the NonHierarchicalViewBasedAlgorithm. + mapView = findViewById(R.id.map) + + if (!isRestore) { + map.moveCamera( + CameraUpdateFactory.newLatLngZoom( + LatLng(51.503186, -0.126446), + 10f + ) + ) + } + + setupSpinner() + + setupClusterer(0) + } + + private fun setupSpinner() { + val spinner: Spinner = findViewById(R.id.algorithm_spinner) + val adapter = ArrayAdapter.createFromResource( + this, + R.array.clustering_algorithms, + android.R.layout.simple_spinner_item + ) + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + spinner.adapter = adapter + spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { + setupClusterer(position) + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + // Do nothing + } + } + } + + /** + * Sets up the ClusterManager with the chosen algorithm and populates it with items. + */ + private fun setupClusterer(algorithmPosition: Int) { + // 1. Clear the map and previous cluster manager + map.clear() + clusterManager = null + + // 2. Initialize a new ClusterManager, using getMap() from BaseDemoActivity + clusterManager = ClusterManager(this, map) + + // 3. Set the desired algorithm based on the spinner position + when (algorithmPosition) { + 1 -> { + clusterManager?.algorithm = GridBasedAlgorithm() + } + + 2 -> { + clusterManager?.algorithm = NonHierarchicalDistanceBasedAlgorithm() + } + + 3 -> { + clusterManager?.algorithm = CentroidNonHierarchicalDistanceBasedAlgorithm() + } + + 4 -> { + clusterManager?.algorithm = NonHierarchicalViewBasedAlgorithm( + mapView.width, mapView.height + ) + } + + 5 -> { + clusterManager?.algorithm = ContinuousZoomEuclideanCentroidAlgorithm() + } + + else -> { // Default + + } + } + + // 4. Point the map's listeners to the ClusterManager + map.setOnCameraIdleListener(clusterManager) + map.setOnMarkerClickListener(clusterManager) + + // 5. Add cluster items to the manager + addItems() + + // 6. Trigger the initial clustering + clusterManager?.cluster() + } + + private fun addItems() { + val items = mutableListOf() + // Add 100 random items in the map region + for (i in 0..99) { + val lat = 51.5145 + (Random.nextDouble() - 0.5) / 2.0 + val lng = -0.1245 + (Random.nextDouble() - 0.5) / 2.0 + items.add(MyItem(lat, lng, "Marker #$i", "Snippet for marker #$i")) + } + clusterManager?.addItems(items) + } +} \ No newline at end of file diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.java b/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.java index 4f22adbfc..1d4b866fb 100644 --- a/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.java +++ b/demo/src/main/java/com/google/maps/android/utils/demo/MainActivity.java @@ -36,8 +36,9 @@ protected void onCreate(Bundle savedInstanceState) { mListView = findViewById(R.id.list); - addDemo("Clustering", ClusteringDemoActivity.class); addDemo("Advanced Markers Clustering Example", CustomAdvancedMarkerClusteringDemoActivity.class); + addDemo("Cluster Algorithms", ClusterAlgorithmsDemoActivity.class); + addDemo("Clustering", ClusteringDemoActivity.class); addDemo("Clustering: Custom Look", CustomMarkerClusteringDemoActivity.class); addDemo("Clustering: Diff", ClusteringDiffDemoActivity.class); addDemo("Clustering: 2K markers", BigClusteringDemoActivity.class); diff --git a/demo/src/main/res/layout/activity_cluster_algorithms_demo.xml b/demo/src/main/res/layout/activity_cluster_algorithms_demo.xml new file mode 100644 index 000000000..a5fb12113 --- /dev/null +++ b/demo/src/main/res/layout/activity_cluster_algorithms_demo.xml @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/demo/src/main/res/values/arrays.xml b/demo/src/main/res/values/arrays.xml new file mode 100644 index 000000000..192ac984e --- /dev/null +++ b/demo/src/main/res/values/arrays.xml @@ -0,0 +1,27 @@ + + + + + + Default + Grid-based + Distance-based + Distance-based (Centroid) + View-based + Continuous Zoom (Centroid) + + \ No newline at end of file From 81dbf3e4a750c82d4aa86f128125948898e9ef10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Tue, 26 Aug 2025 15:34:37 +0200 Subject: [PATCH 2/3] docs: header --- .../layout/activity_cluster_algorithms_demo.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/demo/src/main/res/layout/activity_cluster_algorithms_demo.xml b/demo/src/main/res/layout/activity_cluster_algorithms_demo.xml index a5fb12113..fbb63a6ba 100644 --- a/demo/src/main/res/layout/activity_cluster_algorithms_demo.xml +++ b/demo/src/main/res/layout/activity_cluster_algorithms_demo.xml @@ -1,4 +1,20 @@ + + From 0cfa400a3f25bd03ce455bbfd908c07702f0699a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Lo=CC=81pez=20Man=CC=83as?= Date: Thu, 28 Aug 2025 09:19:36 +0200 Subject: [PATCH 3/3] docs: comments from PR --- .../demo/ClusterAlgorithmsDemoActivity.kt | 60 ++++++------------- .../activity_cluster_algorithms_demo.xml | 9 ++- 2 files changed, 24 insertions(+), 45 deletions(-) diff --git a/demo/src/main/java/com/google/maps/android/utils/demo/ClusterAlgorithmsDemoActivity.kt b/demo/src/main/java/com/google/maps/android/utils/demo/ClusterAlgorithmsDemoActivity.kt index b6ddc2e7e..93e275907 100644 --- a/demo/src/main/java/com/google/maps/android/utils/demo/ClusterAlgorithmsDemoActivity.kt +++ b/demo/src/main/java/com/google/maps/android/utils/demo/ClusterAlgorithmsDemoActivity.kt @@ -24,6 +24,7 @@ import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.MapView import com.google.android.gms.maps.model.LatLng import com.google.maps.android.clustering.ClusterManager +import com.google.maps.android.clustering.algo.AbstractAlgorithm import com.google.maps.android.clustering.algo.CentroidNonHierarchicalDistanceBasedAlgorithm import com.google.maps.android.clustering.algo.ContinuousZoomEuclideanCentroidAlgorithm import com.google.maps.android.clustering.algo.GridBasedAlgorithm @@ -46,14 +47,11 @@ class ClusterAlgorithmsDemoActivity : BaseDemoActivity() { } override fun startDemo(isRestore: Boolean) { - // The MapView is needed for the NonHierarchicalViewBasedAlgorithm. - mapView = findViewById(R.id.map) if (!isRestore) { map.moveCamera( CameraUpdateFactory.newLatLngZoom( - LatLng(51.503186, -0.126446), - 10f + LatLng(51.503186, -0.126446), 10f ) ) } @@ -66,18 +64,13 @@ class ClusterAlgorithmsDemoActivity : BaseDemoActivity() { private fun setupSpinner() { val spinner: Spinner = findViewById(R.id.algorithm_spinner) val adapter = ArrayAdapter.createFromResource( - this, - R.array.clustering_algorithms, - android.R.layout.simple_spinner_item + this, R.array.clustering_algorithms, android.R.layout.simple_spinner_item ) adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) spinner.adapter = adapter spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected( - parent: AdapterView<*>?, - view: View?, - position: Int, - id: Long + parent: AdapterView<*>?, view: View?, position: Int, id: Long ) { setupClusterer(position) } @@ -94,59 +87,40 @@ class ClusterAlgorithmsDemoActivity : BaseDemoActivity() { private fun setupClusterer(algorithmPosition: Int) { // 1. Clear the map and previous cluster manager map.clear() - clusterManager = null // 2. Initialize a new ClusterManager, using getMap() from BaseDemoActivity clusterManager = ClusterManager(this, map) // 3. Set the desired algorithm based on the spinner position - when (algorithmPosition) { - 1 -> { - clusterManager?.algorithm = GridBasedAlgorithm() - } - - 2 -> { - clusterManager?.algorithm = NonHierarchicalDistanceBasedAlgorithm() - } - - 3 -> { - clusterManager?.algorithm = CentroidNonHierarchicalDistanceBasedAlgorithm() - } - - 4 -> { - clusterManager?.algorithm = NonHierarchicalViewBasedAlgorithm( - mapView.width, mapView.height - ) - } - - 5 -> { - clusterManager?.algorithm = ContinuousZoomEuclideanCentroidAlgorithm() - } - - else -> { // Default - - } + clusterManager?.algorithm = when (algorithmPosition) { + 1 -> GridBasedAlgorithm() + 2 -> NonHierarchicalDistanceBasedAlgorithm() + 3 -> CentroidNonHierarchicalDistanceBasedAlgorithm() + 4 -> NonHierarchicalViewBasedAlgorithm(mapView.width, mapView.height) + 5 -> ContinuousZoomEuclideanCentroidAlgorithm() + else -> error("Unsupported algorithm position: $algorithmPosition") } // 4. Point the map's listeners to the ClusterManager map.setOnCameraIdleListener(clusterManager) map.setOnMarkerClickListener(clusterManager) - // 5. Add cluster items to the manager - addItems() + // 5. Generate and add cluster items to the manager + val items = generateItems() + clusterManager?.addItems(items) // 6. Trigger the initial clustering clusterManager?.cluster() } - private fun addItems() { + private fun generateItems(): List { val items = mutableListOf() // Add 100 random items in the map region - for (i in 0..99) { + for (i in 0 until 100) { val lat = 51.5145 + (Random.nextDouble() - 0.5) / 2.0 val lng = -0.1245 + (Random.nextDouble() - 0.5) / 2.0 items.add(MyItem(lat, lng, "Marker #$i", "Snippet for marker #$i")) } - clusterManager?.addItems(items) + return items } } \ No newline at end of file diff --git a/demo/src/main/res/layout/activity_cluster_algorithms_demo.xml b/demo/src/main/res/layout/activity_cluster_algorithms_demo.xml index fbb63a6ba..4d8d1ae98 100644 --- a/demo/src/main/res/layout/activity_cluster_algorithms_demo.xml +++ b/demo/src/main/res/layout/activity_cluster_algorithms_demo.xml @@ -19,10 +19,15 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + + android:layout_height="match_parent" + map:mapId="mapId" /> +