작업 일지

[Android] 내비게이션의 다음 경로 정보 구하기#2

O_Gyong 2024. 3. 7.
 

[Android] 내비게이션 다음 경로의 정보 구하기#1

프로젝트에서 내비게이션을 사용하여 다음 경로까지의 거리 값과 회전 정보를 구해야하는 일이 생겼다. 개발 과정과 사용하게 된 API에 대해서 정리를 해본다. 사용 API ◾ 카카오내비 길찾기 SDK

ogyong.tistory.com

이전 글에서는 내비게이션 사용을 위한 API 관리와 출발지, 도착지를 어떻게 설정했는지에 대해서 정리했다. 이번에는 지도 화면에서 길찾기 버튼을 눌렀을 때부터 내비게이션 다음 경로 정보를 구하기까지에 대해서 정리하려고 한다.

 

카카오내비 길찾기 SDK 참고 자료 링크

API 레퍼런스 가이드 에러 값 확인

내비게이션 사용을 위한 인스톨 및 초기화

@HiltAndroidApp
class ServiceApplication: Application()  {
    override fun onCreate() {
        super.onCreate()
        // KNSDK 등록
        KNSDK.install(this, "$filesDir/files")
    }
}

KNSDK는 카카오내비를 사용하기 위한 지도, 경로, 가이던스 기능을 제공한다. KNSDK를 사용하기 위해서는 install과 초기화가 필요하다. Application 단에서 KNSDK를 install을 사용하여 등록한다.

 

도착지 상세 뷰에서 길찾기 버튼을 클릭했을 때 KNSDK 초기화를 진행했다. 원래는 NavigationActivity에서 처리를 했으나 SearchActivity에서 로딩 없이 사용하기 위해 MapActivity로 옮겼다.

 

        /**
         * MapActivity
         */
        binding.btnIntentNavi.setOnClickListener {
            // 카카오 내비게이션 초기화
            KNSDK.initializeWithAppKey(
                com.navirotation.BuildConfig.KAKAO_NATIVE_APP_KEY,
                com.navirotation.BuildConfig.VERSION_NAME,
                null,
                KNLanguageType.KNLanguageType_KOREAN,
                aCompletion = {
                    if (it != null) {
                        when (it.code) {
                            KNError_Code_C103 -> {
                                Log.d(NAVI_ROTATION, "내비 인증 실패: $it")
                                return@initializeWithAppKey
                            }

                            KNError_Code_C302 -> {
                                Log.d(NAVI_ROTATION, "내비 권한 오류 : $it")
                                requestPermissions(
                                    arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
                                    1
                                )
                                return@initializeWithAppKey
                            }

                            else -> {
                                Log.d(NAVI_ROTATION, "내비 초기화 실패: $it")
                                return@initializeWithAppKey
                            }
                        }
                    } else {
                        Log.d(NAVI_ROTATION, "내비 초기화 성공")
                        
                        val intent = Intent(applicationContext, NavigationActivity::class.java)
                        intent.putExtra("startLatitude", myLatitude)
                        intent.putExtra("startLongitude", myLongitude)
                        intent.putExtra("endLatitude", endLatitude)
                        intent.putExtra("endLongitude", endLongitude)
                        startActivity(intent)
                        finish()
                    }
                })
        }

 

KNSDK를 사용하기 전에 initializeWithAppKey를 사용하여 반드시 초기화를 진행해야 한다. 이때 local.properties에 등록한 키 값을 사용한다. 그리고 파라미터 중 completion은 초기화가 완료된 이후 결과 상태를 전달한다. 결과 값은 KNError로 이 값이 null이면 초기화에 성공한 것이다. 성공이 확인되면 출발지와 도착지 정보를 갖고 NavigationActivity로 이동한다.

 

참고로 completion 블록은 메인 스레드가 아니라서 UI 작업을 할 수 없다. 만약 해당 블록에서 UI 작업이 필요하다면 Handler를 생성하여 메인 스레드에서 동작할 수 있도록 해야 한다.

/**
 * 예시
 */
Handler(Looper.getMainLooper()).post {
	// UI 작업
}

내비게이션 경로 생성하기

 KATECH 좌표 변환

/**
 * NavigationActivity
 */
override fun onCreate(savedInstanceState: Bundle?) {
	// 생략

    startLatitude = intent.getDoubleExtra("startLatitude", 0.0)
    startLongitude = intent.getDoubleExtra("startLongitude", 0.0)
    endLatitude = intent.getDoubleExtra("endLatitude", 0.0)
    endLongitude = intent.getDoubleExtra("endLongitude", 0.0)

    viewModel.getCoordConvertData(
        startLatitude, startLongitude,
        endLatitude, endLongitude
    )
}
/**
 * NavigationRepository
 */
fun getCoordConvertData(myLatitude: Double, myLongitude: Double): CoordConvertResponse? {
    return navigationService.coordConvertRequest(
        appKey = com.navirotation.BuildConfig.SK_APP_KEY,
        version = "1",
        lat = myLatitude.toString(),
        lon = myLongitude.toString(),
        fromCoord = "WGS84GEO",
        toCoord =  "KATECH"
    ).execute().body()
}

내비게이션 경로를 생성하려면 KATECH 좌표의 출발지와 도착지 정보가 필요하다. MapActivity에서 전달받은 출발지와 도착지 정보를 KATECH 좌표로 변환하기 위해  'SK 좌표 변환 API'를 사용하여 변경해 준다.

API 요청 시에 WGS84GEP 좌표를 KATECH 좌표로 변환하기 위해 froomCoord와 toCoord를 설정해 준다.

 

/**
 * NavigationViewModel
 */
fun getCoordConvertData(
    startLatitude: Double, startLongitude: Double,
    endLatitude: Double, endLongitude: Double
) {
    viewModelScope.launch(Dispatchers.Default) {
        try {
            val startLocationFlow = flow {
                emit(navigationRepository.getCoordConvertData(startLatitude, startLongitude)?.coordinate)
            }

            val endLocationFlow = flow {
                emit(navigationRepository.getCoordConvertData(endLatitude, endLongitude)?.coordinate)
            }

            startLocationFlow.zip(endLocationFlow) { startLocation, endResult ->
                CoordZipResult(
                    success = CoordZipData(
                        startLocation?.lat,
                        startLocation?.lon,
                        endResult?.lat,
                        endResult?.lon
                    )
                )
            }.collect { coordZipResult->
                _coordZipResult.value = coordZipResult
            }
        }catch (e: Exception) {
            _coordZipResult.value = CoordZipResult(failure = e)
        }
    }
}

KATECH 좌표로 변환한 출발지와 도착지 값을 한 번에 처리하기 위해 StateFlow의 zip()을 사용했다.

 

KNSDK를 사용하여 경로 생성하기

/**
 * NavigationActivity
 */
viewModel.coordZipResult.collectLatest {
    Log.d(NAVI_ROTATION, "좌표 변환 결과: $it")
    if (it.success == null) return@collectLatest

    val katechStartX = it.success.startLongitude!!.split(".")[0].toInt()
    val katechStartY = it.success.startLatitude!!.split(".")[0].toInt()
    val katechEndX = it.success.endLongitude!!.split(".")[0].toInt()
    val katechEndY = it.success.endLatitude!!.split(".")[0].toInt()

    // 출발지 설정
    val start = KNPOI("", katechStartX, katechStartY, null)

    // 목적지 설정
    val end = KNPOI("", katechEndX, katechEndY, null)

    // KNTrip 생성
    KNSDK.makeTripWithStart(
        start,
        end,
        null,
        null,
        aCompletion = { knError: KNError?, knTrip: KNTrip? ->
            if (knError != null) {
                Log.d(NAVI_ROTATION, "경로 생성 에러(KNError: $knError")
            }

            /**
             * 경로 옵션 설정
             * 1. KNRoutePriority : 목적지까지의 경로 안내 옵션 중 우선적으로 고려할 항목 설정
             * 2. KNRouteAvoidOption : 경로에서 회피하고 싶은 구간 설정
             */
            val curRoutePriority = KNRoutePriority.valueOf(KNRoutePriority.KNRoutePriority_Recommand.toString())   
            val curAvoidOptions = KNRouteAvoidOption.KNRouteAvoidOption_None.value

            knTrip?.routeWithPriority(curRoutePriority, curAvoidOptions) { error, _ ->
                // 경로 요청 실패
                if (error != null) {
                    Log.d(NAVI_ROTATION, "경로 요청 실패 : $error")
                }
                // 경로 요청 성공
                else {
                    Log.d(NAVI_ROTATION, "경로 요청 성공")
                    KNSDK.sharedGuidance()?.apply {
                        // 각 가이던스 델리게이트 등록
                        guideStateDelegate = this@NavigationActivity
                        locationGuideDelegate = this@NavigationActivity
                        routeGuideDelegate = this@NavigationActivity
                        safetyGuideDelegate = this@NavigationActivity
                        voiceGuideDelegate = this@NavigationActivity
                        citsGuideDelegate = this@NavigationActivity
                        
                        settingMap()
                        
                        // 기본 주행 화면을 통해 길 안내 시작
                        binding.naviView.initWithGuidance(
                            this,
                            knTrip,
                            curRoutePriority,
                            curAvoidOptions
                        )
                    }
                }
            }
        })
}

 

KNSDK의 makeTripWithFrame()을 사용하면 경로를 관리하는 KNTrip을 생성할 수 있다. 이 과정에서 KNPOI라는 위치 정보 클래스가 필요한데, 앞에서 KATECH 좌표로 변환한 출발지와 도착지 값을 KNPOI로 설정해 준다.

 

makeTripWithFrame()도 KNTrip의 생성 성공 여부를 completion 블록으로 전달받는다. KNTrip 생성에 성공하면 경로 옵션을 설정 후 KNTrip의 routeWithPriority()로 경로를 요청한다.

 

KNGuidance는 길 안내 기능을 관리하고 경로 정보를 제공하는 클래스다. 경로 요청에 성공하면 shareGuidance()를 통해 현재 KNGuidance 값을 업데이트하고 KNNaviView의 initWithGuidance()로 길 안내를 시작한다.


다음 경로의 정보 구하기

/**
 * NavigationActivity
 */

/**
 * 경로 안내 정보 업데이트 시 호출
 * `routeGuide`의 항목이 1개 이상 변경 시 전달됨.
 */
override fun guidanceDidUpdateRouteGuide(aGuidance: KNGuidance, aRouteGuide: KNGuide_Route) {
    binding.naviView.guidanceDidUpdateRouteGuide(aGuidance, aRouteGuide)

    if (aRouteGuide.curDirection?.location?.pos != null && aRouteGuide.nextDirection?.location?.pos != null) {
        rgCode = aRouteGuide.nextDirection?.rgCode.toString()
        
        // todo : 모든 rgCode를 적용해야하나?
        when (rgCode) {
            "KNRGCode_Straight" -> { rgCode = "직진" }

            "KNRGCode_LeftTurn" -> { rgCode = "좌회전" }

            "KNRGCode_RightTurn" -> { rgCode = "우회전" }

            "KNRGCode_UTurn" -> { rgCode = "유턴" }
        }

        /**
         * SK 직선 거리 API 호출
         */
        viewModel.getDistanceData(
            aRouteGuide.curDirection?.location?.pos!!.toFloatPoint(),
            aRouteGuide.nextDirection?.location?.pos!!.toFloatPoint()
        )
    }
}

내비게이션 길 안내 이후 guidanceDidUpdateRoutes() 경로 안내 정보의 상세 항목 중 한 개 이상의 항목이 변경될 때 호출된다. 이곳에서 다음 경로에 대한 회전 정보와 위치 정보를 얻을 수 있다. 필드 변수인 rgCode에 회정 정보를 저장하고 'SK 직선 거리 계산 API'를 통해 다음 경로까지의 거리를 계산했다.

 

/**
 * NavigationRepository
 */
fun getDistanceData(curDirection: FloatPoint, nextDirection: FloatPoint) : DistanceResponse? {
    return navigationService.distanceRequest(
        appKey = com.navirotation.BuildConfig.SK_APP_KEY,
        version = "1",
        startX = curDirection.x.toString(),
        startY = curDirection.y.toString(),
        endX = nextDirection.x.toString(),
        endY = nextDirection.y.toString(),
        "KATECH"
    ).execute().body()
}

'SK 직선 거리 계산 API'를 사용할 때 마지막 파라미터 값으로 입력하는 좌표계 유형을 KATECH으로 지정해 준다.

 

/**
 * NavigationActivity
 */
lifecycleScope.launch {
    viewModel.distanceData.collectLatest {
        // todo : 실패 case 작성
        Log.d(NAVI_ROTATION, "SK 직선 거리 : ${it.success?.distance}")
        if (it.success == null) return@collectLatest
        binding.tvInform.text = "다음 경로: $rgCode ${it.success?.distance}m"
    }
}

다음 경로에 대한 회전 정보와 거리 값을 간단하게 표시했다.

 

 

전체 코드

댓글