Вычислить заголовок компаса из 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 больше примеров.