我正在使用
react-native-sensors
磁力计开发一个指南针应用程序。我得到了正确的值,并且罗盘工作正常,主要问题是罗盘的快速更新,方向变化过于频繁,变化为+-5度。
我想做一个平滑的定向罗盘。
_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 }) })
发现一个答案听起来类似于SamuelPS的答案,我使用了LPF:用于JavaScript的低通滤波器它只是更加优化和平滑。
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))
}
我会提出两件事。
不要用磁力计的每个输出来更新您的状态。相反,对数据进行某种过滤。 一个简单的例子就是减少采样。假设磁力计为您提供 1000 个样本/秒(我编造的数据)。每秒对视图进行 1000 次更新实在是太多了,而不是创建一个包含 200 个样本的缓冲区,并在每次满时设置这 200 个样本的平均值的状态。在这种情况下,每秒只有 5 次更新,从而大大减少了振动感。在这里使用不同的值进行一些实验,直到找到所需的输出。如果你想要更平滑的东西,重叠缓冲区也可以工作:200 个样本缓冲区,但不必每次缓冲区满时都重置缓冲区,只需删除第一个 100 个样本即可。因此,样本减少了 1/10,但每个输出都是 100 个新样本和 100 个已经影响输出的样本之间的平均值。
第二件事是不要将罗盘指针直接放在磁力计值的位置,否则,看起来像指针在跳动(零平滑)。创建过渡动画以在改变位置时产生平滑的运动。
有了这两件事,应该就可以顺利进行了。 我希望这些信息有用,祝你的指南针好运!!
添加 Abdullah Yahya 的答案,安装并导入 LPF 模块。设置LPF平滑值并检查是否仍然存在波动。
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));
};
请参阅此存储库 - react-native-compass 了解详细信息。
0° 附近的问题是因为,在计算平均值时,我们最终会混合像
[0, 359, 352, 2, 3, ..]
这样的值。为了避免“跳来跳去”的方向,我们没有对 angle
值进行平均。相反,我们计算向量的平均分量。
例如:
import { magnetometer, SensorTypes, setUpdateIntervalForType } from 'react-native-sensors';
const calculateHeading = (x: number, y: number) => {
let angle;
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 angle;
};
const angleToVector = (angle: number) => {
const rad = (angle * Math.PI) / 180;
return {
x: Math.cos(rad),
y: Math.sin(rad),
};
};
const vectorToAngle = (vector: { x: number; y: number }) => {
const rad = Math.atan2(vector.y, vector.x);
const angle = rad * (180 / Math.PI);
return angle >= 0 ? angle : angle + 360;
};
export const useOrientation = (bufferSize = 10) => {
const [buffer, setBuffer] = useState(new Array<number>());
const [angle, setAngle] = useState(0);
useEffect(() => {
setUpdateIntervalForType(SensorTypes.magnetometer, 100);
const subscription = magnetometer.subscribe(({ x, y }) => {
const value = calculateHeading(x, y);
buffer.unshift(value);
while (buffer.length > bufferSize) {
buffer.pop();
}
const vectors = buffer.map(angleToVector);
const avgVector = vectors.reduce(
(acc, vec) => ({
x: acc.x + vec.x,
y: acc.y + vec.y,
}),
{ x: 0, y: 0 },
);
avgVector.x /= buffer.length;
avgVector.y /= buffer.length;
const averageAngle = vectorToAngle(avgVector);
setBuffer([...buffer]);
setAngle(averageAngle);
});
return () => subscription.unsubscribe();
}, [buffer, bufferSize]);
return angle;
};