Показание азимута меняется на противоположное, когда пользователь держит телефон в вертикальном положении - PullRequest
0 голосов
/ 21 ноября 2018

Я применил показания компаса в соответствии с обычными рекомендациями, которые я мог найти в Интернете.Я использую тип датчика 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 телефона, которая становитсявертикальный на вертикальном телефоне, и его наземная ориентация направлена ​​к вам, когда телефон наклонен к вам.

Что можно сделать, чтобы улучшить это поведение, чтобы оно не чувствительно к высоте звука телефона?

1 Ответ

0 голосов
/ 21 ноября 2018

Анализ

Видимо, этот код отслеживает ориентацию оси 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: вектор, указывающий на телефон front

Векторы описаны с точки зрения системы координат Земли ( восток , север и небо ).

Имея это в виду, мы можем интерпретировать код в getOrientation():

  1. выберите ось телефона вверх (столбец матрицы 1, хранится в элементах массива 1, 4, 7)
  2. проецируйте его на горизонтальную плоскость Земли (это легко, просто игнорируйтекомпонент sky , хранящийся в элементе 7)
  3. Используйте atan2, чтобы вывести угол из оставшихся восточных и север компоненты вектора.

Здесь скрывается еще одна тонкость: подпись atan2 равна

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

Обратите внимание на порядок параметров: y, затем x.Но getOrientation передает аргументы в порядке восток , север .Это позволяет достичь двух вещей:

  • делает север исходной осью (в геометрии это ось x)
  • отражает угол: геометрические углы являютсяпо часовой стрелке, но азимут должен быть углом по часовой стрелке с севера

Естественно, когда ось up телефона переходит в вертикальное положение ("в небо"), а затем за его пределы, его азимут поворачивается на 180 градусов,Мы можем исправить это очень простым способом: вместо этого мы будем использовать правую ось телефона.Обратите внимание на следующее:

  • , когда телефон расположен горизонтально и направлен на север, его ось вправо совмещена с осью восток .Ось восток в системе координат Земли является геометрической осью "x", поэтому наша нулевая точка отсчета верна прямо из коробки.
  • , когда телефон поворачиваетсявправо (на восток), его азимут должен возрасти, но его геометрический угол становится отрицательным.Поэтому мы должны перевернуть знак геометрического угла.

Решение

Итак, наша новая формула такова:

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

И это тривиальное изменение - это все, что вынеобходимость!Не нужно звонить getOrientation, просто примените это к матрице ориентации.

Улучшенное решение

Пока все хорошо.Но что, если пользователь использует телефон в альбомной ориентации?На оси телефона это не влияет, но теперь пользователь воспринимает направление «влево» или «вправо» телефона как «вперед» (в зависимости от того, как пользователь повернул телефон).Мы можем исправить это, проверив свойство Display.rotation.Если экран повернуть, мы будем использовать ось телефона up , чтобы играть ту же роль, что и ось вправо выше.

Таким образом, полный кодслушатель ориентации становится таким:

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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...