Proper way to get compass heading

Include the following details (edit as applicable):

  • Issue category: ARDK Documentation / VPS
  • Device type & OS version: Android
  • Host machine & OS version: Windows
  • Issue Environment : On Device
  • ARDK version: 2.0.0
  • Unity version: 2021.1.3f1

Description of the issue:

Is tracking, with or without VPS, ever intended to take compass heading into account?

I initially assumed it would, but world Z+ seems to stay in roughly the direction the camera was facing when the app opened, rather than any real-world consistent direction.

My next idea, after reading some tangential references on a thread about deriving XYZ offsets from lat/lon (another part of my demo, but moot if directions are inconsistent), was to explore the Compass API, which I have to assume is wrapped into LocationService. However, a handler attached to LocationService.CompassUpdated seems to never get called, so I don’t seem to be able to get TrueHeading from that path.

If there’s a way to get to the underlying Compass API from whatever LocationService uses under the hood, or raw from the Unity side, neither the ARDK docs nor the Unity docs are particularly clear how to correctly get a Compass instance.

Once I can figure out how to establish a true-enough north relative to camera facing, it looks like the LatLng class has all the right kinds of helper functions for getting distances between geolocations, from which to place objects in the scene, but until I know the device’s real-world heading, I’m kinda stuck.

This is largely copypasta from various samples, and I can confirm OnLocationUpdate gets called. No evidence that OnCompassUpdate is hit, however.

//callback for the session starting.
private void OnSessionInitialized(AnyARSessionInitializedArgs args)
{
  //only run once guard
  ARSessionFactory.SessionInitialized -= OnSessionInitialized;

  //save the session.
  _session = args.Session;

  _locationService = LocationServiceFactory.Create(_session.RuntimeEnvironment);
  // For LocationService, we recommend using a desiredAccuracyInMeters of 1 and an updateDistanceInMeters of 0.001 for best results
  _locationService.Start(1, 0.001f);

  _locationService.LocationUpdated += OnLocationUpdate;
  _locationService.CompassUpdated += OnCompassUpdate;
}
private void OnCompassUpdate(CompassUpdatedArgs args)
{
  _trueHeadingDeg = args.TrueHeading;
  _antiHeadingQuat = Quaternion.AngleAxis(-_trueHeadingDeg, Vector3.up);
  _compassNorth = _antiHeadingQuat * Vector3.forward;
  _compassEast = _antiHeadingQuat * Vector3.right;
  Debug.Log("Compass Updated");
}

Hello Dev,

I have included a sample script which accesses the compass tool in Unity. Hopefully this will give you a jumping off point in integrating a compass into your build. I am also including a link to the compass feature on the unity docs site. This could also provide further insight in how to resolve your issue. Please let us know if you need further assistance.

PointNorth.cs (788 Bytes)

Thanks. That’s definitely a start to getting at the API. However, even the native compass API seems to return 0 constantly… excepting exactly once when, several minutes into the run, it suddenly began returning data that seemed correct and consistent.

Is compass functionality conditional upon anything other than starting the location service as the docs mention? Is the location service contingent upon anything other than coarse/fine location permissions? Is there any race/interplay between the native unity location service and the ARDK location service that might be causing the one to block the other? I am successfully getting lat/long from ARDK, so at least something under the hood is functional, permissions granted, API licensed, etc.

1 Like

I do kinda suspect I’m up against a race condition of some sort here. If compass doesn’t start working on app launch (which it has now done exactly once), it usually picks up if I sleep the device, then wake it back up and unlock the screen to get back to the app. Can’t propose that as a fix for a client-facing production app, though, notwithstanding the fact that backgrounding the app completely tends to make the camera feed drop to black as often as not.

Update for future seekers: when the Input.compass API is returning data, the ARDK LocationService.CompassUpdated event is being invoked with effectively the same data, so the APIs are redundant to use in tandem. But I still have no idea why, most of the time, they simply refuse to yield data until the device is bounced.

Also of note, anyone looking to use compass data to do alignment to the outside world, the compass API returns the angle between true north and the direction the top of the screen is pointing, not the direction the camera is pointing. So for any relevant compass reading, you’ll need to project your camera transform.up down onto the XZ groundplane and angle your world off of that rather than transform.forward (which will be more or less the same even if the device is tilted). For accurate compass reading… you’re basically out of luck. AR needs the device held vertically, which is the worst case for compass, compass needs the device held horizontally, which is worst-case / useless for AR.

Hi @NNDev, adding some info here that may be related to your post:

By default, the orientation of the ARDK scene is relative to the starting location of the device. The origin of the scene is wherever the phone is located upon initialization of the AR Session, and ARDK recognizes Y as up-positive/down-negative (gravity). However, the other axes act as such: Z as forward-positive/backward-negative from the camera lens, and X as left-negative/right-positive based on where the AR Session begins.

As a note, the Unity Compass class contains distinct headings for geographic north and magnetic north.

In regard to the camera going to a black screen on background, it’s possible that this is happening due to a loss of tracking while the app was in the background.

As far as the possible race condition that you mentioned goes, we’re looking into why this may be happening.

Hello @NNDev,

In order to get non-zero information out of Unity’s compass class, you need to ensure that the compass is enabled – Input.compass.enabled = true; – prior to starting Unity’s location service – Input.location.Start();.

There shouldn’t be any strange interplay between ARDK’s and Unity’s location services, specifically since Unity’s is read-only. If you want to rule out any potential race conditions, you can ensure that the script that’s accessing Unity’s compass features gets called first by going to Edit > Project Settings > Script Execution Order and adding it to the list with the + button and moving it above Default Time. There’s more information on altering script execution order in the Unity docs here.

As a side note, you may want to confirm your device’s compass functionality first with a published compass utility app to ensure the magnetometer in the device is working as expected. For example – in the process of troubleshooting this, I went through two test devices that appeared to be giving incorrect headings when testing my Unity build, and when I opened Apple’s Compass app I could see that one device was indeed reporting a heading flipped by 180° and the other was off by about 60°… so I seem to have encountered device-specific issues that perhaps you’re also experiencing. If you’re still getting strange readings, then it’s also possible that there may be something nearby that’s disrupting the local magnetic field of the Earth, so that’s something else to consider while testing.

Thanks. This is exactly what I’m doing. On an object enabled at scene launch, Start is enabling compass, starting the native location service, and setting up an ARDK session listener.

// Start is called before the first frame update
void Start()
{
	ARSessionFactory.SessionInitialized += OnSessionInitialized;

	Input.compass.enabled = true;
	Input.location.Start();
}

and then that session listener is setting up my data listeners

//callback for the session starting.
private void OnSessionInitialized(AnyARSessionInitializedArgs args)
{
	//only run once guard
	ARSessionFactory.SessionInitialized -= OnSessionInitialized;

	//save the session.
	_session = args.Session;

	_locationService = LocationServiceFactory.Create(_session.RuntimeEnvironment);
	// For LocationService, we recommend using a desiredAccuracyInMeters of 1 and an updateDistanceInMeters of 0.001 for best results
	_locationService.Start(1, 0.001f);

	_locationService.LocationUpdated += OnLocationUpdate;
	_locationService.CompassUpdated += OnCompassUpdate;
}

It’s all pretty much verbatim documentation sample code. But at runtime on target, whether I’m watching values coming in (or not) from the CompassUpdated handler or simply polling Input.compass.trueHeading during Update, I’m routinely seeing long/indefinite periods of no data whatsoever at app launch, usually until, as mentioned, I sleep the device and wake it back up again, at which point both APIs immediately start returning data, and the data seems sound enough. On the rare occasions when I see compass data immediately at launch, it’s usually right after the app has crashed and I’ve immediately relaunched it.

So unless e.g. the ARDK session becomes fully initialized (with compass disabled) before I attach the session listener, and the listener is run immediately inline upon attachment before execution proceeds to enabling the compass, I don’t see how I shouldn’t be seeing the data through ARDK, and either way, if there’s no interaction between the two systems, I can’t figure out why the Unity native API would be returning 0 data when initialized as such. And even if either situation was happening at launch, I’m not sure why sleeping the device would fix it, unless perhaps all ARDK services shut down on device sleep and are re-initialized (with ambient settings, including possibly the previously enabled compass) at wake.

If that IS the race- ARDK session beats the compass script to the punch and initializes Unity’s location service with compass disabled in such a way that it can’t be subsequently enabled and reinitialized- that ought to go away if I do either set my own script(s) to priority (which will get progressively more awkward in progressively more realistically large projects) or… set ARSessionManager to lower-than-default priority? If that’s safe, it would likely be preferable. If I can pin down a fix via priority shuffling, this may turn into a feature request that a compass use flag be hoisted into ARDK’s session logic / manager so it can be sure the underlying Unity API is set up the way the developer expects/requires from the outset.

Update: neither moving the compass init lines above the session handler attachment, nor moving the entire script to earlier-than-default priority appear to be affecting the situation at all. Compass native API still returns 0 and ARDK callback is not invoked until the device is slept/awakened mid-run.

Hello NNDev,

After investigating the issue further, we may have discovered an issue with ARDK’s _UnityLocationService class that’s located within the _UnityLocationService.cs script file.

As of ARDK 2.0.0, the script shows lines 46 & 47 as follows…

Input.location.Start(desiredAccuracyInMeters, updateDistanceInMeters);
Input.compass.enabled = true;

Would you mind flipping the statement order here in your local copy of the _UnityLocationService.cs script so it appears as follows…

Input.compass.enabled = true;
Input.location.Start(desiredAccuracyInMeters, updateDistanceInMeters);

…saving the file, and testing the results? I found some indication through the Unity forums that the compass may need to be enabled prior to initialization of Unity’s location service.

The suspicion is that the Unity service may be kicking off without the compass enabled – which would account for your device reporting zeroes – and then perhaps Unity re-initializes its location service (with the compass flag having been enabled) when the device is awoken. It’s hard to tell for certain, since Unity can be a bit of a black box system at times.

If this resolves the issue, I’ll file a report to let the ARDK team know.

It’s not 1000% conclusive, but on the one device that’s been having problems with compass (this issue did, ultimately, not seem to occur on other devices after sorting out some UI refresh issues), 5 launches out of 5 in a test just now did have compass on startup after that fix. So based on what I’ve read on how the compass API works, I’d definitely say those lines should be switched. Reading back, this was actually one of my leading hypotheses about the interplay- ARDK initializing the service without compass before I could initialize it with, although it doesn’t explain why shifting my initialization ahead of default didn’t fix it earlier (unless _UnityLocationService is itself being prioritized even further ahead of default…)

Thank you for confirming that swapping the statement order resolved the issue on your end, Dev. I’ve gone ahead and filed a report, so this issue should be addressed in a future release.

I had a similar problem!
As I read this post, I decided to ONLY use Unities intern Location Service (as I will not be working with VPS in the future), but I still had to swap these two lines of code in _UnityLocationServices.cs and in my own script (where I finally used the ‘Input.compass.trueHeading’).

I just started working with Unity so I was very confused why the Unity intern Location Service got overwritten by Niantics Location Service, even though I didnt include it in my header. Maybe someone who knows more about this can enlighten me.

But thanks a lot for this post, it was very helpful!!

Hi @Josef_Waschlab,

The fix for this compass-related issue should be included in the most recent release of ARDK, version 2.1.0.

Can you try updating your project to the latest version of ARDK – found on the Downloads page here – and posting the results?