Вычислить заголовок компаса из DeviceOrientation Event API

Для веб-приложения дополненной реальности для смартфонов я пытаюсь вычислить направление компаса, когда пользователь держит устройство в руке, с экраном в вертикальной плоскости и верхней частью экрана, направленной вверх.

Я взял предложенную формулу с http://dev.w3.org/geo/api/spec-source-orientation (см. Работающий пример) и реализовал следующую функцию:

function compassHeading(alpha, beta, gamma) {
    var a1, a2, b1, b2;
    if ( beta !== 0 || gamma !== 0 ) {
        a1 = -Math.cos(alpha) * Math.sin(gamma);
        a2 = Math.sin(alpha) * Math.sin(beta) * Math.cos(gamma);
        b1 = -Math.sin(alpha) * Math.sin(gamma);
        b2 = Math.cos(alpha) * Math.sin(beta) * Math.cos(gamma);
        return Math.atan((a1 - a2) / (b1 + b2)).toDeg();
    }
    else {
        return 0;
    }
}

в то время как.toDeg() является любезным расширением объекта Number http://www.movable-type.co.uk/scripts/latlong.html

/** Converts radians to numeric (signed) degrees */
if (typeof Number.prototype.toDeg == 'undefined') {
    Number.prototype.toDeg = function() {
        return this * 180 / Math.PI;
    };
}  

Однако проблема заключается в том, что расчетное значение курса компаса изменяется от -75 до 80, даже если устройство (Google Galaxy Nexus) установлено в статическом положении. Похоже, это происходит как в Google Chrome BETA, так и в FF BETA 23.

Кто-то видит ошибку в моем подходе или знает более надежный способ расчета курса компаса?

4 ответа

Решение

Шаги, необходимые для определения направления компаса в соответствии с рабочим примером, приведенным в спецификации*, следующие:

  • Преобразовать возвращенную DeviceOrientation alpha, beta а также gamma значения от градусов до радиан как alphaRad, betaRad, gammaRad,
  • Вычислить вращение A (rA) и вращение B (rB) компонентов согласно проработанному примеру в спецификации, используя alphaRad, betaRad а также gammaRad (как показано в примере кода ниже).
  • вычисление compassHeading = Math.atan(rA / rB),
  • Преобразовать возвращенные заголовки полунульных окружностей в заголовки полных окружностей в диапазоне [0-360) градусов.
  • Перерабатывать compassHeading от радианов до градусов (необязательно).

Вот обработанный пример из спецификации, реализованной в JavaScript:

function compassHeading(alpha, beta, gamma) {

  // Convert degrees to radians
  var alphaRad = alpha * (Math.PI / 180);
  var betaRad = beta * (Math.PI / 180);
  var gammaRad = gamma * (Math.PI / 180);

  // Calculate equation components
  var cA = Math.cos(alphaRad);
  var sA = Math.sin(alphaRad);
  var cB = Math.cos(betaRad);
  var sB = Math.sin(betaRad);
  var cG = Math.cos(gammaRad);
  var sG = Math.sin(gammaRad);

  // Calculate A, B, C rotation components
  var rA = - cA * sG - sA * sB * cG;
  var rB = - sA * sG + cA * sB * cG;
  var rC = - cB * cG;

  // Calculate compass heading
  var compassHeading = Math.atan(rA / rB);

  // Convert from half unit circle to whole unit circle
  if(rB < 0) {
    compassHeading += Math.PI;
  }else if(rA < 0) {
    compassHeading += 2 * Math.PI;
  }

  // Convert radians to degrees
  compassHeading *= 180 / Math.PI;

  return compassHeading;

}

window.addEventListener('deviceorientation', function(evt) {

  var heading = null;

  if(evt.absolute === true && evt.alpha !== null) {
    heading = compassHeading(evt.alpha, evt.beta, evt.gamma);
  }

  // Do something with 'heading'...

}, false);

Вы также можете просмотреть демонстрацию кода, представленного выше.

На момент написания статьи (17 февраля 2014 г.) это в настоящее время работает в:

  • Google Chrome для Android
  • Opera Mobile для Android
  • Firefox Beta для Android

Другие браузеры еще не соответствуют калибровке DeviceOrientation, описанной в спецификации события DeviceOrientation, и / или не предоставляют absolute Значения данных DeviceOrientation, делающие невозможным определение compassHeading с неполными данными.

* Определение направления по компасу горизонтальной составляющей вектора, которая ортогональна к экрану устройства и указывает на заднюю часть экрана.

2023 Ответ

Формула для расчета направления по компасу по абсолютным значениям альфа, бета и гамма выглядит следующим образом:

      compass = -(alpha + beta * gamma / 90);
compass -= Math.floor(compass / 360) * 360; // Wrap to range [0,360]

Приведенная выше формула хорошо работает для нулевых значений бета и гаммы (например, телефон лежит на столе), а также работает для бета=90 (например, если держать его вертикально).


На дворе 2023 год, и iOS до сих пор не поддерживает абсолютное DeviceOrientationEvent, а Android по-прежнему не поддерживает компас напрямую. Следующий код TypeScript показывает, как получить значение компаса для обоих типов устройств. Вы можете преобразовать его в JavaScript, удалив спецификаторы типа после двоеточий.

      function startCompassListener(callback: (compass: number) => void) {
    if (!window["DeviceOrientationEvent"]) {
        console.warn("DeviceOrientation API not available");
        return;
    }
    let absoluteListener = (e: DeviceOrientationEvent) => {
        if (!e.absolute || e.alpha == null || e.beta == null || e.gamma == null)
            return;
        let compass = -(e.alpha + e.beta * e.gamma / 90);
        compass -= Math.floor(compass / 360) * 360; // Wrap into range [0,360].
        window.removeEventListener("deviceorientation", webkitListener);
        callback(compass);
    };
    let webkitListener = (e) => {
        let compass = e.webkitCompassHeading;
        if (compass!=null && !isNaN(compass)) {
            callback(compass);
            window.removeEventListener("deviceorientationabsolute", absoluteListener);
        }
    }

    function addListeners() {
        // Add both listeners, and if either succeeds then remove the other one.
        window.addEventListener("deviceorientationabsolute", absoluteListener);
        window.addEventListener("deviceorientation", webkitListener);
    }

    if (typeof (DeviceOrientationEvent["requestPermission"]) === "function") {
        DeviceOrientationEvent["requestPermission"]()
            .then(response => {
                if (response == "granted") {
                    addListeners();
                } else
                    console.warn("Permission for DeviceMotionEvent not granted");
            });
    } else
        addListeners();
}

При тестировании я обнаружил, что абсолютные значения моего Android (альфа, бета, гамма) были очень неточными (погрешность в альфе ~60 градусов). Однако, перевернув устройство, оно откалибровалось и получило ошибку ~5 градусов.

Кажется, что получение заголовка компаса из событий ориентации устройства не рекомендуется, возможно, по соображениям конфиденциальности. Вы должны взглянуть на API Geolocation, который требует разрешения. У событий есть координаты.

Я также поиграл с DeviceOrientationEvent (может принять формулу...) и время от времени видел подобные проблемы. Вы можете попробовать калибровку, как в этом видео. Вы можете искать на YouTube больше примеров.

Другие вопросы по тегам