티스토리 뷰

반응형

구글 맵 클러스터링

화장실 찾기까지 하였지만 화장실이 너무 많아서 렉도 걸리고 화면에 화장실이 겹쳐진 모양으로 가득 차게 됩니다.

구글 맵 클러스터링은 이러한 문제를 보완하여 여러개 겹친 경우 원의 숫자로 보여줍니다.

 

 

시작하기

구글 맵 클러스터링을 구현하기 위해서는 크게 ClusterItem 인터페이스와 ClusterManager 클래스가 필요합니다

ClusterItem 인터페이스는 화면에 표시되는 정보를 제공하고 ClusterManager 클래스는 마커로 표시할지 원으로 표시할지를 관리합니다.

 

추가로 마커의 이미지를 변경하고 싶다면 ClusterRenderer 클래스도 사용해야 합니다.

 

 

 

ClusterItem 인터페이스인 MyItem.kt

 

class MyItem(private val position: LatLng, private val title: String, private val snippet: String, private val icon: BitmapDescriptor) : ClusterItem {

    override fun getSnippet(): String = snippet

    override fun getTitle(): String = title

    override fun getPosition(): LatLng = position
    
    fun getIcon(): BitmapDescriptor = icon
}

 

생성자에게 전달받은 데이터를 반환할 수 있게 구현되어있습니다.

 

getIcon은 ClusterItem의 오버라이드 함수가 아니지만 아이콘 변경을 위해 구현하였습니다.

 

 

 

그리고 나중에 검색 기능 구현 때 필요한 아래 함수 두 개를 추가해줍니다.

 

class MyItem(private val position: LatLng, private val title: String, private val snippet: String, private val icon: BitmapDescriptor) : ClusterItem {

    override fun equals(other: Any?): Boolean {

        if(other is MyItem)
            return (other.position.latitude == position.latitude
                    && other.position.longitude == position.longitude
                    && other.title == title
                    && other.snippet == snippet)

        return true
    }

    override fun hashCode(): Int {
        var hash = position.latitude.hashCode() * 31

        hash = hash * 31 + position.longitude.hashCode()
        hash = hash * 31 + title.hashCode()
        hash = hash * 31 + snippet.hashCode()

        return hash
    }

}

검색했을 때 위도, 경도, 제목, 설명이 모두 같으면 같은 객체로 취급하기 위해서 equals 함수를 구현했습니다.

그리고 hashCode는 equals를 오버라이드 한경우 반드시 오버라이드 해줘야 합니다.

 

 

ClusterRenderer 클래스 구현하기

 

클러스터링 기능은 알고리즘, 렌더러 이렇게 두 가지로 나눠져있습니다. 

ClusterManager의 setAlgorithm과 setRenderer 메서드로 커스텀 클래스를 등록할 수 있습니다.

하지만 DefaultClusterRenderer NonHierarchicalDistanceBasedAlgorithm을 사용할 수도 있습니다.

여기에서는 DefaultClusterRenderer를 사용합니다.

 

class ClusterRenderer(context: Context?, map: GoogleMap?, clusterManager: ClusterManager<MyItem>?): DefaultClusterRenderer<MyItem>(context, map, clusterManager) {
    
    init {
        clusterManager?.renderer = this
    }

    override fun onBeforeClusterItemRendered(item: MyItem?, markerOptions: MarkerOptions?) {
        
        markerOptions?.icon(item?.getIcon())
        markerOptions?.visible(true)
    }
}

 

ClusterManager 클래스의 렌더러를 자신으로 초기화해주고, 클러스터 아이템이 랜더링 되기 전에 호출되는 함수인 onBeforeClusterItemRendered 함수에서 마커의 아이콘을 지정해줍니다.

 

 

구글 맵에 ClusterManager 연동하기

 

MainActivity에 있는 initMap과 addMarkers, AsyncTask를 아래와 같이 수정합니다.

 

 

initMap

class MainActivity : AppCompatActivity() {

    var clusterManager: ClusterManager<MyItem>? = null
    var clusterRenderer: ClusterRenderer? = null

    @SuppressLint("MissingPermission")
    fun initMap() {
        mapView.getMapAsync {
            
            clusterManager = ClusterManager(this, it)
            clusterRenderer = ClusterRenderer(this, it, clusterManager)
            
            it.setOnCameraIdleListener(clusterManager)
            it.setOnMarkerClickListener(clusterManager)

            googleMap = it
            it.uiSettings.isMyLocationButtonEnabled = false

            when {
                checkPermissions() -> {
                    it.isMyLocationEnabled = true
                    it.moveCamera(CameraUpdateFactory.newLatLngZoom(getMyLocation(), DEFAULT_ZOOM_LEVEL))
                }
                else -> {
                    it.moveCamera(CameraUpdateFactory.newLatLngZoom(CITY_HALL, DEFAULT_ZOOM_LEVEL))
                }
            }
        }
    }
}

 

위와 같이 OnCameraChangeListener로 ClusterManager를 지정해줘야 클러스터링이 실행됩니다.

 

 

 

addMarkers

class MainActivity : AppCompatActivity() {

    fun addMarkers(toilet: JSONObject) {
        
        clusterManager?.addItem(
            MyItem(
                LatLng(toilet.getDouble("Y_WGS84"), toilet.getDouble("X_WGS84")),
                toilet.getString("FNAME"),
                toilet.getString("ANAME"),
                BitmapDescriptorFactory.fromBitmap(bitmap)
            )
        )
    }
}

 

AsyncTask의 onProgressUpdate

class MainActivity : AppCompatActivity() {

    override fun onProgressUpdate(vararg values: JSONArray?) {

            val array = values[0]
            array?.let {
                for (i in 0 until array.length()) {
                    addMarkers(array.getJSONObject(i))
                }
            }

            clusterManager?.cluster()
        }
    }
}

 

실행을 하게 되면 구글 맵 클러스터링이 적용된 깔끔한 지도 앱을 볼 수 있습니다.

 

 

예제 코드

https://github.com/Im-Tae/Blog_Example/tree/master/Android/Google_Map_Example

반응형
댓글