静止不动时使用 TYPE_ROTATION_VECTOR 实现当前的基本方向方法?

Posted

技术标签:

【中文标题】静止不动时使用 TYPE_ROTATION_VECTOR 实现当前的基本方向方法?【英文标题】:Implementing current cardinal direction method with TYPE_ROTATION_VECTOR while standing still? 【发布时间】:2019-03-24 10:44:22 【问题描述】:

android 设备上获取当前主要方向的旧示例似乎有很多,但 Google 提供的官方解决方案似乎并未出现在他们的文档中。

最古老的参考 Sensor.TYPE_ORIENTATION 已被弃用,最近的参考提到 Sensor.TYPE_ACCELEROMETERSensor.TYPE_MAGNETIC_FIELD(我尝试过但收效甚微 - 准确度会根据设备方向迅速变化)。我一直在尝试使用 this. 之类的这两个实现,我什至看到过一些使用 TYPE.GRAVITY 的实现。

most recent seem to suggestTYPE_ROTATION_VECTOR 显然是一个融合传感器 (reference),但示例实现似乎并不容易获得。

我需要使用这些位置/运动传感器,而不是 GPS,因为在需要进行测量时用户不会移动。还需要测量稳定,无论手机是平的还是垂直的(就像你在拍照一样)

在我们以某种方式拉出度数测量后,转换为基本方向似乎很容易。(https://***.com/a/25349774/1238737)

以前的解决方案

    How to get Direction in Android (Such as North, West) https://***.com/a/11068878/1238737

【问题讨论】:

【参考方案1】:

我之前从事开源地图项目,例如 OsmAnd、MapsWithMe 和 MapBox。我认为这些项目是地图和导航领域最好的 android 开源。我检查了他们的代码,发现当手机垂直然后围绕垂直轴(y)旋转时,显示指南针的 MapBox 方法是稳定的。如果旋转矢量传感器可用,它使用TYPE_ROTATION_VECTOR。否则,它使用TYPE_ORIENTATION 传感器或TYPE_ACCELEROMETERTYPE_MAGNETIC_FIELD 的组合。在使用TYPE_ACCELEROMETERTYPE_MAGNETIC_FIELD 的情况下,可以通过低通滤波器来减少结果中的振荡,以获得更平滑的值。

这是 MapBox 的指南针引擎及其用法。 .

LocationComponentCompassEngine.java:

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.Surface;
import android.view.WindowManager;
import timber.log.Timber;

import java.util.ArrayList;
import java.util.List;

/**
 * This manager class handles compass events such as starting the tracking of device bearing, or
 * when a new compass update occurs.
 */
public class LocationComponentCompassEngine implements SensorEventListener 

    // The rate sensor events will be delivered at. As the Android documentation states, this is only
    // a hint to the system and the events might actually be received faster or slower then this
    // specified rate. Since the minimum Android API levels about 9, we are able to set this value
    // ourselves rather than using one of the provided constants which deliver updates too quickly for
    // our use case. The default is set to 100ms
    private static final int SENSOR_DELAY_MICROS = 100 * 1000;
    // Filtering coefficient 0 < ALPHA < 1
    private static final float ALPHA = 0.45f;

    // Controls the compass update rate in milliseconds
    private static final int COMPASS_UPDATE_RATE_MS = 500;

    private final WindowManager windowManager;
    private final SensorManager sensorManager;
    private final List<CompassListener> compassListeners = new ArrayList<>();

    // Not all devices have a compassSensor
    @Nullable
    private Sensor compassSensor;
    @Nullable
    private Sensor gravitySensor;
    @Nullable
    private Sensor magneticFieldSensor;

    private float[] truncatedRotationVectorValue = new float[4];
    private float[] rotationMatrix = new float[9];
    private float[] rotationVectorValue;
    private float lastHeading;
    private int lastAccuracySensorStatus;

    private long compassUpdateNextTimestamp;
    private float[] gravityValues = new float[3];
    private float[] magneticValues = new float[3];

    /**
     * Construct a new instance of the this class. A internal compass listeners needed to separate it
     * from the cleared list of public listeners.
     */
    LocationComponentCompassEngine(WindowManager windowManager, SensorManager sensorManager) 
        this.windowManager = windowManager;
        this.sensorManager = sensorManager;
        compassSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
        if (compassSensor == null) 
            if (isGyroscopeAvailable()) 
                Timber.d("Rotation vector sensor not supported on device, falling back to orientation.");
                compassSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
             else 
                Timber.d("Rotation vector sensor not supported on device, falling back to accelerometer and magnetic field.");
                gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
                magneticFieldSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
            
        
    

    public void addCompassListener(@NonNull CompassListener compassListener) 
        if (compassListeners.isEmpty()) 
            onStart();
        
        compassListeners.add(compassListener);
    

    public void removeCompassListener(@NonNull CompassListener compassListener) 
        compassListeners.remove(compassListener);
        if (compassListeners.isEmpty()) 
            onStop();
        
    

    public int getLastAccuracySensorStatus() 
        return lastAccuracySensorStatus;
    

    public float getLastHeading() 
        return lastHeading;
    

    public void onStart() 
        registerSensorListeners();
    

    public void onStop() 
        unregisterSensorListeners();
    

    @Override
    public void onSensorChanged(SensorEvent event) 
        // check when the last time the compass was updated, return if too soon.
        long currentTime = SystemClock.elapsedRealtime();
        if (currentTime < compassUpdateNextTimestamp) 
            return;
        
        if (lastAccuracySensorStatus == SensorManager.SENSOR_STATUS_UNRELIABLE) 
            Timber.d("Compass sensor is unreliable, device calibration is needed.");
            return;
        
        if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) 
            rotationVectorValue = getRotationVectorFromSensorEvent(event);
            updateOrientation();

            // Update the compassUpdateNextTimestamp
            compassUpdateNextTimestamp = currentTime + COMPASS_UPDATE_RATE_MS;
         else if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) 
            notifyCompassChangeListeners((event.values[0] + 360) % 360);
         else if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) 
            gravityValues = lowPassFilter(getRotationVectorFromSensorEvent(event), gravityValues);
            updateOrientation();
         else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) 
            magneticValues = lowPassFilter(getRotationVectorFromSensorEvent(event), magneticValues);
            updateOrientation();
        
    

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) 
        if (lastAccuracySensorStatus != accuracy) 
            for (CompassListener compassListener : compassListeners) 
                compassListener.onCompassAccuracyChange(accuracy);
            
            lastAccuracySensorStatus = accuracy;
        
    

    private boolean isGyroscopeAvailable() 
        return sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) != null;
    

    @SuppressWarnings("SuspiciousNameCombination")
    private void updateOrientation() 
        if (rotationVectorValue != null) 
            SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVectorValue);
         else 
            // Get rotation matrix given the gravity and geomagnetic matrices
            SensorManager.getRotationMatrix(rotationMatrix, null, gravityValues, magneticValues);
        

        final int worldAxisForDeviceAxisX;
        final int worldAxisForDeviceAxisY;

        // Remap the axes as if the device screen was the instrument panel,
        // and adjust the rotation matrix for the device orientation.
        switch (windowManager.getDefaultDisplay().getRotation()) 
            case Surface.ROTATION_90:
                worldAxisForDeviceAxisX = SensorManager.AXIS_Z;
                worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_X;
                break;
            case Surface.ROTATION_180:
                worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_X;
                worldAxisForDeviceAxisY = SensorManager.AXIS_MINUS_Z;
                break;
            case Surface.ROTATION_270:
                worldAxisForDeviceAxisX = SensorManager.AXIS_MINUS_Z;
                worldAxisForDeviceAxisY = SensorManager.AXIS_X;
                break;
            case Surface.ROTATION_0:
            default:
                worldAxisForDeviceAxisX = SensorManager.AXIS_X;
                worldAxisForDeviceAxisY = SensorManager.AXIS_Z;
                break;
        

        float[] adjustedRotationMatrix = new float[9];
        SensorManager.remapCoordinateSystem(rotationMatrix, worldAxisForDeviceAxisX,
                worldAxisForDeviceAxisY, adjustedRotationMatrix);

        // Transform rotation matrix into azimuth/pitch/roll
        float[] orientation = new float[3];
        SensorManager.getOrientation(adjustedRotationMatrix, orientation);

        // The x-axis is all we care about here.
        notifyCompassChangeListeners((float) Math.toDegrees(orientation[0]));
    

    private void notifyCompassChangeListeners(float heading) 
        for (CompassListener compassListener : compassListeners) 
            compassListener.onCompassChanged(heading);
        
        lastHeading = heading;
    

    private void registerSensorListeners() 
        if (isCompassSensorAvailable()) 
            // Does nothing if the sensors already registered.
            sensorManager.registerListener(this, compassSensor, SENSOR_DELAY_MICROS);
         else 
            sensorManager.registerListener(this, gravitySensor, SENSOR_DELAY_MICROS);
            sensorManager.registerListener(this, magneticFieldSensor, SENSOR_DELAY_MICROS);
        
    

    private void unregisterSensorListeners() 
        if (isCompassSensorAvailable()) 
            sensorManager.unregisterListener(this, compassSensor);
         else 
            sensorManager.unregisterListener(this, gravitySensor);
            sensorManager.unregisterListener(this, magneticFieldSensor);
        
    

    private boolean isCompassSensorAvailable() 
        return compassSensor != null;
    

    /**
     * Helper function, that filters newValues, considering previous values
     *
     * @param newValues      array of float, that contains new data
     * @param smoothedValues array of float, that contains previous state
     * @return float filtered array of float
     */
    private float[] lowPassFilter(float[] newValues, float[] smoothedValues) 
        if (smoothedValues == null) 
            return newValues;
        
        for (int i = 0; i < newValues.length; i++) 
            smoothedValues[i] = smoothedValues[i] + ALPHA * (newValues[i] - smoothedValues[i]);
        
        return smoothedValues;
    

    /**
     * Pulls out the rotation vector from a SensorEvent, with a maximum length
     * vector of four elements to avoid potential compatibility issues.
     *
     * @param event the sensor event
     * @return the events rotation vector, potentially truncated
     */
    @NonNull
    private float[] getRotationVectorFromSensorEvent(@NonNull SensorEvent event) 
        if (event.values.length > 4) 
            // On some Samsung devices SensorManager.getRotationMatrixFromVector
            // appears to throw an exception if rotation vector has length > 4.
            // For the purposes of this class the first 4 values of the
            // rotation vector are sufficient (see crbug.com/335298 for details).
            // Only affects Android 4.3
            System.arraycopy(event.values, 0, truncatedRotationVectorValue, 0, 4);
            return truncatedRotationVectorValue;
         else 
            return event.values;
        
    

    public static float shortestRotation(float heading, float previousHeading) 
        double diff = previousHeading - heading;
        if (diff > 180.0f) 
            heading += 360.0f;
         else if (diff < -180.0f) 
            heading -= 360.f;
        
        return heading;
    


CompassListener.java:

/**
 * Callbacks related to the compass
 */
public interface CompassListener 

    /**
     * Callback's invoked when a new compass update occurs. You can listen into the compass updates
     * using @link LocationComponent#addCompassListener(CompassListener) and implementing these
     * callbacks. Note that this interface is also used internally to to update the UI chevron/arrow.
     *
     * @param userHeading the new compass heading
     */
    void onCompassChanged(float userHeading);

    /**
     * This gets invoked when the compass accuracy status changes from one value to another. It
     * provides an integer value which is identical to the @code SensorManager class constants:
     * <ul>
     * <li>@link android.hardware.SensorManager#SENSOR_STATUS_NO_CONTACT</li>
     * <li>@link android.hardware.SensorManager#SENSOR_STATUS_UNRELIABLE</li>
     * <li>@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_LOW</li>
     * <li>@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_MEDIUM</li>
     * <li>@link android.hardware.SensorManager#SENSOR_STATUS_ACCURACY_HIGH</li>
     * </ul>
     *
     * @param compassStatus the new accuracy of this sensor, one of
     *                      @code SensorManager.SENSOR_STATUS_*
     */
    void onCompassAccuracyChange(int compassStatus);


MainActivity.java:

import android.content.Context;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.WindowManager;
import android.widget.TextView;

import java.util.Locale;

public class MainActivity extends AppCompatActivity 

    private LocationComponentCompassEngine compassEngine;
    private float previousCompassBearing = -1f;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TextView textView = findViewById(R.id.textView);

        CompassListener compassListener = new CompassListener() 

            @Override
            public void onCompassChanged(float targetCompassBearing) 
                if (previousCompassBearing < 0) 
                    previousCompassBearing = targetCompassBearing;
                
                float normalizedBearing =
                        LocationComponentCompassEngine.shortestRotation(targetCompassBearing, previousCompassBearing);
                previousCompassBearing = targetCompassBearing;

                String status = "NO_CONTACT";
                switch (compassEngine.getLastAccuracySensorStatus()) 
                    case SensorManager.SENSOR_STATUS_NO_CONTACT:
                        status = "NO_CONTACT";
                        break;
                    case SensorManager.SENSOR_STATUS_UNRELIABLE:
                        status = "UNRELIABLE";
                        break;
                    case SensorManager.SENSOR_STATUS_ACCURACY_LOW:
                        status = "ACCURACY_LOW";
                        break;
                    case SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM:
                        status = "ACCURACY_MEDIUM";
                        break;
                    case SensorManager.SENSOR_STATUS_ACCURACY_HIGH:
                        status = "ACCURACY_HIGH";
                        break;
                

                textView.setText(String.format(Locale.getDefault(),
                        "CompassBearing: %f\nAccuracySensorStatus: %s", normalizedBearing, status));
            

            @Override
            public void onCompassAccuracyChange(int compassStatus) 
            

        ;

        WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        compassEngine = new LocationComponentCompassEngine(windowManager, sensorManager);
        compassEngine.addCompassListener(compassListener);

        compassEngine.onStart();
    

    @Override
    protected void onDestroy() 
        super.onDestroy();
        compassEngine.onStop();
    


【讨论】:

有了你描述的实现,这个主要的方向方法***.com/a/2131294/1238737是相关的吗? 如果您想显示指标视图,normalizedBearing 为您提供更好的更改。您可以使用hastebin.com/uvukulecim.cpp 来获取您提到的链接所说的主要方向。

以上是关于静止不动时使用 TYPE_ROTATION_VECTOR 实现当前的基本方向方法?的主要内容,如果未能解决你的问题,请参考以下文章

cartographer当机器人不动时,同时收到landmark,如何解决定位问题?

在运行时更改融合位置优先级

unity 2017.1 播放后画面静止不动的原因是?谢谢!

我电脑CPU的使用率突然达到100%静止不动,程序运行缓慢这是为啥?

CoreMotion框架--加速计和陀螺仪

PHP版本,微信扫码支付native模式二,扫码支付成功,页面静止不动,怎么可以支付回调URL?