Skip to content

Commit

Permalink
#267 feat : 클러스터링 첫 적용
Browse files Browse the repository at this point in the history
  • Loading branch information
BENDENG1 committed Feb 8, 2024
1 parent c86586f commit 650b4dd
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import com.avengers.nibobnebob.presentation.ui.toRestaurantDetail
import com.avengers.nibobnebob.presentation.util.Constants.NEAR_RESTAURANT
import com.avengers.presentation.R
import com.avengers.presentation.databinding.FragmentHomeBinding
import com.avengers.presentation.ui.main.home.Cluster
import com.avengers.presentation.ui.main.home.HomeEvents
import com.avengers.presentation.ui.main.home.HomeViewModel
import com.avengers.presentation.ui.main.home.TrackingState
import com.naver.maps.geometry.LatLng
import com.naver.maps.map.CameraAnimation
import com.naver.maps.map.CameraPosition
Expand Down Expand Up @@ -142,12 +146,19 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home),
.apply { animate(CameraAnimation.Fly, 500) }

naverMap.moveCamera(cameraUpdate)
}

private fun handleCluster() {
removeAllMarker()
viewModel.uiState.value.markerList.forEach { data ->
setMarker(data)
}
viewModel.uiState.value.clusterList.forEach { cluster ->
setCluster(cluster)
}
}


private fun initMapView() {
val mapFragment = childFragmentManager.findFragmentById(R.id.map_fragment) as MapFragment?
?: MapFragment.newInstance().also {
Expand Down Expand Up @@ -177,7 +188,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home),

// todo 화면 이동시 리스너
naverMap.addOnCameraChangeListener { _, _ ->

}

naverMap.addOnCameraIdleListener {
Expand All @@ -187,6 +197,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home),
cameraPosition.target.longitude,
cameraPosition.zoom
)
handleCluster()
}

naverMap.addOnLocationChangeListener {
Expand Down Expand Up @@ -216,12 +227,12 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home),
}

private fun setMarker(data: UiRestaurantData) {
if (data.isClustered)
return
val marker = Marker()

marker.position = LatLng(data.latitude, data.longitude)

// Log.d("marker test--", "${viewModel.uiState.value.curFilter}..${NEAR_RESTAURANT}.. ")

marker.icon = if (viewModel.uiState.value.curFilter == NEAR_RESTAURANT)
OverlayImage.fromResource(R.drawable.ic_marker_near)
else
Expand All @@ -244,6 +255,20 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home),
markerList.add(marker)
}

private fun setCluster(cluster: Cluster) {
val marker = Marker()

marker.position = LatLng(cluster.centerLatitude, cluster.centerLongitude)
marker.icon = OverlayImage.fromResource(R.drawable.ic_marker)
marker.map = naverMap

marker.setOnClickListener {
viewModel.onClusterClick(cluster)
true
}
markerList.add(marker)
}

private fun setSingleMarker(marker: Marker?, item: UiRestaurantData) {

marker?.setOnClickListener {
Expand All @@ -261,15 +286,22 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home),

}


// todo 모든 marker 데이터 markerList 에 저장해 놨다가, remove 다음 방식으로 진행
private fun removeAllMarker() {
markerList.forEach {
it.map = null
}
markerList.clear()
}

private fun removeMarker(marker: Marker) {
markerList.remove(marker)
marker.map = null
}

fun addMarker(marker: Marker) {
markerList.add(marker)
}

private fun finishApp() {
var backPressTime = 0L
requireActivity().onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.avengers.nibobnebob.presentation.ui.main.home
package com.avengers.presentation.ui.main.home


import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.avengers.nibobnebob.domain.model.base.BaseState
Expand All @@ -16,6 +17,7 @@ import com.avengers.nibobnebob.presentation.ui.main.home.model.UiRestaurantData
import com.avengers.nibobnebob.presentation.util.Constants.ERROR_MSG
import com.avengers.nibobnebob.presentation.util.Constants.MY_LIST
import com.avengers.nibobnebob.presentation.util.Constants.NEAR_RESTAURANT
import com.avengers.presentation.util.DistanceUtil.haversineDistance
import com.naver.maps.geometry.LatLng
import com.naver.maps.map.overlay.Marker
import dagger.hilt.android.lifecycle.HiltViewModel
Expand All @@ -33,17 +35,14 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.lang.Math.pow
import javax.inject.Inject
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
import kotlin.math.pow

data class HomeUiState(
val locationTrackingState: TrackingState = TrackingState.TryOn,
val filterList: List<UiFilterData> = emptyList(),
val markerList: List<UiRestaurantData> = emptyList(),
val clusterList: List<Cluster> = emptyList(),
val recommendList: List<UiRecommendRestaurantData> = emptyList(),
val curFilter: String = MY_LIST,
val cameraLatitude: Double = 37.553836,
Expand All @@ -56,6 +55,13 @@ data class HomeUiState(
val addRestaurantId: Int = 0
)

data class Cluster(
val centerLatitude: Double,
val centerLongitude: Double,
val markers: MutableList<UiRestaurantData>,
val clusterId: MutableList<Int>
)

sealed class TrackingState {
data object TryOn : TrackingState()
data object On : TrackingState()
Expand Down Expand Up @@ -87,7 +93,8 @@ class HomeViewModel @Inject constructor(
private val restaurantRepository: RestaurantRepository,
private val myRestaurantListUseCase: GetMyRestaurantListUseCase,
private val addWishRestaurantUseCase: AddWishRestaurantUseCase,
private val deleteMyWishRestaurantUseCase: DeleteMyWishRestaurantUseCase
private val deleteMyWishRestaurantUseCase: DeleteMyWishRestaurantUseCase,
private val clusterManager: ClusterManager
) : ViewModel() {

private val _uiState = MutableStateFlow(HomeUiState())
Expand All @@ -112,12 +119,47 @@ class HomeViewModel @Inject constructor(
fun updateCamera(latitude: Double, longitude: Double, zoom: Double) {
_uiState.update { state ->
state.copy(
cameraRadius = pow(2.0, 14.0 - uiState.value.cameraZoom) * 1000,
cameraRadius = 2.00.pow(14.00 - uiState.value.cameraZoom) * 1000,
cameraLatitude = latitude,
cameraLongitude = longitude,
cameraZoom = zoom
)
}
clusterManager.updateCluster(
uiState.value.markerList,
latitude,
longitude,
uiState.value.cameraRadius
)
updateCluster()
}

private fun updateCluster() {
val clusterList = clusterManager.getClusterList()

val updatedMarkerList = _uiState.value.markerList.map { marker ->
val isClustered = clusterList.any { cluster ->
cluster.markers.any { it.id == marker.id }
}
marker.copy(isClustered = isClustered)
}

_uiState.update { state ->
state.copy(
clusterList = clusterList,
markerList = updatedMarkerList
)
}
}

fun onClusterClick(cluster: Cluster) {
Log.d("클러스터 클릭", "클러스터 클릭")
val markersToShow = cluster.markers
_uiState.update { state ->
state.copy(
markerList = uiState.value.markerList + markersToShow
)
}
}


Expand Down Expand Up @@ -225,8 +267,7 @@ class HomeViewModel @Inject constructor(
radius = uiState.value.cameraRadius.toString(),
longitude = uiState.value.cameraLongitude.toString(),
latitude = uiState.value.cameraLatitude.toString(),
limit = if(uiState.value.cameraRadius < 500) 100 else 40
//이게 null일때 갯수제한이여야하는데.. 반대로 되어있어 200개 임시로 적음
limit = if (uiState.value.cameraRadius < 500) 200 else 40
).onStart {
_events.emit(HomeEvents.ShowLoading)
}.onEach {
Expand All @@ -242,13 +283,13 @@ class HomeViewModel @Inject constructor(
)
}
moveCamera()
_events.emit(HomeEvents.SetNewMarkers)
}

is BaseState.Error -> {
_events.emit(HomeEvents.ShowSnackMessage(ERROR_MSG))
}
}
_events.emit(HomeEvents.SetNewMarkers)
}.onCompletion {
_events.emit(HomeEvents.DismissLoading)
}.launchIn(viewModelScope)
Expand Down Expand Up @@ -277,11 +318,11 @@ class HomeViewModel @Inject constructor(
}
}
moveCamera()
_events.emit(HomeEvents.SetNewMarkers)
}

is BaseState.Error -> _events.emit(HomeEvents.ShowSnackMessage(ERROR_MSG))
}
_events.emit(HomeEvents.SetNewMarkers)
}.onCompletion {
_events.emit(HomeEvents.DismissLoading)
}.launchIn(viewModelScope)
Expand All @@ -306,11 +347,11 @@ class HomeViewModel @Inject constructor(
)
}
moveCamera()
_events.emit(HomeEvents.SetNewMarkers)
}

is BaseState.Error -> _events.emit(HomeEvents.ShowSnackMessage(ERROR_MSG))
}
_events.emit(HomeEvents.SetNewMarkers)
}.onCompletion {
_events.emit(HomeEvents.DismissLoading)
}.launchIn(viewModelScope)
Expand Down Expand Up @@ -386,18 +427,6 @@ class HomeViewModel @Inject constructor(
return density
}

private fun haversineDistance(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
val radius = 6371000 // 지구 반지름(미터 단위)
val distanceLatitude = Math.toRadians(lat2 - lat1)
val distanceLongitude = Math.toRadians(lon2 - lon1)

val a = (sin(distanceLatitude / 2) * sin(distanceLatitude / 2)) +
(cos(Math.toRadians(lat1)) * cos(Math.toRadians(lat2)) *
sin(distanceLongitude / 2) * sin(distanceLongitude / 2))

val c = 2 * atan2(sqrt(a), sqrt(1 - a))
return radius * c
}

private fun moveCamera() {
if (_uiState.value.markerList.isEmpty()) return
Expand All @@ -413,7 +442,7 @@ class HomeViewModel @Inject constructor(
}
return
}

updateCluster()
var closestPoint: LatLng? = null
var maxDensityPoint: LatLng? = null
var minDistance = Double.MAX_VALUE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ data class UiRestaurantData(
val reviewCount: String = "",
val isInWishList: Boolean = false,
val isInMyList: Boolean = false,
val reviewImage: String = ""
val reviewImage: String = "",
val isClustered : Boolean = false
)

0 comments on commit 650b4dd

Please sign in to comment.