Measuring Differences of Degrees Using CLLocation’s CLHeading
I was recently approached to develop an app for a series of physical and cognitive exercises. Without going to much detail, the project will take various measurements such as gyroscopic movement, accelerometer readings, and microphone detection for breathing. But what this specific exercise was meant to measure was a person’s rotation from a standing position with their fingers interlaced as if they were holding a pistol. Given certain stimuli, a person’s rotation could either increase or decrease, and these stimuli would prove the basis for other factors that could give an athlete a competitive edge.
For such a project, iOS is a well-equipped platform with a wealth of sensors and a sophisticated framework known as CoreLocation. This is the class that harnesses much of the geographic and spatial data collection on OS devices. In addition to GPS and orientation, Core Location for iOS is also capable of gathering data for altitude, barometric pressure, and even utilizes a built in magnetometer used to detect magnetic forces. This last instrument will be helpful for detecting the earth’s magnetic field, but creative minds have also developed other uses for this unique instrument.
For the purposes of this project, collecting the user’s navigational direction is as easy as creating an instance of CLLocationManager and calling one of its delegate functions. Setting up CLLocationManager is fairly straightforward, however an important condition is that Apple recommends you initialize only ONE instance within your application at a time. This is because multiple instances running simultaneously has the effect of causing interference and distorting the data. For this reason if you need to access the CLLocationManager across different ViewControllers, it is common to store it in the AppDelegate or implement another singleton design pattern.
To start tracking the device’s orientation, I call the manager’s
startUpdatingHeading() method and check out the readings in the delegate’s
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading). A print out of
newHeading.trueHeading displays an extremely responsive and accurate navigational direction of my phone. Step one is complete, in only a few lines of code I have a way to measure which direction the user is facing.
The next step proved to be slightly more challenging. Given the parameters, the user should be able to set a starting direction and rotate to the left or right, with the rotation demonstrating their maximum flexibility. The thing is unless every user started the exercise facing 180º, there would need to be some arithmetic involved to translate the data into an accurate display reading…
For example, consider the starting point of the user as 5º. If the user rotates to the left and arrives at 360º, and to the right to 20º, asking the program bluntly for the difference in degrees is would return 355º rather than 15º. Furthermore the display needs to show the maximum degrees for each direction, so the correct response would be 10º to the left and 15º to the right relative to the starting position.
There are undoubtedly many ways to address these problems, with creative minds attacking from various positions of mathematics, navigation, and clever coding devices. My starting approach was a simple one: given the data I had of a user’s starting point and current position, I wished to obtain the magnitude of change as well as the direction relative to that starting position. As such the arguments for my method were:
func findDegreesAndDirection(start: Double, current: Double) -> (difference: Double, isLeft: Bool). (Note that the inputs are Doubles; CLLHeading’s
trueHeading can be easily translated to this primitive).
To find the
difference, I took absolute value of the user’s current heading minus the initial start heading, and truncated the remainder by 360. (This is essentially the same as using the Modulo operator for Doubles). Next if that
difference was greater than 180º, I subtracted it from 360º; otherwise I kept it the same. This was to ensure equal measurement from the starting point regardless of which direction the user rotated.
var difference = abs(current - start).truncatingRemainder(dividingBy: 360)
difference = difference > 180 ? (360 - difference) : difference
Finally I arrived at the tricky logic of determining the direction of the movement. I won’t pretend that this came through careful deduction but rather several permutations and a few “a-Ha!” moments. I made the assumption that we would not have many contortionists using the app, so having a user who could stand still and rotate only their upper body 180º was not a possibility; this made things much easier.
I also made a Boolean variable
isLeft with a default value of true, so all I had to do was state all scenarios in which a user could turn right. The logic that follows is determining which half of the compass a user is facing, what would be 180º behind them, and which half the difference falls on. Complications arose on the edge cases, for instance if a user was facing 280º and rotated 90º to the right, their current direction would be 10º and should display a 90º difference. After testing with numerous scenarios I arrived at this solution:
For a while this exercise spun me right round (baby), right round. Like a record (baby) right round, round round. But in the end I was grateful for the arithmetic workout; as coders we should be open to challenges from any field and welcoming of those that help to break the monotony of our comfort zones. I hope this example may help other developers who are exploring the possibilities of CLLocation and phone orientation, and hopefully will also save them time in formatting the relative distances of CLHeadings.
Thanks for reading, keep your head on a swivel and happy coding!