다중 대상 경로 탐색

이 가이드에 따라 Android용 Navigation SDK를 사용하여 앱에서 여러 목적지(경유지라고도 함)까지의 경로를 표시합니다.

개요

  1. 프로젝트 설정에 설명된 대로 Navigation SDK를 앱에 통합합니다.
  2. 앱에 SupportNavigationFragment 또는 NavigationView를 추가합니다. 이 UI 요소는 활동에 대화형 지도와 세부 경로 탐색 UI를 추가합니다.
  3. NavigationApi 클래스를 사용하여 SDK를 초기화합니다.
  4. 세부 경로 안내 내비게이션을 제어하는 Navigator를 정의합니다.

    • setDestinations()를 사용하여 대상을 추가합니다.
    • startGuidance() 아이콘을 탭하여 내비게이션을 시작합니다.
    • getSimulator()를 사용하여 경로를 따라 차량의 진행 상황을 시뮬레이션하여 앱을 테스트, 디버그, 데모합니다.
  5. 앱을 빌드하고 실행합니다.

코드 보기

탐색 프래그먼트 추가

SupportNavigationFragment는 양방향 지도 및 세부 경로 안내를 비롯한 탐색의 시각적 출력을 표시하는 UI 구성요소입니다. 아래와 같이 XML 레이아웃 파일에서 프래그먼트를 선언할 수 있습니다.

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:name="com.google.android.libraries.navigation.SupportNavigationFragment"
    android:id="@+id/navigation_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

또는 Android 문서에 설명된 대로 FragmentActivity.getSupportFragmentManager()를 사용하여 프래그먼트를 프로그래매틱 방식으로 구성할 수 있습니다.

UI 구성요소는 프래그먼트 대신 NavigationView로 사용할 수도 있습니다. 대부분의 경우 NavigationView와 직접 상호작용하는 대신 NavigationView의 래퍼인 SupportNavigationFragment를 사용하는 것이 좋습니다. 자세한 내용은 탐색 지도 상호작용 권장사항 을 참고하세요.

위치 정보 액세스 권한 요청

기기의 위치를 확인하려면 앱에서 위치 정보 액세스 권한을 요청해야 합니다.

이 튜토리얼에서는 상세한 위치 정보 액세스 권한을 요청하는 데 필요한 코드를 제공합니다. 자세한 내용은 Android 권한 가이드를 참고하세요.

  1. Android 매니페스트에 권한을 <manifest> 요소의 하위 요소로 추가합니다.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.navsdkmultidestination">
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    </manifest>
    
  2. 앱에서 런타임 권한을 요청하여 사용자에게 위치 정보 액세스 권한을 허용 또는 거부할 수 있는 기회를 제공합니다. 다음 코드는 사용자가 상세 위치 정보 액세스 권한을 부여했는지 확인합니다. 부여하지 않은 경우, 해당 권한을 요청합니다.

    if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
            android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
        mLocationPermissionGranted = true;
    } else {
        ActivityCompat.requestPermissions(this,
                new String[] { android.Manifest.permission.ACCESS_FINE_LOCATION },
                PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
    }
    
    if (!mLocationPermissionGranted) {
        displayMessage("Error loading Navigation SDK: "
                + "The user has not granted location permission.", DISPLAY_BOTH);
        return;
    }
    
  3. 권한 요청의 결과를 처리하도록 onRequestPermissionsResult() 콜백을 재정의합니다.

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
                                           @NonNull int[] grantResults) {
        mLocationPermissionGranted = false;
        switch (requestCode) {
            case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
                // If request is canceled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    mLocationPermissionGranted = true;
                }
            }
        }
    }
    

Navigation SDK 초기화 및 경로 구성

NavigationApi 클래스는 앱에서 Google 탐색을 사용하도록 승인하는 초기화 로직을 제공합니다. Navigator 클래스는 탐색 여정의 구성 및 시작/중지를 제어합니다.

  1. 화면과 로그에 메시지를 표시하는 도우미 메서드를 만듭니다.

    private void displayMessage(String errorMessage, String displayMedium) {
        if (displayMedium.equals(DISPLAY_BOTH) || displayMedium.equals(DISPLAY_TOAST)) {
            Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show();
        }
    
        if (displayMedium.equals(DISPLAY_BOTH) || displayMedium.equals(DISPLAY_LOG)) {
            Log.d(TAG, errorMessage);
        }
    }
    
  2. Navigation SDK를 초기화하고 onNavigatorReady() 콜백을 재정의하여 내비게이터가 준비되었을 때 내비게이션을 시작합니다.

    NavigationApi.getNavigator(this, new NavigationApi.NavigatorListener() {
                /**
                 * Sets up the navigation UI when the navigator is ready for use.
                 */
                @Override
                public void onNavigatorReady(Navigator navigator) {
                    displayMessage("Navigator ready.", DISPLAY_BOTH);
                    mNavigator = navigator;
                    mNavFragment = (SupportNavigationFragment) getFragmentManager()
                            .findFragmentById(R.id.navigation_fragment);
    
                    // Set the camera to follow the device location with 'TILTED' driving view.
                    mNavFragment.getCamera().followMyLocation(Camera.Perspective.TILTED);
    
                    // Navigate to the specified places.
                    navigateToPlaces();
                }
    
                /**
                 * Handles errors from the Navigation SDK.
                 * @param errorCode The error code returned by the navigator.
                 */
                @Override
                public void onError(@NavigationApi.ErrorCode int errorCode) {
                    switch (errorCode) {
                        case NavigationApi.ErrorCode.NOT_AUTHORIZED:
                            displayMessage("Error loading Navigation SDK: Your API key is "
                                    + "invalid or not authorized to use the Navigation SDK.",
                                    DISPLAY_BOTH);
                            break;
                        case NavigationApi.ErrorCode.TERMS_NOT_ACCEPTED:
                            displayMessage("Error loading Navigation SDK: User did not accept "
                                    + "the Navigation Terms of Use.", DISPLAY_BOTH);
                            break;
                        case NavigationApi.ErrorCode.NETWORK_ERROR:
                            displayMessage("Error loading Navigation SDK: Network error.",
                                    DISPLAY_BOTH);
                            break;
                        case NavigationApi.ErrorCode.LOCATION_PERMISSION_MISSING:
                            displayMessage("Error loading Navigation SDK: Location permission "
                                    + "is missing.", DISPLAY_BOTH);
                            break;
                        default:
                            displayMessage("Error loading Navigation SDK: " + errorCode,
                                    DISPLAY_BOTH);
                    }
                }
            });
    
  3. 지정된 장소 ID 및 제목에서 Waypoint 객체를 만드는 메서드를 추가합니다.

    private void createWaypoint(String placeId, String title) {
        try {
            mWaypoints.add(
              Waypoint.builder()
                     .setPlaceIdString(placeId)
                     .setTitle(title)
                     .build()
            );
        } catch (Waypoint.UnsupportedPlaceIdException e) {
            displayMessage("Error starting navigation: Place ID is not supported: " + placeId,
                    DISPLAY_BOTH);
        }
    }
    
  4. 각 중간 지점까지의 계산된 이동 시간과 거리를 표시하는 메서드를 추가합니다.

    private void displayTimesAndDistances() {
        List<TimeAndDistance> timesAndDistances = mNavigator.getTimeAndDistanceList();
        int leg = 1;
        String message = "You're on your way!";
        for (TimeAndDistance timeAndDistance : timesAndDistances) {
            message = message + "\nRoute leg: " + leg++
                    + ": Travel time (seconds): " + timeAndDistance.getSeconds()
                    + ". Distance (meters): " + timeAndDistance.getMeters();
        }
        displayMessage(message, DISPLAY_BOTH);
    }
    
  5. 이 여정의 모든 경유지를 설정합니다. (탐색기에서 경로를 표시할 수 없는 장소 ID를 사용하면 오류가 발생할 수 있습니다. 이 튜토리얼의 샘플 앱은 오스트레일리아의 중간 지점에 장소 ID를 사용합니다. 다른 장소 ID 가져오기에 관한 아래의 메모를 참고하세요.) 경로를 계산한 후 SupportNavigationFragment는 지도에 경로를 나타내는 다중선을 표시하고 각 경유지에 마커를 표시합니다.

    private void navigateToPlaces() {
    
        // Set up a waypoint for each place that we want to go to.
        createWaypoint("ChIJq6qq6jauEmsRJAf7FjrKnXI", "Sydney Star");
        createWaypoint("ChIJ3S-JXmauEmsRUcIaWtf4MzE", "Sydney Opera House");
        createWaypoint("ChIJLwgLFGmuEmsRzpDhHQuyyoU", "Sydney Conservatorium of Music");
    
        // If this journey is already in progress, no need to restart navigation.
        // This can happen when the user rotates the device, or sends the app to the background.
        if (mSavedInstanceState != null
                && mSavedInstanceState.containsKey(KEY_JOURNEY_IN_PROGRESS)
                && mSavedInstanceState.getInt(KEY_JOURNEY_IN_PROGRESS) == 1) {
            return;
        }
    
        // Create a future to await the result of the asynchronous navigator task.
        ListenableResultFuture<Navigator.RouteStatus> pendingRoute =
                mNavigator.setDestinations(mWaypoints);
    
        // Define the action to perform when the SDK has determined the route.
        pendingRoute.setOnResultListener(
                new ListenableResultFuture.OnResultListener<Navigator.RouteStatus>() {
                    @Override
                    public void onResult(Navigator.RouteStatus code) {
                        switch (code) {
                            case OK:
                                mJourneyInProgress = true;
                                // Hide the toolbar to maximize the navigation UI.
                                if (getActionBar() != null) {
                                    getActionBar().hide();
                                }
    
                                // Register some listeners for navigation events.
                                registerNavigationListeners();
    
                                // Display the time and distance to each waypoint.
                                displayTimesAndDistances();
    
                                // Enable voice audio guidance (through the device speaker).
                                mNavigator.setAudioGuidance(
                                        Navigator.AudioGuidance.VOICE_ALERTS_AND_GUIDANCE);
    
                                // Simulate vehicle progress along the route for demo/debug builds.
                                if (BuildConfig.DEBUG) {
                                    mNavigator.getSimulator().simulateLocationsAlongExistingRoute(
                                            new SimulationOptions().speedMultiplier(5));
                                }
    
                                // Start turn-by-turn guidance along the current route.
                                mNavigator.startGuidance();
                                break;
                            // Handle error conditions returned by the navigator.
                            case NO_ROUTE_FOUND:
                                displayMessage("Error starting navigation: No route found.",
                                        DISPLAY_BOTH);
                                break;
                            case NETWORK_ERROR:
                                displayMessage("Error starting navigation: Network error.",
                                        DISPLAY_BOTH);
                                break;
                            case ROUTE_CANCELED:
                                displayMessage("Error starting navigation: Route canceled.",
                                        DISPLAY_BOTH);
                                break;
                            default:
                                displayMessage("Error starting navigation: "
                                        + String.valueOf(code), DISPLAY_BOTH);
                        }
                    }
                });
    }
    

앱 빌드 및 실행

  1. 컴퓨터에 Android 기기를 연결합니다. 안내에 따라 Android 기기에서 개발자 옵션을 사용 설정하고 기기를 감지하도록 시스템을 구성합니다. (또는 Android Virtual Device(AVD) Manager를 사용하여 가상 기기를 구성할 수 있습니다. 에뮬레이터를 선택할 때 Google API가 포함된 이미지를 선택해야 합니다.)
  2. Android 스튜디오에서 Run 메뉴 옵션(또는 재생 버튼 아이콘)을 클릭합니다. 표시되는 메시지에 따라 기기를 선택합니다.

사용자 환경 개선을 위한 팁

  • 사용자가 Google 내비게이션 서비스 약관에 동의해야 내비게이션을 사용할 수 있습니다. 승인은 한 번만 수행하면 됩니다. 기본적으로 SDK는 탐색기가 처음 호출될 때 동의 메시지를 표시합니다. 원하는 경우 showTermsAndConditionsDialog()를 사용하여 가입 또는 로그인 중에 앱의 UX 흐름 초기에 내비게이션 서비스 약관 대화상자를 트리거할 수 있습니다.
  • 도착지 경도/위도 대신 장소 ID를 사용하여 중간 지점을 초기화하면 내비게이션 품질과 도착 예정 시간 정확성이 크게 개선됩니다.
  • 이 샘플은 특정 장소 ID에서 경유지를 가져옵니다. 장소 ID를 가져오는 다른 방법은 다음과 같습니다.