TIL🔥스파르타 | 네이버 지도 API로 최단경로인 마커 사이에 폴리라인 긋기

hyihyi·2024년 3월 1일
0

TIL

목록 보기
56/69
post-thumbnail

🤔 마커 사이 가장 가까운 거리를 구해 폴리라인을 긋는 로직을 Fragment에서 ViewModel로 분리시켜보자

  • markers : 마커들의 리스트
`private val markers = mutableListOf<Marker>()`
  • _furthestPair : 마커 쌍 1개
`private val _furthestPair = MutableLiveData<Pair<Marker, Marker>?>()`
  • findFurthestMarkers : 지도에서 가장 멀리 있는 마커 구하는 함수
`fun findFurthestMarkers(markers: List<Marker>) { }`
  • connectMarkersSequentiallyFromFurthest : 마커끼리 폴리라인 연결하는 함수
`private fun connectMarkersSequentiallyFromFurthest(naverMap: NaverMap, markers: MutableList<Marker>, furthestPair: Pair<Marker, Marker>) {`

🤚 순서

  1. findFurthestMarkers에서 markers 안의 마커들을 2개씩 비교해 가장 거리가 먼 마커 쌍 하나인 furthestPair를 반환한다.

  2. connectMarkersSequentiallyFromFurthest에서 furthestPair 중 첫 번째 마커를 출발점으로 잡고 currentMarker와 marker 리스트 안의 마커를 비교하면서 가장 가까운 마커와 연결시킨다.
    한 번 연결된 마커는 connectedMarkers 리스트에 추가하고 markers 리스트에서는 제거해 다시 연결되지 않도록 한다.

  3. markers 리스트에 마커가 남아 있는 동안 계속 반복한다.

🖤 원래의 Fragment 안에 있던 코드

private fun findFurthestMarkers(markers: List<Marker>): Pair<Marker, Marker>? {
    if (markers.size < 2) return null

    var furthestPair: Pair<Marker, Marker>? = null
    var longestDistance = 0.0

    markers.forEach { marker1 ->
        markers.forEach { marker2 ->
            val distance = marker1.position.distanceTo(marker2.position)
            if (distance > longestDistance) {
                longestDistance = distance
                furthestPair = Pair(marker1, marker2)
            }
        }
    }

    return furthestPair
}
private fun connectMarkersSequentiallyFromFurthest(naverMap: NaverMap, markers: MutableList<Marker>, furthestPair: Pair<Marker, Marker>) {
        var currentMarker = furthestPair.first // 시작점으로 설정할 마커
        val connectedMarkers = mutableListOf(currentMarker)
        markers.remove(currentMarker)
        Log.d("polyline1", markers.toString())

        while (markers.isNotEmpty()) {
            val closestMarker = markers.minByOrNull { marker -> currentMarker.position.distanceTo(marker.position) }
            closestMarker?.let { marker ->
            Log.d("polyline2", marker.position.toString())
                PolylineOverlay().apply {
                    coords = listOf(currentMarker.position, marker.position)
                    color = 0xFF0085FF.toInt()
                    map = naverMap
                }
                currentMarker = marker
                connectedMarkers.add(marker)
                markers.remove(marker)
            }
        }
    }

LiveData를 선언하는 방식에는 2가지가 있다

  1. 백킹 프로퍼티를 사용
private val _furthestPair = MutableLiveData<Pair<Marker, Marker>?>()
val furthestPair: LiveData<Pair<Marker, Marker>?> get() = _furthestPair
  1. 직접 할당
private val _furthestPair = MutableLiveData<Pair<Marker, Marker>?>()
val furthestPair: LiveData<Pair<Marker, Marker>?> = _furthestPair

백킹 프로퍼티를 사용하면 게터를 통해 추가 로직을 구현할 수 있다.
아래의 코드는 백킹 프로퍼티를 사용해 data 프로퍼티에 접근할 때마다 로그를 남기는 로직을 실행시키는 코드이다.
이 방식의 장점은 데이터를 가져올 때마다 일관되게 추가 로직을 실행할 수 있다. 데이터의 검증, 변형, 로깅 등에 유용하게 사용되며 더 깔끔하고 유지보수가 용이한 코드를 작성할 수 있다.

class ExampleViewModel : ViewModel() {
    private val _data = MutableLiveData<List<String>>()
    
	//아래와 똑같다
	// val data: LiveData<List<String>> get() = _data 
    
    // 백킹 프로퍼티를 사용하여 _data의 값을 노출하지만, 커스텀 게터를 통해 추가 로직을 구현
    val data: LiveData<List<String>> get() {
        // 게터에 로직 추가. 여기서는 단순히 로깅을 수행하고 있음.
        Log.d("ExampleViewModel", "Data accessed")
        
        // 필요한 경우 데이터를 변형하거나, 특정 조건에 따라 다른 값을 반환할 수도 있음.
        // 예: _data.value?.filter { it.isNotEmpty() } ?: emptyList()
        return _data
    }
}

🤍 ViewModel 사용하기(Fragment)

1. ViewModel 클래스 만들어주기

ex) RecordDetailViewModel

class RecordDetailViewModel : ViewModel() {

}

2. ViewModel 안에 LiveData 선언

class RecordDetailViewModel : ViewModel() {
    private val _furthestPair = MutableLiveData<Pair<Marker, Marker>?>()
    val furthestPair: LiveData<Pair<Marker, Marker>?> get() = _furthestPair
    	//아래처럼 백킹 프로퍼티를 사용할 수도 있다
    	/*val furthestPair: LiveData<Pair<Marker, Marker>?> get(){
        	Log.d("furthestPair", "RecordDetailViewModel에서 furthestPair 라이브데이터")
        	return _furthestPair
    	}*/
    ....
}

3. ViewModel 안에 비즈니스 로직 함수 넣기

fun findFurthestMarkers(markers: List<Marker>) {
        if (markers.size < 2) {
            _furthestPair.value = null
            return
        }

        var furthestPair: Pair<Marker, Marker>? = null
        var longestDistance = 0.0

        markers.forEach { marker1 ->
            markers.forEach { marker2 ->
                val distance = marker1.position.distanceTo(marker2.position)
                if (distance > longestDistance) {
                    longestDistance = distance
                    furthestPair = Pair(marker1, marker2)
                }
            }
        }
        _furthestPair.value = furthestPair
    }

4. Fragment에서 ViewModelProvider 사용해서 viewModel 선언

private val viewModel by lazy {
        ViewModelProvider(this@RecordDetailMapFragment)[RecordDetailViewModel::class.java]
    }

4. Fragment에서 Observing하기

viewModel 안에 있는 findFurthestMarkers 함수는 마커들 중 가장 멀리 있는 마커를 구하는 함수고 viewModel 안에 있는 furthestPair는 가장 멀리 있는 마커이다.

private fun observeFurthestPairAndConnectMarkers() {
    viewModel.findFurthestMarkers(markers) // LiveData를 업데이트하도록 요청
    viewModel.furthestPair.observe(viewLifecycleOwner, Observer { furthestPair ->
        furthestPair?.let {
            connectMarkersSequentiallyFromFurthest(naverMap, markers, it)
        }
    })
}
  • _가 붙은 변수
    read와 write 처리 가능
  • _가 안 붙은 변수
    read만 가능

💟 전체 코드

RecordDetailMapFragment

class RecordDetailMapFragment : Fragment(), OnMapReadyCallback {

    private var _binding: FragmentRecordDetailMapBinding? = null
    private val binding get() = _binding!!

    private lateinit var naverMap: NaverMap
    private lateinit var mapView: MapView
    // 마커 리스트 생성
    private val markers = mutableListOf<Marker>()

    private val viewModel by lazy {
        ViewModelProvider(this@RecordDetailMapFragment)[RecordDetailViewModel::class.java]
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View? {
        _binding = FragmentRecordDetailMapBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // mapView 변수 초기화
        mapView = binding.mvRecordDetailFullMap
        mapView.onCreate(savedInstanceState)
        mapView.getMapAsync(this)
    }
    override fun onMapReady(naverMap: NaverMap) {
        this.naverMap = naverMap

        // 임의의 위치 데이터 목록
        private val locations = listOf(
        	LatLng(37.4979, 127.0276),
        	LatLng(37.5124, 127.0589),
        	LatLng(37.5145, 127.0573),
        	LatLng(37.5273, 127.0390),
        	LatLng(37.5195, 127.0378),
        	LatLng(37.5089, 127.0468),
    )

        locations.forEach { latLng ->
            Log.d("latLng", latLng.toString())
            val marker = Marker().apply {
                position = latLng
                icon = OverlayImage.fromBitmap(resizeMapIcons(R.drawable.ic_marker, 100, 100))
                map = naverMap
            }
            markers.add(marker) // 마커 리스트에 추가
        }
        // markers에 마커를 다 추가 후 지도에서 가장 멀리 있는 마커 구하는 함수에 인자로 markers 넣어서 호출
        observeFurthestPairAndConnectMarkers()

        // 카메라 위치와 줌 레벨 조정 (선택 사항)
        val cameraUpdate = CameraUpdate.scrollTo(locations.first()).animate(CameraAnimation.Easing)
        naverMap.moveCamera(cameraUpdate)
        naverMap.moveCamera(CameraUpdate.zoomTo(10.0))
    }

    private fun observeFurthestPairAndConnectMarkers() {
        viewModel.findFurthestMarkers(markers) // LiveData를 업데이트하도록 요청
        viewModel.furthestPair.observe(viewLifecycleOwner, Observer { furthestPair ->
            furthestPair?.let {
                connectMarkersSequentiallyFromFurthest(naverMap, markers, it)
            }
        })
    }

    private fun resizeMapIcons(iconId: Int, width: Int, height: Int): Bitmap {
        val imageBitmap = BitmapFactory.decodeResource(resources, iconId)
        return Bitmap.createScaledBitmap(imageBitmap, width, height, false)
    }

    // 마커끼리 폴리라인 연결하는 함수
    private fun connectMarkersSequentiallyFromFurthest(naverMap: NaverMap, markers: MutableList<Marker>, furthestPair: Pair<Marker, Marker>) {
        var currentMarker = furthestPair.first // 시작점으로 설정할 마커
        val connectedMarkers = mutableListOf(currentMarker)
        markers.remove(currentMarker)

        while (markers.isNotEmpty()) {
            val closestMarker = markers.minByOrNull { marker -> currentMarker.position.distanceTo(marker.position) }
            closestMarker?.let { marker ->
                PolylineOverlay().apply {
                    coords = listOf(currentMarker.position, marker.position)
                    color = 0xFF0085FF.toInt()
                    map = naverMap
                }
                currentMarker = marker
                connectedMarkers.add(marker)
                markers.remove(marker)
            }
        }
    }

    override fun onStart() {
        super.onStart()
        mapView.onStart()
    }

    override fun onResume() {
        super.onResume()
        mapView.onResume()
    }

    override fun onPause() {
        mapView.onPause()
        super.onPause()
    }

    override fun onStop() {
        mapView.onStop()
        super.onStop()
    }

    override fun onLowMemory() {
        super.onLowMemory()
        mapView.onLowMemory()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        mapView.onSaveInstanceState(outState)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        mapView.onDestroy()
        _binding = null
    }

}

RecordDetailViewModel

class RecordDetailViewModel : ViewModel() {
    private val _furthestPair = MutableLiveData<Pair<Marker, Marker>?>()
    val furthestPair: LiveData<Pair<Marker, Marker>?> get(){
        Log.d("furthestPair", "RecordDetailViewModel에서 furthestPair 라이브데이터")
        return _furthestPair
    }

    // 지도에서 가장 멀리 있는 마커 구하는 함수
    fun findFurthestMarkers(markers: List<Marker>) {
        if (markers.size < 2) {
            _furthestPair.value = null
            return
        }

        var furthestPair: Pair<Marker, Marker>? = null
        var longestDistance = 0.0

        markers.forEach { marker1 ->
            markers.forEach { marker2 ->
                val distance = marker1.position.distanceTo(marker2.position)
                if (distance > longestDistance) {
                    longestDistance = distance
                    furthestPair = Pair(marker1, marker2)
                }
            }
        }
        _furthestPair.value = furthestPair
    }
}

🚫 주의할 점

markers 리스트에 marker를 다 추가하고 observeFurthestPairAndConnectMarkers를 호출해야 폴리라인이 그려진다.

결과물

뭔가, 살짝, 조금 다른 것 같지만 지금은 그냥 넘어가자

profile
자유롭게 쓴 나의 자유로운 Development voyage⛵

0개의 댓글