Как построить маршрут с несколькими пунктами назначения

В этом руководстве рассказывается, как с помощью Navigation SDK для Android построить в приложении маршрут с несколькими пунктами назначения (путевыми точками). Вы можете задать не более 25 путевых точек, включая пункт назначения.

Обзор

  1. Интегрируйте Navigation SDK в приложение, как описано в статье Настройка проекта.
  2. Добавьте в приложение элемент NavigationFragment или NavigationView. Он добавляет в объект activity интерактивную карту и интерфейс пошаговой навигации.
  3. Для инициализации SDK используйте класс NavigationApi.
  4. Определите Navigator для управления пошаговой навигацией:

    • Добавьте целевые страницы с помощью setDestinations.
    • Запустите навигацию с помощью startGuidance.
    • Используйте getSimulator, чтобы симулировать движение транспортного средства по маршруту для тестирования, отладки и демонстрации приложения.
  5. Создайте сборку и запустите приложение.

Посмотреть код

Как добавить фрагмент навигации

NavigationFragment – это компонент пользовательского интерфейса, который отображает визуальные данные навигации, включая интерактивную карту и пошаговые маршруты. Вы можете объявить фрагмент в 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.NavigationFragment"
    android:id="@+id/navigation_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

Вы также можете создать фрагмент программно, как описано в документации для Android, используя FragmentActivity.getSupportFragmentManager().

В качестве альтернативы фрагменту компонент интерфейса также доступен как NavigationView. В большинстве случаев мы рекомендуем использовать NavigationFragment – оболочку для NavigationView, а не взаимодействовать напрямую с NavigationView. Подробнее о рекомендациях по взаимодействию с навигационной картой …

Запрос доступа к данным о местоположении

Чтобы в вашем приложении работали функции геолокации, необходимо получить от пользователя разрешение на доступ к данным о местоположении.

Это руководство содержит код, позволяющий запросить доступ к точным данным о местоположении. Подробную информацию можно найти в руководстве по разрешениям Android.

  1. Добавьте следующее разрешение в качестве дочернего для элемента <manifest> в вашем манифесте Android:

        <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 = (NavigationFragment) getSupportFragmentManager()
                                .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. Добавьте метод для создания объекта 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. Для этой поездки можно задать до 25 путевых точек. После расчета маршрута NavigationFragment отображает на карте ломаную линию, представляющую маршрут, с маркером в каждой путевой точке.

        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).
                                    AudioGuidanceSettings audioSettings =
                                            AudioGuidanceSettings.builder()
                                                    .setGuidanceMode(
                                                            AudioGuidanceSettings.GuidanceMode
                                                                    .VOICE_ALERTS_AND_GUIDANCE)
                                                    .build();
                                    mNavigator.setAudioGuidanceSettings(audioSettings);
    
                                    // 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;
                                case ROUTING_TIMEOUT:
                                    displayMessage("Error starting navigation: Routing timed out.",
                                            DISPLAY_BOTH);
                                    break;
                                default:
                                    displayMessage("Error starting navigation: "
                                            + String.valueOf(code), DISPLAY_BOTH);
                            }
                        }
                    });
        }
    

Как создать сборку и запустить приложение

  1. Подключите устройство Android к компьютеру. Выполните инструкции по активации параметров для разработчиков на устройстве Android и настройте систему для обнаружения этого устройства. Для настройки виртуального устройства вы также можете использовать Менеджер виртуальных устройств Android (AVD). Выбирая эмулятор, убедитесь, что вы используете образ, который содержит API Google.
  2. В Android Studio выберите пункт меню Run (Запустить) или нажмите на значок воспроизведения, чтобы запустить свое приложение. В открывшемся окне выберите устройство.

Подсказки для улучшения пользовательского опыта

  • Чтобы использовать навигацию, пользователь должен принять Условия использования Google Навигации. Это нужно сделать только один раз. По умолчанию SDK запрашивает согласие при первом вызове навигатора. При желании вы можете вызвать диалоговое окно с Условиями использования Navigation на раннем этапе UX-потока вашего приложения, например при регистрации или входе, с помощью showTermsAndConditionsDialog.
  • Качество навигации и точность расчетного времени прибытия значительно повышаются, если для инициализации путевой точки использовать идентификаторы мест, а не широту и долготу пункта назначения.
  • В этом примере путевые точки определяются по идентификаторам мест. Другие способы получить идентификатор места:

  • Чтобы получить идентификаторы мест для определенных местоположений, воспользуйтесь инструментом поиска идентификаторов мест.

  • Используйте Geocoding API, чтобы найти идентификатор места для заданного адреса. Geocoding API хорошо работает, если у вас есть полные и однозначные адреса путевых точек. Ознакомьтесь с руководством по геокодированию.

  • Используйте текстовый поиск Places API, чтобы найти идентификатор места для заданного адреса. Places API хорошо подходит, если у вас есть неполные или неоднозначные адреса путевых точек. Ознакомьтесь с руководством по геокодированию.