Building a compass web app

--

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.

Step 1 — Raw Magnetometer data:

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:

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:

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.

Step 2 — Realise that iPhones don’t give raw magnetometer data to web apps…

Probably should have checked this part first. Oh well.

Step 3 — Discover the wonder of the deviceorientation event

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 compatible      window.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-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]);      }...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!");    }`

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:

`<!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>`

--

--

Quantitive Developer, AI, Computer Vision. Melbourne. www.grantholtes.com