代码之家  ›  专栏  ›  技术社区  ›  Marko Topolnik

当用户竖起手机时,方位角读数变为相反

  •  1
  • Marko Topolnik  · 技术社区  · 6 年前

    我已经实现了罗盘读数根据通常的建议,我可以在网上找到。我用 ROTATION_VECTOR 传感器类型,我把它转换成 (azimuth, pitch, roll) 三重使用标准的api调用。这是我的代码:

    fun Fragment.receiveAzimuthUpdates(
            azimuthChanged: (Float) -> Unit,
            accuracyChanged: (Int) -> Unit
    ) {
        val sensorManager = activity!!.getSystemService(Context.SENSOR_SERVICE)
                as SensorManager
        val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)!!
        sensorManager.registerListener(OrientationListener(azimuthChanged, accuracyChanged),
                sensor, 10_000)
    }
    
    private class OrientationListener(
            private val azimuthChanged: (Float) -> Unit,
            private val accuracyChanged: (Int) -> Unit
    ) : SensorEventListener {
        private val rotationMatrix = FloatArray(9)
        private val orientation = FloatArray(3)
    
        override fun onSensorChanged(event: SensorEvent) {
            if (event.sensor.type != Sensor.TYPE_ROTATION_VECTOR) return
            SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)
            SensorManager.getOrientation(rotationMatrix, orientation)
            azimuthChanged(orientation[0])
        }
    
        override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
            if (sensor.type == Sensor.TYPE_ROTATION_VECTOR) {
                accuracyChanged(accuracy)
            }
        }
    }
    

    这样做的结果是,当你水平握着手机时,你的行为相当好,就像你是一个真正的指南针。然而,当你像相机一样拿着它,笔直地放在面前时,阅读就会崩溃。如果你稍微倾斜它,使它向你倾斜,方位角就会转向相反的方向(突然180度旋转)。

    显然,这段代码跟踪手机y轴的方向,在直立的手机上,y轴变得垂直,当手机向你倾斜时,它的地面方向是朝向你的。

    我该怎么做才能改善这种行为,使它对手机的音高不敏感?

    1 回复  |  直到 6 年前
        1
  •  2
  •   Marko Topolnik    6 年前

    分析

    显然,这段代码跟踪手机y轴的方向,在直立的手机上,y轴变得垂直,当手机向你倾斜时,它的地面方向是朝向你的。

    是的,这是正确的。你可以检查 getOrientation() 看看发生了什么:

    public static float[] getOrientation(float[] R, float[] values) {
        /*
         *   /  R[ 0]   R[ 1]   R[ 2]  \
         *   |  R[ 3]   R[ 4]   R[ 5]  |
         *   \  R[ 6]   R[ 7]   R[ 8]  /
         */
         values[0] = (float) Math.atan2(R[1], R[4]);
         ...
    

    values[0] 是你得到的方位角值。

    你可以解释旋转矩阵 R 作为指向设备三个主轴的矢量分量:

    • 第0列:指向电话的矢量 正确的
    • 第1列:指向手机的矢量 向上的
    • 第2列:指向手机的矢量 前面

    矢量是从地球坐标系的角度描述的( , 天空 )

    考虑到这一点,我们可以在 获取方向() :

    1. 选择手机的 向上的 轴(矩阵列1,存储在数组元素1、4、7中)
    2. 把它投射到地球的水平面上(这很容易,忽略 天空 存储在元素7中的组件)
    3. 使用 atan2 从其余的角度推断 向量的分量。

    这里隐藏着另一个微妙之处: ATAN2

    public static double atan2(double y, double x);
    

    注意参数顺序: y 然后 x . 但是 getOrientation 在中传递参数 , 秩序。这实现了两件事:

    • 使 参考轴(在几何学中是 X 轴)
    • 反射角度:几何角度是逆时针的,但方位角必须是从北开始的顺时针角度

    当然,当电话 向上的 轴是垂直的(“向天空”)然后超过,它的方位角翻转180度。我们可以用一种非常简单的方法解决这个问题:我们将使用 正确的 取而代之的是轴心。注意以下几点:

    • 当手机水平朝北时, 正确的 轴与 轴。这个 在地球坐标系中,轴是“x”几何轴,所以我们的0角参考是正确的。
    • 当手机右转(向东)时,它的方位角应该上升,但它的几何角度变为负值。因此我们必须翻转几何角的符号。

    解决方案

    所以我们的新公式是:

    val azimuth = -atan2(R[3], R[0])
    

    而这些琐碎的改变就是你所需要的!不用打电话 GetOrientation公司 ,只需将其应用于方向矩阵。

    改进的解决方案

    到目前为止,还不错。但如果用户在横向使用手机呢?手机的轴不受影响,但现在用户将手机的“左”或“右”方向视为“前方”(取决于用户转动手机的方式)。我们可以通过检查 Display.rotation 财产。如果屏幕旋转,我们将使用 向上的 手机轴的作用与 正确的 轴以上。

    因此,定向侦听器的完整代码如下:

    private class OrientationListener(
            private val activity: Activity,
            private val azimuthChanged: (Float) -> Unit,
            private val accuracyChanged: (Int) -> Unit
    ) : SensorEventListener {
        private val rotationMatrix = FloatArray(9)
    
        override fun onSensorChanged(event: SensorEvent) {
            if (event.sensor.type != Sensor.TYPE_ROTATION_VECTOR) return
            SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)
            val (matrixColumn, sense) = when (val rotation = 
                    activity.windowManager.defaultDisplay.rotation
            ) {
                Surface.ROTATION_0 -> Pair(0, 1)
                Surface.ROTATION_90 -> Pair(1, -1)
                Surface.ROTATION_180 -> Pair(0, -1)
                Surface.ROTATION_270 -> Pair(1, 1)
                else -> error("Invalid screen rotation value: $rotation")
            }
            val x = sense * rotationMatrix[matrixColumn]
            val y = sense * rotationMatrix[matrixColumn + 3]
            azimuthChanged(-atan2(y, x))
        }
    
        override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
            if (sensor.type == Sensor.TYPE_ROTATION_VECTOR) {
                accuracyChanged(accuracy)
            }
        }
    }
    

    使用这段代码,您得到的行为与google地图上的完全相同。