Hocine Elhadj
Hocine Elhadj

Reputation: 105

Google map clustering issue on Jetpack Compose

I am trying to develop an app that uses Google Map to show some locations. I tried to implement markers clustering for markers that are very close. However, by doing so the marker click in order to show an info window doesn't work anymore. If anyone can show me a trun around I would be so thankful. Here is my code.

GoogleMap(
            modifier = Modifier
                .fillMaxSize(),
            cameraPositionState = cameraPositionState,
            uiSettings = uiSettings,
            onMapClick = {
                onMapClick(it)
            }
        ) {
            var clusterManager by remember {
                mutableStateOf<ClusterManager<CustomMarkerState>?>(
                    null
                )
            }
            MapEffect(listOfMarkers) { map ->
                if (clusterManager == null) {
                    clusterManager = ClusterManager<CustomMarkerState>(context, map)
                }
                clusterManager?.addItems(listOfMarkers)
            }
            LaunchedEffect(key1 = cameraPositionState.isMoving) {
                if (!cameraPositionState.isMoving) {
                    clusterManager?.onCameraIdle()
                }
            }
            if (listOfMarkers.isNotEmpty()) {
                listOfMarkers.forEach { marker ->
                        Marker(
                            state = MarkerState(
                                position = LatLng(
                                    marker.latitude,
                                    marker.longitude
                                )
                            ),
                            title = marker.name,
                            snippet = marker.description,
                            onClick = {
                                isShown = true
                                selectedMarker = marker
                                return@Marker true
                            }
                        )
                    } 
                }
            }
        }

Upvotes: 3

Views: 1836

Answers (1)

Rizwan Rasheed
Rizwan Rasheed

Reputation: 386

Make Your own Cluster Rendering here is the complete solution

package com.google.maps.android.compose

import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.clustering.ClusterItem
import com.google.maps.android.clustering.ClusterManager

private val singapore = LatLng(1.35, 103.87)
private val singapore2 = LatLng(2.50, 103.87)

class MapClusteringActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            GoogleMapClustering()
        }
    }

    fun showToast(msg: String) {
        Toast.makeText(this@MapClusteringActivity, msg, Toast.LENGTH_SHORT).show()
    }


    @Composable
    fun GoogleMapClustering() {
        val items = remember { mutableStateListOf<MyItem>() }
        LaunchedEffect(Unit) {
            for (i in 1..10) {
                val position = LatLng(
                    singapore2.latitude + kotlin.random.Random.nextFloat(),
                    singapore2.longitude + kotlin.random.Random.nextFloat(),
                )
                items.add(MyItem(position, "Marker", "Snippet"))
            }

           
        }
        GoogleMapClustering(items = items)
    }

    @OptIn(MapsComposeExperimentalApi::class)
    @Composable
    fun GoogleMapClustering(items: List<MyItem>) {
        val cameraPositionState = rememberCameraPositionState {
            position = CameraPosition.fromLatLngZoom(singapore, 10f)
        }
        GoogleMap(
            modifier = Modifier.fillMaxSize(),
            cameraPositionState = cameraPositionState
        ) {
            val context = LocalContext.current
            var clusterManager by remember { mutableStateOf<ClusterManager<MyItem>?>(null) }
            MapEffect(items) { map ->
                if (clusterManager == null) {
                    clusterManager = ClusterManager<MyItem>(context, map)
                }
                clusterManager?.addItems(items)
                clusterManager?.renderer = MarkerClusterRender(context,map,clusterManager!!) {
                    showToast(it.itemTitle)
                }
            }
            LaunchedEffect(key1 = cameraPositionState.isMoving) {
                if (!cameraPositionState.isMoving) {
                    clusterManager?.onCameraIdle()
                }
            }
        }


    }
}

data class MyItem(
    val itemPosition: LatLng,
    val itemTitle: String,
    val itemSnippet: String,
) : ClusterItem {
    override fun getPosition(): LatLng =
        itemPosition

    override fun getTitle(): String =
        itemTitle

    override fun getSnippet(): String =
        itemSnippet
}

Custom Cluster Manager

package com.google.maps.android.compose

import android.content.Context
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.Marker
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

class MarkerClusterRender<T : ClusterItem>(
    var context: Context,
    var googleMap: GoogleMap,
    clusterManager: ClusterManager<T>,
    var onInfoWindowClick: (MyItem) -> Unit
) :
    DefaultClusterRenderer<T>(context, googleMap, clusterManager) {

    private var clusterMap: HashMap<String, Marker> = hashMapOf()

    override fun shouldRenderAsCluster(cluster: Cluster<T>): Boolean {
        return cluster.size > 1
    }

    override fun getBucket(cluster: Cluster<T>): Int {
        return cluster.size
    }

    override fun getClusterText(bucket: Int): String {
        return super.getClusterText(bucket).replace("+", "")
    }

    override fun onClusterItemRendered(clusterItem: T, marker: Marker) {
        super.onClusterItemRendered(clusterItem, marker)
        clusterMap[(clusterItem as MyItem).itemTitle] = marker

        setMarker((clusterItem as MyItem), marker)
    }

    private fun setMarker(poi: MyItem, marker: Marker?) {
        val markerColor = BitmapDescriptorFactory.HUE_RED
        marker?.let {
            it.tag = poi
            it.showInfoWindow()
            changeMarkerColor(it, markerColor)
        }
        googleMap.setOnInfoWindowClickListener {
            onInfoWindowClick(it.tag as MyItem)
        }
    }

    private fun getClusterMarker(itemId: String): Marker? {
        return if (clusterMap.containsKey(itemId)) clusterMap[itemId]
        else null
    }


    fun showRouteInfoWindow(key: String) {
        getClusterMarker(key)?.showInfoWindow()
    }

    private fun changeMarkerColor(marker: Marker, color: Float) {
        try {
            marker.setIcon(BitmapDescriptorFactory.defaultMarker(color));
        } catch (ex: IllegalArgumentException) {
            ex.printStackTrace()
        } catch (ex: Exception) {
            ex.printStackTrace()
        }
    }

}

Upvotes: 5

Related Questions