4

I'm developing a compass app using react-native-sensors magnetometer. I'm getting the correct values and the compass is working perfectly, the main problem is the fast update of the compass, the direction keeps changing too frequently and the change is +-5 degrees. I want to do a smooth orientation compass.

_angle = (magnetometer) => {
    if (magnetometer) {
      let { x, y, z } = magnetometer

      if (Math.atan2(y, x) >= 0) {
        angle = Math.atan2(y, x) * (180 / Math.PI)
      } else {
        angle = (Math.atan2(y, x) + 2 * Math.PI) * (180 / Math.PI)
      }
    }

    return Math.round(angle)
  }


//Inside ComponentDidMount
magnetometer.subscribe(({ x, y, z, timestamp }) =>
      this.setState({ sensorValue: this._angle({ x, y, z }) })

Vishal G. Gohel
  • 1,008
  • 1
  • 16
  • 31
Abdullah Yahya
  • 119
  • 1
  • 10
  • I have had good luck with taking an average over ~100 samples, which seems to smooth things out and updates reasonably fast. – Anger Density Aug 01 '19 at 12:02
  • @AngerDensity Actually I tried taking the median and just tried taking the average it's definitely smoother but not that great. – Abdullah Yahya Aug 01 '19 at 12:19

3 Answers3

3

Found an Answer sounds similar to SamuelPS's answer, I used LPF: Low Pass Filter for JavaScript it's just more optimized and smooth.

constructor(props) {
    super(props)
    LPF.init([])
  }

_angle = (magnetometer) => {
    if (magnetometer) {
      let { x, y, z } = magnetometer

      if (Math.atan2(y, x) >= 0) {
        angle = Math.atan2(y, x) * (180 / Math.PI)
      } else {
        angle = (Math.atan2(y, x) + 2 * Math.PI) * (180 / Math.PI)
      }
    }

    return Math.round(LPF.next(angle))
  }
Abdullah Yahya
  • 119
  • 1
  • 10
1

I would propose 2 things.

Don't update your state with every output of the magnetometer. Instead, do some kind of filter with the data. A simple example could be to reduce sampling. Let's say that magnetometer is providing you 1000 samples/s(i made up the data). 1000 updates per second to the view is way too much, instead of that create a buffer of 200 samples and set the state of the average value of that 200 samples every-time it is full. In that case, you would have just 5 updates per second, reducing greatly the vibration feeling. Do some experimentation here with different values until you find the desired output. If you want something smoother, an overlapping buffer could work also: 200 samples buffer, but instead of resetting the buffer every-time is full, you just remove the 100 first one. So you have a reduction of 1/10 in samples, but every output is the average between 100 new samples and 100 samples that were already affecting the output.

The second thing is to not set the compass needle directly in the position of the magnetometer value, otherwise, it would look like the needle is jumping(zero smooth). To create a transition animation to produce a smooth movement when changing position.

With these two things, it should work smoothly. I hope this info is useful, good luck with your compass!!

Vishal G. Gohel
  • 1,008
  • 1
  • 16
  • 31
SamuelPS
  • 555
  • 6
  • 20
0

Adding to Abdullah Yahya's answer, install and import LPF module. Set LPF smoothing value and check if fluctuations still exists.

import LPF from "lpf";

constructor() {
  super();
  LPF.init([]);
  LPF.smoothing = 0.2;
}

_angle = magnetometer => {
let angle = 0;
if (magnetometer) {
  let {x, y} = magnetometer;
    if (Math.atan2(y, x) >= 0) {
      angle = Math.atan2(y, x) * (180 / Math.PI);
    } else {
      angle = (Math.atan2(y, x) + 2 * Math.PI) * (180 / Math.PI);
    }
  }
  return Math.round(LPF.next(angle));
};

See this repo - react-native-compass for detail info.

Rahul Haque
  • 146
  • 2
  • 8