首页 > 解决方案 > Mapbox SDK - onMilestoneEvent 根本不会触发 + 无法设置自定义里程碑

问题描述


我在尝试使用自定义里程碑设置里程碑事件的侦听器时遇到问题。我按照文档上的说明进行操作,但 Milestone 事件中的代码似乎根本没有触发。我的目标是获得 BannerInstruction 并在下一步前 10 米触发默认指令。然后,横幅指令测试(例如“左转”)将通过 BLE(我正在使用 RxAndroidBle 库)发送到另一台设备。

到目前为止,我通过触发 willDisplay 侦听器成功通过蓝牙发送横幅指令(每次指令发生变化时都会激活,但问题是我无法控制指令显示的时间。

我是 Java 的初学者,所以我不完全确定我所做的是否正确。也请不要介意写得不好的代码,我还在学习!

这是 MainActivity.java:

    package com.bpnavi.backpacknavigator;

import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.mapbox.api.directions.v5.DirectionsCriteria;
import com.mapbox.api.directions.v5.MapboxDirections;
import com.mapbox.api.directions.v5.models.BannerInstructions;
import com.mapbox.mapboxsdk.annotations.Marker;
import com.mapbox.mapboxsdk.annotations.MarkerOptions;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.location.modes.RenderMode;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.MapView;



// classes needed to add the location component
import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import android.location.Location;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.Toast;
import com.mapbox.mapboxsdk.geometry.LatLng;
import android.support.annotation.NonNull;
import com.mapbox.mapboxsdk.location.LocationComponent;
import com.mapbox.mapboxsdk.location.modes.CameraMode;
import com.mapbox.services.android.navigation.ui.v5.NavigationLauncherOptions;
import com.mapbox.android.core.permissions.PermissionsListener;
import com.mapbox.android.core.permissions.PermissionsManager;


// classes to calculate a route
import com.mapbox.services.android.navigation.ui.v5.NavigationView;
import com.mapbox.services.android.navigation.ui.v5.NavigationViewOptions;
import com.mapbox.services.android.navigation.ui.v5.OnNavigationReadyCallback;
import com.mapbox.services.android.navigation.ui.v5.listeners.BannerInstructionsListener;
import com.mapbox.services.android.navigation.ui.v5.route.NavigationMapRoute;
import com.mapbox.services.android.navigation.v5.instruction.Instruction;
import com.mapbox.services.android.navigation.v5.milestone.Milestone;
import com.mapbox.services.android.navigation.v5.milestone.MilestoneEventListener;
import com.mapbox.services.android.navigation.v5.milestone.RouteMilestone;
import com.mapbox.services.android.navigation.v5.milestone.StepMilestone;
import com.mapbox.services.android.navigation.v5.milestone.Trigger;
import com.mapbox.services.android.navigation.v5.milestone.TriggerProperty;
import com.mapbox.services.android.navigation.v5.navigation.NavigationEventListener;
import com.mapbox.services.android.navigation.v5.navigation.NavigationRoute;
import com.mapbox.api.directions.v5.models.DirectionsResponse;
import com.mapbox.api.directions.v5.models.DirectionsRoute;
import com.mapbox.services.android.navigation.v5.navigation.MapboxNavigation;
import com.mapbox.services.android.navigation.v5.navigation.MapboxNavigationOptions;
import com.mapbox.services.android.navigation.v5.routeprogress.ProgressChangeListener;
import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress;
import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgressState;
import com.mapbox.services.android.navigation.v5.utils.RouteUtils;

import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import timber.log.Timber;


// classes needed to launch navigation UI
import android.view.View;
import android.widget.Button;
import com.mapbox.services.android.navigation.ui.v5.NavigationLauncher;

import com.mapbox.services.android.navigation.v5.offroute.OffRouteListener;
import com.mapbox.services.android.navigation.v5.routeprogress.ProgressChangeListener;
import com.mapbox.services.android.navigation.v5.routeprogress.RouteProgress;
import com.polidea.rxandroidble2.RxBleClient;
import com.polidea.rxandroidble2.RxBleConnection;
import com.polidea.rxandroidble2.RxBleDevice;
import com.polidea.rxandroidble2.internal.RxBleLog;
import com.polidea.rxandroidble2.scan.ScanSettings;

import android.util.Log;



import java.util.List;
import java.util.UUID;


public class MainActivity extends AppCompatActivity implements PermissionsListener, MapboxMap.OnMapClickListener, OnNavigationReadyCallback, BannerInstructionsListener,
        MilestoneEventListener, OffRouteListener, ProgressChangeListener {
    private MapView mapView;
    private NavigationView navigationView;

    private MapboxMap mapboxMap;
    private PermissionsManager permissionsManager;
    private Location originLocation;

    // variables for adding a marker
    private Marker destinationMarker;
    private LatLng originCoord;
    private LatLng destinationCoord;

    // variables for calculating and drawing a route
    private Point originPosition;
    private Point destinationPosition;
    private DirectionsRoute currentRoute;
    private static final String TAG = "DirectionsActivity";
    private NavigationMapRoute navigationMapRoute;
    private MapboxDirections client;
    private Button startButton;

    public static final String BACKPACK_DEVICE_ADDRESS = "123";
    public static final String TEST_DEVICE_ADDRESS = "123";
    private static final UUID WRITE_CHARACTERISTIC = UUID.fromString("abc123");
    private Disposable disposable;
    private RxBleDevice device;
    private BannerInstructions BannerInstructionMilestone;
    private MapboxNavigation navigation;

    private static final int INSTR_MILESTONE = 1001;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        RxBleClient rxBleClient = RxBleClient.create(this);

        Disposable scanSubscription = rxBleClient.scanBleDevices(
                new ScanSettings.Builder()
                        // .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) // change if needed
                        // .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) // change if needed
                        .build()
                // add filters if needed
        )
                .subscribe(
                        scanResult -> {
                            // Process scan result here.
                        },
                        throwable -> {
                            // Handle an error here.
                        }
                );

        // When done, just dispose.
        scanSubscription.dispose();

        Disposable flowDisposable = rxBleClient.observeStateChanges()
                .switchMap(state -> { // switchMap makes sure that if the state will change the rxBleClient.scanBleDevices() will dispose and thus end the scan
                    switch (state) {

                        case READY:
                            // everything should work
                            return rxBleClient.scanBleDevices();
                        case BLUETOOTH_NOT_AVAILABLE:
                            // basically no functionality will work here
                        case LOCATION_PERMISSION_NOT_GRANTED:
                            // scanning and connecting will not work
                        case BLUETOOTH_NOT_ENABLED:
                            // scanning and connecting will not work
                        case LOCATION_SERVICES_NOT_ENABLED:
                            // scanning will not work
                        default:
                            return Observable.empty();
                    }
                })
                .subscribe(
                        rxBleScanResult -> {
                            // Process scan result here.
                        },
                        throwable -> {
                            // Handle an error here.
                        }
                );

                // When done, just dispose.
                flowDisposable.dispose();



        device = rxBleClient.getBleDevice(TEST_DEVICE_ADDRESS);

        Disposable disposable = device.establishConnection(true) // <-- autoConnect flag
                .subscribe(
                        rxBleConnection -> {
                            // All GATT operations are done through the rxBleConnection.
                        },
                        throwable -> {
                            // Handle an error here.
                        }
                );
//        disposable.dispose();


        Mapbox.getInstance(this, getString(R.string.access_token));
        setContentView(R.layout.activity_main);
        mapView = (MapView) findViewById(R.id.mapView);
        startButton = findViewById(R.id.startButton);

        startButton.setOnClickListener(v -> {
            setTheme(R.style.Theme_AppCompat_NoActionBar);
            setContentView(R.layout.activity_navigation);
            navigationView = findViewById(R.id.navigationView);
            navigationView.onCreate(savedInstanceState);
            navigationView.initialize(this);

//            boolean simulateRoute = true;
//            NavigationLauncherOptions options = NavigationLauncherOptions.builder()
//                    .directionsRoute(currentRoute)
//                    .shouldSimulateRoute(simulateRoute)
//                    .build();
//            // Call this method with Context from within an Activity
//            NavigationLauncher.startNavigation(MainActivity.this, options);
        });

        MapboxNavigationOptions navOptions = MapboxNavigationOptions.builder().isDebugLoggingEnabled(true).build();
        navigation = new MapboxNavigation(getApplicationContext(), getString(R.string.access_token), navOptions);
        navigation.addMilestoneEventListener(this);


        navigation.addMilestone(new StepMilestone.Builder()
                .setIdentifier(INSTR_MILESTONE)
                .setInstruction(this.myInstruction)
                .setTrigger(
                        Trigger.all(
                                Trigger.lt(TriggerProperty.STEP_DISTANCE_REMAINING_METERS, 10)))
                .build()
        );

        mapView.onCreate(savedInstanceState);
        mapView.getMapAsync(mapboxMap -> {
            MainActivity.this.mapboxMap = mapboxMap;
            enableLocationComponent();

            originCoord = new LatLng(originLocation.getLatitude(), originLocation.getLongitude());

            mapboxMap.addOnMapClickListener(this);

            //Set stuff here
            mapboxMap.addMarker(new MarkerOptions()
                    .position(new LatLng(45.506833, 9.163333))
                    .title("Politecnico di Milano")
                    .snippet("Dipartimento di Design")
            );
        });

    }

    @SuppressWarnings( {"MissingPermission"})
    private void enableLocationComponent() {
        // Check if permissions are enabled and if not request
        if (PermissionsManager.areLocationPermissionsGranted(this)) {
            // Activate the MapboxMap LocationComponent to show user location
            // Adding in LocationComponentOptions is also an optional parameter
            LocationComponent locationComponent = mapboxMap.getLocationComponent();
            locationComponent.activateLocationComponent(this);
            locationComponent.setLocationComponentEnabled(true);
            // Set the component's camera mode
            locationComponent.setCameraMode(CameraMode.TRACKING_GPS);
            locationComponent.setRenderMode(RenderMode.GPS);
            originLocation = locationComponent.getLastKnownLocation();

        } else {
            permissionsManager = new PermissionsManager(this);
            permissionsManager.requestLocationPermissions(this);
        }
    }

    @Override
    public void onMapClick(@NonNull LatLng point){

        if (destinationMarker != null) {
            mapboxMap.removeMarker(destinationMarker);
        }
        destinationCoord = point;
        destinationMarker = mapboxMap.addMarker(new MarkerOptions()
                .position(destinationCoord)
        );

            destinationPosition = Point.fromLngLat(destinationCoord.getLongitude(), destinationCoord.getLatitude());
            originPosition = Point.fromLngLat(originCoord.getLongitude(), originCoord.getLatitude());
            getRoute(originPosition, destinationPosition);
    }




    private void getRoute(Point origin, Point destination) {

        Toast.makeText(this, "Calculating route...", Toast.LENGTH_SHORT).show();

        MapboxDirections.builder()
                .origin(origin)
                .destination(destination)
                .accessToken(getString(R.string.access_token))
                .profile(DirectionsCriteria.PROFILE_CYCLING)
                .bannerInstructions(true)
                .steps(true)
                .roundaboutExits(true)
                .build();

        NavigationRoute.builder(this)
                .accessToken(getString(R.string.access_token))
                .origin(origin)
                .destination(destination)
                .profile(DirectionsCriteria.PROFILE_CYCLING)
                .voiceUnits("metric")
                .build()
                .getRoute(new Callback<DirectionsResponse>() {
                    @Override
                    public void onResponse(Call<DirectionsResponse> call, Response<DirectionsResponse> response) {
                        // You can get the generic HTTP info about the response
                        Log.d(TAG, "Response code: " + response.code());
                        if (response.body() == null) {
                            Log.e(TAG, "No routes found, make sure you set the right user and access token.");
                            return;
                        } else if (response.body().routes().size() < 1) {
                            Log.e(TAG, "No routes found");
                            return;
                        }

                        currentRoute = response.body().routes().get(0);

                        // Draw the route on the map
                        if (navigationMapRoute != null) {
                            navigationMapRoute.removeRoute();
                        } else {
                            navigationMapRoute = new NavigationMapRoute(null, mapView, mapboxMap, R.style.NavigationMapRoute);

                            startButton.setVisibility(View.VISIBLE);
                            startButton.setBackgroundResource(R.color.colorPrimary);

                        }
                        navigationMapRoute.addRoute(currentRoute);
                    }

                    @Override
                    public void onFailure(Call<DirectionsResponse> call, Throwable throwable) {
                        Log.e(TAG, "Error: " + throwable.getMessage());
                    }
                });
    }



    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        permissionsManager.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    @Override
    public void onExplanationNeeded(List<String> permissionsToExplain) {
        Toast.makeText(this, R.string.user_location_permission_explanation, Toast.LENGTH_LONG).show();
    }

    @Override
    public void onPermissionResult(boolean granted) {
        if (granted) {
            enableLocationComponent();
        } else {
            Toast.makeText(this, R.string.user_location_permission_not_granted, Toast.LENGTH_LONG).show();
            finish();
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        mapView.onStart();
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        int REQUEST_ENABLE_BT = 1;
        this.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mapView.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mapView.onPause();
        }

    @Override
    protected void onStop() {
        super.onStop();
        mapView.onStop();
    }


    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        mapView.onDestroy();
        if (disposable != null) {
            disposable.dispose();
            disposable = null;
        }
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        mapView.onLowMemory();
    }


    @Override
    public void onNavigationReady(boolean isRunning) {

        NavigationViewOptions options = NavigationViewOptions.builder()
                .directionsRoute(currentRoute)
                .shouldSimulateRoute(true)
                .bannerInstructionsListener(this)
                .milestoneEventListener(MainActivity.this)
                .build();

        navigationView.startNavigation(options);

    }

    @Override
    public void onProgressChange(Location location, RouteProgress routeProgress) {
        Toast.makeText(this, "change", Toast.LENGTH_LONG).show();
    }

    @Override
    public void userOffRoute(Location location) {
        Toast.makeText(this, "offRoute", Toast.LENGTH_LONG).show();
    }

    @Override
    public void onMilestoneEvent(RouteProgress routeProgress, String instruction, Milestone milestone) {

        Context context = getApplicationContext();
        String instr = myInstruction.buildInstruction(routeProgress);
        int duration = Toast.LENGTH_SHORT;

        Toast.makeText(context, instr, duration).show();
    }

    Instruction myInstruction = new Instruction() {

        @Override
        public String buildInstruction(RouteProgress routeProgress) {
            return routeProgress.currentLegProgress().upComingStep().maneuver().type()+" "+routeProgress.currentLegProgress().upComingStep().maneuver().modifier();
            }

        };

    @Override
    public BannerInstructions willDisplay(BannerInstructions instructions) {

        RxBleClient rxBleClient = RxBleClient.create(this);

        Context context = getApplicationContext();
        String instr = instructions.primary().type()+" "+instructions.primary().modifier();

        int duration = Toast.LENGTH_SHORT;

//        Toast.makeText(context, instr, duration).show();

        device = rxBleClient.getBleDevice(TEST_DEVICE_ADDRESS);

        Disposable disposable = device.establishConnection(true) // <-- autoConnect flag
                .flatMapSingle(rxBleConnection -> rxBleConnection.writeCharacteristic(WRITE_CHARACTERISTIC, instr.getBytes()))
                .subscribe(
                        characteristicValue -> {
                            // Characteristic value confirmed.
                        },
                        throwable -> {
                            // Handle an error here.
                        }
                );

        // When done... dispose and forget about connection teardown :)
//        disposable.dispose();

        return instructions;
    }
}

我究竟做错了什么?

更新:在缺乏更好的解决方案的情况下,我找到了一种解决方法:使用onProgressChange侦听器,我使用routeProgress对象来获取我需要在正确的时间显示指令的所有数据,并且每一步只需一次。

@Override
    public void onProgressChange(Location location, RouteProgress routeProgress) {
        if (routeProgress.currentLegProgress().currentStepProgress().distanceRemaining()>20) {
            noRe = false;
        }

            Context context = getApplicationContext();
        String instr = myInstruction.buildInstruction(routeProgress).replaceAll(" ", "");
        int duration = Toast.LENGTH_SHORT;

        if (instr.length() < 20 &&
                !noRe &&
                routeProgress.currentLegProgress().currentStepProgress().distanceRemaining() < activationDistance
                ) {
            noRe = true;
            Toast.makeText(context, instr, duration).show();
            }
    }

标签: androidmapboxmapbox-android

解决方案


感谢您查看导航 SDK!

我认为这里的问题是您用于StepMilestone. 我们目前1用于我们的VoiceInstructionMilestone. 10您可以将其设置为其他类似的东西20吗?谢谢!

查看更新后的代码后,问题似乎是您正在创建两个导航场景并将您Milestone的添加到未使用的导航场景中。

MapboxNavigation在上面的代码中从未使用过(据我所知)。 NavigationLauncher为转弯 UI 创建一个“幕后”的新实例。如果您想使用带有自定义 s 的转弯 UI Milestone,您需要NavigationViewOptions#milestones使用NavigationView. 有关此设置,请参见此处。谢谢!


推荐阅读