Mapbox Navigation SDK Request and draw routes - Java

 We have got the coordinate(longitude and latitude) of the destination by geocoding search and the device location at the OnIndicatorPositionChangedListener (introduced in the post using Mapbox Map SDK). Now we can request routes connecting the device location(start point) to the destination with the instance of MapboxNavigation in Navigation SDK. 

In this post, I will request routes by providing the coordinate of start point and destination. Then draw the paths of the routes on MapView. Buttons are added on the screen to allow the user to change the primary route. The result of the screen is shown below.

Add Dependency

To use Mapbox Navigation SDK, we can use the same token that applied  in the previous post Mapbox Map SDK. Make sure the dependencies of the version are the same as Mapbox Search SDK if you want to use both SDKs in the same apps.

//Navigation SDK has included Map SDK, so that you can comment this line
//implementation 'com.mapbox.maps:android:10.8.0'
implementation "com.mapbox.navigation:android:2.8.0"
implementation "com.mapbox.navigation:ui-dropin:2.8.0"


Fragment Layout

First, add UI components in the corresponding fragment layout xml file. The path of routes will be drawn on the MapView. Those 3 buttons are used to select which routes wanted to go, in case more than 1 route is returned by Mapbox.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:id="@+id/frameLayout"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".RouteFragment">

   <com.mapbox.maps.MapView
       xmlns:mapbox="http://schemas.android.com/apk/res-auto"
       android:id="@+id/mapView"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       mapbox:mapbox_cameraTargetLat="22.3360"
       mapbox:mapbox_cameraTargetLng="114.1756"
       mapbox:mapbox_cameraZoom="16.0"
       android:layout_width="match_parent"
       android:layout_height="match_parent" />

   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintBottom_toBottomOf="parent"
       android:orientation="horizontal">

       <Button
           android:id="@+id/route1_button"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_gravity="center_vertical"
           android:layout_weight="1"
           android:visibility="invisible"
           android:text="@string/route1" />

       <Button
           android:id="@+id/route2_button"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_gravity="center_vertical"
           android:layout_weight="1"
           android:visibility="invisible"
           android:text="@string/route2" />

       <Button
           android:id="@+id/route3_button"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_gravity="center_vertical"
           android:layout_weight="1"
           android:visibility="invisible"
           android:text="@string/route3" />

   </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>


MapboxNavigation

MapboxNavigation is responsible for requesting routes as well as listening for the location update when the trip session starts. To avoid conflict, it cannot have more than one instance at any time. However, we can destroy and create an instance again many times.

The MapboxNavigation instance is created by MapboxNavigationApp and isSetup() is used to check if the instance has been created or not . We need to provide the NavigationOptions and attach LifeCycleOwner to MapboxNavigationApp. The MapboxNavigation instance only be created when at least 1 LifeCycleOwner is CREATED state.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                        Bundle savedInstanceState) {

   if(!MapboxNavigationApp.isSetup()){
       Log.d("onCreated", "MapboxNavigationApp is not set up");
       NavigationOptions navigationOptions = new NavigationOptions.Builder(requireContext())
               .accessToken(getString(R.string.mapbox_access_token))
               .build();

       MapboxNavigationApp.attach(this);
       MapboxNavigationApp.setup(navigationOptions);
   }
   ……
}


Then we register the MapboxNavigationObserver in which controls what is going to execute when the Navigation instance is ready to use or detached. The details of the RouteObserver instance will be talked about later. Let’s first look at how to request routes from Mapbox.

@Override
public void onViewCreated(View v, Bundle savedInstanceState ){
   super.onViewCreated(v, savedInstanceState);
   MapboxNavigationApp.registerObserver(myMapboxNavigationObserver);
……
}

private MapboxNavigationObserver myMapboxNavigationObserver = new MapboxNavigationObserver() {
   @Override
   public void onAttached(@NonNull MapboxNavigation mapboxNavigation) {
       mapboxNavigation.registerRoutesObserver(routesObserver);
   }

   @Override
   public void onDetached(@NonNull MapboxNavigation mapboxNavigation) {
       mapboxNavigation.unregisterRoutesObserver(routesObserver);
   }
};


Request routes

The coordinates of start and destination are fixed in this example. We specify what condition/restrictions the return routes have by building the RouteOptions instance. We need the route for cars, not bicycles or pedestrians here.  We want to have more than 1 return route and get the turn-by-turn instructions. The details of RouteOptions can be found in Mapbox Direction API.

Using the MapboxNavigation instance to request routes by providing the routeOptions and a NavigationRouteCallback object, which determine what to do depending on the request result. When routes are successfully returned, we pass those routes to MapboxNavigation which will trigger the RouteObserver to deal with the routes.

private Point start = Point.fromLngLat(114.1756, 22.336);
private Point destination = Point.fromLngLat(114.1731, 22.2832);

private void findRoutes(){
   //for the setting, refer to the Mapbox Directions API https://docs.mapbox.com/api/navigation/directions/
   RouteOptions routeOptions = RouteOptions.builder()
           .profile(DirectionsCriteria.PROFILE_DRIVING)
           .coordinatesList(List.of(start, destination))
           .alternatives(true)     //return up to 2 alternatives path if available
           .steps(true)            //return steps and turn-by-turn instructions
           .bannerInstructions(true)   //
           //.language() // language of returned turn-bt-turn instruction
           .build();

   mapboxNavigation = MapboxNavigationApp.current();
   if(mapboxNavigation == null)
       Log.w("RequestRoutes", "mapboxNavigation is empty");
   else
       mapboxNavigation.requestRoutes(routeOptions, routerCallback);
}

private NavigationRouterCallback routerCallback = new NavigationRouterCallback(){

   @Override
   public void onRoutesReady(@NonNull List<NavigationRoute> list, @NonNull RouterOrigin routerOrigin) {
       mapboxNavigation.setNavigationRoutes(list);
       navigationRoutesList = list;
       switch(list.size()){
           case 3: viewBinding.route3Button.setVisibility(View.VISIBLE);
           case 2: viewBinding.route2Button.setVisibility(View.VISIBLE);
           case 1: viewBinding.route1Button.setVisibility(View.VISIBLE);
                   break;
       }
   }

   @Override
   public void onFailure(@NonNull List<RouterFailure> list, @NonNull RouteOptions routeOptions) {
       Log.d("RequestRoutes", "onFailure");
   }

   @Override
   public void onCanceled(@NonNull RouteOptions routeOptions, @NonNull RouterOrigin routerOrigin) {
       Log.d("RequestRoutes", "onCanceled");
   }
};

RouteObserver

When there is a change of the main route, the method onRoutesChanged() in RouteObserver is invoked. RouteLineApi is used to interpret the details of NavigationsRoutes and generate metadata for drawing paths on the mapView. Then RouteLineView will consume the metadata to draw the paths. If there are no routes, we still need to call RouteLineApi and RouteLineView instances to clear the paths on the MapView.

Then we update ViewportDataSource about the new route so that the NavigationCamera can zoom and move to show the whole path inside the phone screen.

private RoutesObserver routesObserver = new RoutesObserver() {
   @Override
   public void onRoutesChanged(@NonNull RoutesUpdatedResult routesUpdatedResult) {

       if(routesUpdatedResult.getNavigationRoutes().isEmpty()){
           // remove the route line and route arrow from the map
           Style style = viewBinding.mapView.getMapboxMap().getStyle();
           if(style != null){
               routeLineApi.clearRouteLine(routeLineErrorRouteLineClearValueExpected -> {
                   routeLineView.renderClearRouteLineValue(viewBinding.mapView.getMapboxMap().getStyle(),
                           routeLineErrorRouteLineClearValueExpected);
               });
           }
           // remove the route reference from camera position evaluations
           viewportDataSource.clearRouteData();
           viewportDataSource.evaluate();
       }else {
           // RouteLine: wrap the NavigationRoute objects and pass them
           // to the MapboxRouteLineApi to generate the data necessary to draw the route(s)
           // on the map.
           routeLineApi.setNavigationRoutes(routesUpdatedResult.getNavigationRoutes(),
                   new MapboxNavigationConsumer<Expected<RouteLineError, RouteSetValue>>() {
                       @Override
                       public void accept(Expected<RouteLineError, RouteSetValue> routeLineErrorRouteSetValueExpected) {
                           // RouteLine: The MapboxRouteLineView expects a non-null reference to the map style.
                           // the data generated by the call to the MapboxRouteLineApi above must be rendered
                           // by the MapboxRouteLineView in order to visualize the changes on the map.
                           routeLineView.renderRouteDrawData(viewBinding.mapView.getMapboxMap().getStyle(),
                                   routeLineErrorRouteSetValueExpected);
                       }
                   });
           // update the camera position to account for the new route
           viewportDataSource.onRouteChanged(routesUpdatedResult.getNavigationRoutes().get(0));
           viewportDataSource.evaluate();
           navigationCamera.requestNavigationCameraToOverview();
       }
   }
};

Change the primary route

A primary route is the route default for navigation when we start the trip. MapboxNavigation considers the first route in the list as the primary route. Therefore, I change the order of the route list and invoke setNavigationRoutes with the new list to change the primary route here.

private View.OnClickListener buttonClickListener = new View.OnClickListener() {
   @Override
   public void onClick(View view) {
       ArrayList<NavigationRoute> newList = new ArrayList<>();
       newList.addAll(navigationRoutesList);
       NavigationRoute selectedRoute = null;
       if(view.getId() == viewBinding.route1Button.getId()){
           selectedRoute = newList.remove(0);
       }else if(view.getId() == viewBinding.route2Button.getId()){
           selectedRoute = newList.remove(1);
       }else if(view.getId() == viewBinding.route3Button.getId()){
           selectedRoute = newList.remove(2);
       }
       newList.add(0, selectedRoute);
       mapboxNavigation.setNavigationRoutes(newList);
   }
};


Now the important components have been stated. I can run the app and show the screen captured at the beginning. The code can be viewed here in Github.

留言

此網誌的熱門文章

Use okhttp to download file and show progress bar

Download File into app specific storage with Retrofit

Unzipp file with Zip4j library