I’ve begun a project trying to using magnetometers to detect a user’s behaviour, so to begin to understand how to use them I built a simple compass.
Using the PowerSense app for iPhone, I recorded the 3-axis magnetometer readings at various compass headings and device orientations. With the device constrained to the horizontal plane, the following (very promising) reading are obtained:
X & Y magnetometer readings at different headings
Besides from the x-axis being reversed, these points are very usable to give us a compass heading! A conversion to spherical co-ordinates is used to make the output more usable:
From Maths Stack Exchange
As the magnitude of the magnetic field vector in 3D space is constant with changing device orientation, r value can be ignored in this application. In the horizontal plane, the rho value is also constant due to constant z-axis readings, so is also ignored for now. Theta is calculated using:
def direction(vec):#vec = [x,y,z] from imported data
x = -vec\[0\] #negate x
y = vec\[1\]
z = vec\[2\]
if y > 0 and x > 0: #if in Q1
theta = math.atan(x/y)
elif y < 0: #if in Q2 or Q3
theta = math.atan(x/y) + math.pi
else: #if in Q4
theta = math.atan(x/y) + math.pi\*2
return theta
This gives us a heading which is consistent with the angles that the data was recorded at! Now all that is needed is to get this magnetometer data from a mobile device.
Probably should have checked this part first. Oh well.
deviceorientation allows a website to gather data of how a device is being held, which is reported in 3 different axis:
alpha — Rotation around the z axis, from 0 to 360 degrees
beta — Rotation around the x axis, from -180 to 180 degrees
gamma — Rotation around the y axis, from -90 to 90 degrees
The caveat is that this information is specified in a subtly different way between iOS and Android devices. Before chrome 50, Android devices would report alpha as an ‘absolute’ value, constant in the reference frame of the earth’s surface. This means that alpha = 0 corresponds to the device being pointed due north, which is really handy. As seen in the magnetometer data, the rotation direction is reversed. This can be solved by taking heading = 360 - alpha
function deviceOrientationListener(event) {var alpha = event.alpha; //z axis rotation [0,360)var beta = event.beta; //x axis rotation [-180, 180]var gamma = event.gamma; //y axis rotation [-90, 90]var heading = 360 - alpha; //heading [0, 360)}
if(window.DeviceOrientationEvent){ //Check if device is compatiblewindow.addEventListener("deviceorientation", deviceOrientationListener);}
However, now Android has gone the way of iOS, reporting alpha as a ‘relative’ value, where 0 is defined as the device’s direction when the page is loaded. This makes is substantially less useful as a compass…
Luckily, hidden in the depths of google developer pages lies a solution! Some devices will still report a absolute alpha value in the form of a compass heading through event.webkitCompassHeading. The application checks if the device will report this value, and if so uses that. Otherwise, relative alpha values are used to show the page, just not pointing north.
...if (typeof event.webkitCompassHeading !== "undefined") {alpha = event.webkitCompassHeading; //iOS non-standardvar heading = alphadocument.getElementById("heading").innerHTML = heading.toFixed([0]);}else {alert("Your device is reporting relative alpha values, so this compass won't point north! ");var heading = 360 - alpha; //heading [0, 360)document.getElementById("heading").innerHTML = heading.toFixed([0]);}...
if (window.DeviceOrientationAbsoluteEvent) {window.addEventListener("DeviceOrientationAbsoluteEvent", deviceOrientationListener);} // If not, check if the device sends any orientation dataelse if(window.DeviceOrientationEvent){window.addEventListener("deviceorientation", deviceOrientationListener);} // Send an alert if the device isn't compatibleelse {alert("Sorry, try again on a compatible mobile device!");}
All of the code is given at the bottom of the page.
The compass can be tested here, but if your device doesn’t work, have a look below:
The compass in action
<!DOCTYPE html><html><head><style>p {font-family: verdana;font-size: 400px;color: #FFFFFF;}</style>
<title>Compass</title>
<script>
// Get event data
function deviceOrientationListener(event) {
var alpha = event.alpha; //z axis rotation \[0,360)
var beta = event.beta; //x axis rotation \[-180, 180\]
var gamma = event.gamma; //y axis rotation \[-90, 90\]
//Check if absolute values have been sent
if (typeof event.webkitCompassHeading !== "undefined") {
alpha = event.webkitCompassHeading; //iOS non-standard
var heading = alpha
document.getElementById("heading").innerHTML = heading.toFixed(\[0\]);
}
else {
alert("Your device is reporting relative alpha values, so this compass won't point north :(");
var heading = 360 - alpha; //heading \[0, 360)
document.getElementById("heading").innerHTML = heading.toFixed(\[0\]);
}
// Change backgroud colour based on heading
// Green for North and South, black otherwise
if (heading > 359 || heading < 1) { //Allow +- 1 degree
document.body.style.backgroundColor = "green";
document.getElementById("heading").innerHTML = "N"; // North
}
else if (heading > 179 && heading < 181){ //Allow +- 1 degree
document.body.style.backgroundColor = "green";
document.getElementById("heading").innerHTML = "S"; // South
}
else { // Otherwise, use near black
document.body.style.backgroundColor = "#161616";
}
}
// Check if device can provide absolute orientation data
if (window.DeviceOrientationAbsoluteEvent) {
window.addEventListener("DeviceOrientationAbsoluteEvent", deviceOrientationListener);
} // If not, check if the device sends any orientation data
else if(window.DeviceOrientationEvent){
window.addEventListener("deviceorientation", deviceOrientationListener);
} // Send an alert if the device isn't compatible
else {
alert("Sorry, try again on a compatible mobile device!");
}
</script>
</head>
<body><br><br><p id="heading" style="text-align:center"></p></body></html>