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.
留言
發佈留言