有几种方法可以根据 CMDeviceMotion 返回的旋转矩阵计算航向。假设您使用与 Apple 指南针相同的定义,其中 +y 方向(iPhone 顶部)指向正北返回航向 0,向右旋转 iPhone 会增加航向,因此东为 90,南为 180 ,等等。
首先,当您开始更新时,请务必检查以确保标题可用:
if (([CMMotionManager availableAttitudeReferenceFrames] & CMAttitudeReferenceFrameXTrueNorthZVertical) != 0) {
...
}
接下来,当您启动运动管理器时,请求姿态作为从 X 指向真北的旋转(或磁北,如果您出于某种原因需要):
[motionManager startDeviceMotionUpdatesUsingReferenceFrame: CMAttitudeReferenceFrameXTrueNorthZVertical
toQueue: self.motionQueue
withHandler: dmHandler];
当运动管理器报告运动更新时,您想要了解设备在 X-Y 平面上旋转了多少。由于我们对 iPhone 的顶部感兴趣,因此我们将在该方向上选取一个点,并使用返回的旋转矩阵对其进行旋转,以获得旋转后的点:
[m11 m12 m13] [0] [m12]
[m21 m22 m23] [1] = [m22]
[m31 m32 m33] [0] [m32]
时髦的括号是矩阵;这是我能用 ASCII 做的最好的事情了。 :)
航向是旋转点与真北之间的角度。我们可以使用旋转点的 X 和 Y 坐标来提取反正切,从而给出该点与 X 轴之间的角度。这实际上与我们想要的有180度的偏差,因此我们必须进行相应的调整。结果代码如下所示:
CMDeviceMotionHandler dmHandler = ^(CMDeviceMotion *aMotion, NSError *error) {
// Check for an error.
if (error) {
// Add error handling here.
} else {
// Get the rotation matrix.
CMAttitude *attitude = self.motionManager.deviceMotion.attitude;
CMRotationMatrix rm = attitude.rotationMatrix;
// Get the heading.
double heading = PI + atan2(rm.m22, rm.m12);
heading = heading*180/PI;
printf("Heading: %5.0f\n", heading);
}
};
有一个问题:如果 iPhone 的顶部笔直向上或笔直向下,则方向是不确定的。结果是 m21 和 m22 为零或非常接近零。您需要确定这对您的应用意味着什么,并相应地处理这种情况。例如,当 m12*m12 + m22*m22 接近于零时,您可以切换到基于 -Z 轴(iPhone 后面)的航向。
这一切都假设您想要绕 X-Y 平面旋转,就像苹果公司通常对其指南针所做的那样。它之所以有效,是因为您使用运动管理器返回的旋转矩阵来旋转沿 Y 轴指向的向量,即以下矩阵:
[0]
[1]
[0]
要旋转不同的向量(例如,沿着 -Z 的一个向量),请使用不同的矩阵,例如
[0]
[0]
[-1]
当然,你还必须在不同的平面上取反正切,所以而不是
double heading = PI + atan2(rm.m22, rm.m12);
你会用
double heading = PI + atan2(-rm.m33, -rm.m13);
获得 X-Z 平面上的旋转。