Building a compass web app

Written by grantholtes | Published 2017/12/25
Tech Story Tags: javascript | mobile-app-development | web-apps | sensors | apple

TLDRvia the TL;DR 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:

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.

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


Published by HackerNoon on 2017/12/25