use crate::coordinate_systems::Ecef;
use crate::coordinates::Coordinate;
use crate::float_math::FloatMath;
use crate::geodetic::Wgs84;
use crate::systems::EquivalentTo;
use crate::vectors::{LengthBasedComponents, Vector};
use crate::{
Bearing, CoordinateSystem, Isometry3, UnitQuaternion,
systems::{EnuLike, NedLike},
};
use core::convert::From;
use core::fmt;
use core::fmt::{Display, Formatter};
use core::marker::PhantomData;
use core::ops::{Mul, Neg};
use nalgebra::{Matrix3, Rotation3, Translation3};
use typenum::Integer;
use uom::si::angle::radian;
use uom::si::f64::Angle;
#[cfg(any(test, feature = "approx"))]
use approx::{AbsDiffEq, RelativeEq};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(doc)]
use crate::{engineering, systems::FrdLike};
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(bound = ""))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct Rotation<From, To> {
pub(crate) inner: UnitQuaternion,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) from: PhantomData<From>,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) to: PhantomData<To>,
}
impl<From, To> Clone for Rotation<From, To> {
fn clone(&self) -> Self {
*self
}
}
impl<From, To> Copy for Rotation<From, To> {}
impl<From, To> PartialEq<Self> for Rotation<From, To> {
fn eq(&self, other: &Self) -> bool {
self.inner.eq(&other.inner)
}
}
impl<From, To> Display for Rotation<From, To> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Quaternion: {}", self.inner)
}
}
impl<To> Rotation<Ecef, To>
where
To: CoordinateSystem<Convention = NedLike>,
{
unsafe fn ecef_to_ned_at(latitude: impl Into<Angle>, longitude: impl Into<Angle>) -> Self {
let phi = latitude.into().get::<radian>();
let lambda = longitude.into().get::<radian>();
let sin_phi = FloatMath::sin(phi);
let cos_phi = FloatMath::cos(phi);
let sin_lambda = FloatMath::sin(lambda);
let cos_lambda = FloatMath::cos(lambda);
let matrix = Matrix3::new(
-cos_lambda * sin_phi,
-sin_lambda,
-cos_lambda * cos_phi,
-sin_lambda * sin_phi,
cos_lambda,
-sin_lambda * cos_phi,
cos_phi,
0.,
-sin_phi,
);
let rot = Rotation3::from_matrix(&matrix);
Self {
inner: UnitQuaternion::from_rotation_matrix(&rot),
from: PhantomData,
to: PhantomData,
}
}
}
impl<To> Rotation<Ecef, To>
where
To: CoordinateSystem<Convention = EnuLike>,
{
unsafe fn ecef_to_enu_at(latitude: impl Into<Angle>, longitude: impl Into<Angle>) -> Self {
let phi = latitude.into().get::<radian>();
let lambda = longitude.into().get::<radian>();
let sin_phi = FloatMath::sin(phi);
let cos_phi = FloatMath::cos(phi);
let sin_lambda = FloatMath::sin(lambda);
let cos_lambda = FloatMath::cos(lambda);
let matrix = Matrix3::new(
-sin_lambda,
-cos_lambda * sin_phi,
cos_lambda * cos_phi,
cos_lambda,
-sin_lambda * sin_phi,
sin_lambda * cos_phi,
0.,
cos_phi,
sin_phi,
);
let rot = Rotation3::from_matrix(&matrix);
Self {
inner: UnitQuaternion::from_rotation_matrix(&rot),
from: PhantomData,
to: PhantomData,
}
}
}
fn swap_x_y_negate_z_quaternion() -> UnitQuaternion {
use nalgebra::{Matrix3, Rotation3, UnitQuaternion};
const SWAP_X_Y_NEGATE_Z: [f64; 9] = [
0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, ];
let m = Matrix3::from_row_slice(&SWAP_X_Y_NEGATE_Z);
UnitQuaternion::from_rotation_matrix(&Rotation3::from_matrix(&m))
}
impl<From, To> Rotation<From, To>
where
To: CoordinateSystem<Convention = EnuLike>,
{
#[must_use]
pub unsafe fn into_ned_equivalent<NedTo>(self) -> Rotation<From, NedTo>
where
NedTo: CoordinateSystem<Convention = NedLike>,
{
Rotation {
inner: self.inner * swap_x_y_negate_z_quaternion(),
from: PhantomData::<From>,
to: PhantomData::<NedTo>,
}
}
}
impl<From, To> Rotation<From, To>
where
To: CoordinateSystem<Convention = NedLike>,
{
#[must_use]
pub unsafe fn into_enu_equivalent<EnuTo>(self) -> Rotation<From, EnuTo>
where
EnuTo: CoordinateSystem<Convention = EnuLike>,
{
Rotation {
inner: self.inner * swap_x_y_negate_z_quaternion(),
from: PhantomData::<From>,
to: PhantomData::<EnuTo>,
}
}
}
impl<From, To> Rotation<From, To> {
#[doc(alias = "from_nautical_angles")]
#[doc(alias = "from_cardan_angles")]
#[doc(alias = "from_ypr")]
#[deprecated = "Prefer `tait_bryan_builder` to avoid argument-order confusion"]
pub unsafe fn from_tait_bryan_angles(
yaw: impl Into<Angle>,
pitch: impl Into<Angle>,
roll: impl Into<Angle>,
) -> Self {
Self {
inner: UnitQuaternion::from_euler_angles(
roll.into().get::<radian>(),
pitch.into().get::<radian>(),
yaw.into().get::<radian>(),
),
from: PhantomData::<From>,
to: PhantomData::<To>,
}
}
#[must_use]
pub unsafe fn identity() -> Self {
Self {
inner: UnitQuaternion::identity(),
from: PhantomData::<From>,
to: PhantomData::<To>,
}
}
#[must_use]
pub unsafe fn is_also_into<NewTo>(self) -> Rotation<From, NewTo> {
Rotation {
inner: self.inner,
from: self.from,
to: PhantomData::<NewTo>,
}
}
#[must_use]
pub unsafe fn is_also_from<NewFrom>(self) -> Rotation<NewFrom, To> {
Rotation {
inner: self.inner,
from: PhantomData::<NewFrom>,
to: self.to,
}
}
#[must_use]
pub fn cast_type_of_from<AlsoFrom>(self) -> Rotation<AlsoFrom, To>
where
From: EquivalentTo<AlsoFrom>,
{
Rotation {
inner: self.inner,
from: PhantomData::<AlsoFrom>,
to: self.to,
}
}
#[must_use]
pub fn cast_type_of_to<AlsoTo>(self) -> Rotation<From, AlsoTo>
where
To: EquivalentTo<AlsoTo>,
{
Rotation {
inner: self.inner,
from: self.from,
to: PhantomData::<AlsoTo>,
}
}
#[must_use]
pub fn inverse(&self) -> Rotation<To, From> {
Rotation {
inner: self.inner.inverse(),
from: PhantomData::<To>,
to: PhantomData::<From>,
}
}
#[must_use]
pub fn nlerp(&self, rhs: &Self, t: f64) -> Self {
Self {
inner: self.inner.nlerp(&rhs.inner, t),
from: self.from,
to: self.to,
}
}
#[must_use]
pub fn to_tait_bryan_angles(&self) -> (Angle, Angle, Angle) {
let (roll, pitch, yaw) = self.inner.euler_angles();
(
Angle::new::<radian>(yaw),
Angle::new::<radian>(pitch),
Angle::new::<radian>(roll),
)
}
#[doc(alias = "from_versor")]
#[must_use]
pub unsafe fn from_quaternion(w: f64, i: f64, j: f64, k: f64) -> Self {
debug_assert_ne!(
[w, i, j, k],
[0.0, 0.0, 0.0, 0.0],
"Quaternion must be non-zero"
);
let from_to = UnitQuaternion::new_normalize(nalgebra::Quaternion::new(w, i, j, k));
Self {
inner: from_to.inverse(),
from: PhantomData::<From>,
to: PhantomData::<To>,
}
}
#[doc(alias = "to_versor")]
#[must_use]
pub fn to_quaternion(&self) -> (f64, f64, f64, f64) {
let from_to = self.inner.inverse();
let q = from_to.quaternion();
(q.w, q.i, q.j, q.k)
}
#[must_use]
pub fn euler_angles(&self) -> (Angle, Angle, Angle) {
let (roll, pitch, yaw) = self.inner.euler_angles();
(
Angle::new::<radian>(yaw),
Angle::new::<radian>(pitch),
Angle::new::<radian>(roll),
)
}
pub fn tait_bryan_builder()
-> tait_bryan_builder::TaitBryanBuilder<tait_bryan_builder::NeedsYaw, Rotation<From, To>> {
tait_bryan_builder::TaitBryanBuilder::new()
}
}
impl<From, To> Rotation<From, To> {
pub fn and_then<Transform>(self, rhs: Transform) -> <Self as Mul<Transform>>::Output
where
Self: Mul<Transform>,
{
self * rhs
}
#[doc(alias = "apply")]
pub fn transform<T>(&self, in_from: T) -> <T as Mul<Self>>::Output
where
T: Mul<Self>,
{
in_from * *self
}
#[doc(alias = "undo")]
pub fn inverse_transform<T>(&self, in_to: T) -> <Self as Mul<T>>::Output
where
Self: Mul<T>,
{
*self * in_to
}
}
#[cfg(any(test, feature = "approx"))]
impl<From, To> AbsDiffEq<Self> for Rotation<From, To> {
type Epsilon = <f64 as AbsDiffEq>::Epsilon;
fn default_epsilon() -> Self::Epsilon {
UnitQuaternion::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.inner.abs_diff_eq(&other.inner, epsilon)
}
}
#[cfg(any(test, feature = "approx"))]
impl<From, To> RelativeEq for Rotation<From, To> {
fn default_max_relative() -> Self::Epsilon {
UnitQuaternion::default_max_relative()
}
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
self.inner.relative_eq(&other.inner, epsilon, max_relative)
}
}
impl<From, To> Neg for Rotation<From, To> {
type Output = Rotation<To, From>;
fn neg(self) -> Self::Output {
self.inverse()
}
}
impl<From, Over, To> Mul<Rotation<Over, To>> for Rotation<From, Over> {
type Output = Rotation<From, To>;
fn mul(self, rhs: Rotation<Over, To>) -> Self::Output {
Self::Output {
inner: self.inner * rhs.inner,
from: self.from,
to: rhs.to,
}
}
}
impl<From, To> Mul<Bearing<To>> for Rotation<From, To>
where
From: crate::systems::BearingDefined,
To: crate::systems::BearingDefined,
{
type Output = Bearing<From>;
fn mul(self, rhs: Bearing<To>) -> Self::Output {
(self * rhs.to_unit_vector())
.bearing_at_origin()
.expect("magnitude should still be 1 after rotation")
}
}
impl<From, To> Mul<Rotation<From, To>> for Bearing<From>
where
From: crate::systems::BearingDefined,
To: crate::systems::BearingDefined,
{
type Output = Bearing<To>;
fn mul(self, rhs: Rotation<From, To>) -> Self::Output {
(self.to_unit_vector() * rhs)
.bearing_at_origin()
.expect("magnitude should still be 1 after rotation")
}
}
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(bound = ""))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct RigidBodyTransform<From, To> {
pub(crate) inner: Isometry3,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) from: PhantomData<From>,
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) to: PhantomData<To>,
}
impl<From, To> Clone for RigidBodyTransform<From, To> {
fn clone(&self) -> Self {
*self
}
}
impl<From, To> Copy for RigidBodyTransform<From, To> {}
impl<To> RigidBodyTransform<Ecef, To>
where
To: CoordinateSystem<Convention = NedLike>,
{
#[must_use]
pub unsafe fn ecef_to_ned_at(position: &Wgs84) -> Self {
let ecef = Coordinate::<Ecef>::from_wgs84(position);
let translation = Vector::from(ecef);
let rotation = unsafe { Rotation::ecef_to_ned_at(position.latitude, position.longitude) };
unsafe { Self::new(translation, rotation) }
}
}
impl<To> RigidBodyTransform<Ecef, To>
where
To: CoordinateSystem<Convention = EnuLike>,
{
#[must_use]
pub unsafe fn ecef_to_enu_at(position: &Wgs84) -> Self {
let ecef = Coordinate::<Ecef>::from_wgs84(position);
let translation = Vector::from(ecef);
let rotation = unsafe { Rotation::ecef_to_enu_at(position.latitude, position.longitude) };
unsafe { Self::new(translation, rotation) }
}
}
impl<From, To> RigidBodyTransform<From, To> {
#[must_use]
pub unsafe fn new(translation: Vector<From>, rotation: Rotation<From, To>) -> Self {
Self {
inner: Isometry3::from_parts(Translation3::from(translation.inner), rotation.inner),
from: PhantomData::<From>,
to: PhantomData::<To>,
}
}
pub fn and_then<NewTo, Transform>(self, rhs: Transform) -> RigidBodyTransform<From, NewTo>
where
Self: Mul<Transform, Output = RigidBodyTransform<From, NewTo>>,
{
self * rhs
}
#[must_use]
pub unsafe fn identity() -> Self {
unsafe { Self::new(Vector::zero(), Rotation::identity()) }
}
#[must_use]
pub fn cast_type_of_from<AlsoFrom>(self) -> RigidBodyTransform<AlsoFrom, To>
where
From: EquivalentTo<AlsoFrom>,
{
RigidBodyTransform {
inner: self.inner,
from: PhantomData::<AlsoFrom>,
to: self.to,
}
}
#[must_use]
pub fn cast_type_of_to<AlsoTo>(self) -> RigidBodyTransform<From, AlsoTo>
where
To: EquivalentTo<AlsoTo>,
{
RigidBodyTransform {
inner: self.inner,
from: self.from,
to: PhantomData::<AlsoTo>,
}
}
#[must_use]
pub fn inverse(&self) -> RigidBodyTransform<To, From> {
RigidBodyTransform::<To, From> {
inner: self.inner.inverse(),
from: PhantomData,
to: PhantomData,
}
}
#[must_use]
pub fn lerp(&self, rhs: &Self, t: f64) -> Self {
unsafe {
Self::new(
self.translation().lerp(&rhs.translation(), t),
self.rotation().nlerp(&rhs.rotation(), t),
)
}
}
#[must_use]
pub fn translation(&self) -> Vector<From> {
Vector::from_nalgebra_vector(self.inner.translation.vector)
}
#[must_use]
pub fn rotation(&self) -> Rotation<From, To> {
Rotation {
inner: self.inner.rotation,
from: self.from,
to: self.to,
}
}
}
impl<From, To> RigidBodyTransform<From, To> {
#[doc(alias = "apply")]
pub fn transform<T>(&self, in_from: T) -> <T as Mul<Self>>::Output
where
T: Mul<Self>,
{
in_from * *self
}
#[doc(alias = "undo")]
pub fn inverse_transform<T>(&self, in_to: T) -> <Self as Mul<T>>::Output
where
Self: Mul<T>,
{
*self * in_to
}
}
impl<From, To> PartialEq<Self> for RigidBodyTransform<From, To> {
fn eq(&self, other: &Self) -> bool {
self.inner.eq(&other.inner)
}
}
impl<From, To> Display for RigidBodyTransform<From, To> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"Position: {}, Orientation: {}",
self.translation(),
self.rotation()
)
}
}
impl<From, To> Neg for RigidBodyTransform<From, To> {
type Output = RigidBodyTransform<To, From>;
fn neg(self) -> Self::Output {
self.inverse()
}
}
impl<From, To> Mul<Rotation<From, To>> for Coordinate<From> {
type Output = Coordinate<To>;
fn mul(self, rhs: Rotation<From, To>) -> Self::Output {
Coordinate::from_nalgebra_point(rhs.inner.inverse_transform_point(&self.point))
}
}
impl<From, To> Mul<Coordinate<To>> for Rotation<From, To> {
type Output = Coordinate<From>;
fn mul(self, rhs: Coordinate<To>) -> Self::Output {
Coordinate::from_nalgebra_point(self.inner.transform_point(&rhs.point))
}
}
impl<From, To, Time: Integer> Mul<Rotation<From, To>> for Vector<From, Time>
where
Vector<To, Time>: LengthBasedComponents<To, Time>,
{
type Output = Vector<To, Time>;
fn mul(self, rhs: Rotation<From, To>) -> Self::Output {
Vector::from_nalgebra_vector(rhs.inner.inverse_transform_vector(&self.inner))
}
}
impl<From, To, Time: Integer> Mul<Vector<To, Time>> for Rotation<From, To>
where
Vector<From, Time>: LengthBasedComponents<From, Time>,
{
type Output = Vector<From, Time>;
fn mul(self, rhs: Vector<To, Time>) -> Self::Output {
Vector::from_nalgebra_vector(self.inner.transform_vector(&rhs.inner))
}
}
impl<From, To> Mul<RigidBodyTransform<From, To>> for Coordinate<From> {
type Output = Coordinate<To>;
fn mul(self, rhs: RigidBodyTransform<From, To>) -> Self::Output {
Coordinate::from_nalgebra_point(rhs.inner.inverse_transform_point(&self.point))
}
}
impl<From, To> Mul<Coordinate<To>> for RigidBodyTransform<From, To> {
type Output = Coordinate<From>;
fn mul(self, rhs: Coordinate<To>) -> Self::Output {
Coordinate::from_nalgebra_point(self.inner.transform_point(&rhs.point))
}
}
impl<From, To, Time: Integer> Mul<RigidBodyTransform<From, To>> for Vector<From, Time>
where
Vector<To, Time>: LengthBasedComponents<To, Time>,
{
type Output = Vector<To, Time>;
fn mul(self, rhs: RigidBodyTransform<From, To>) -> Self::Output {
Vector::from_nalgebra_vector(rhs.inner.inverse_transform_vector(&self.inner))
}
}
impl<From, To, Time: Integer> Mul<Vector<To, Time>> for RigidBodyTransform<From, To>
where
Vector<From, Time>: LengthBasedComponents<From, Time>,
{
type Output = Vector<From, Time>;
fn mul(self, rhs: Vector<To, Time>) -> Self::Output {
Vector::from_nalgebra_vector(self.inner.transform_vector(&rhs.inner))
}
}
impl<From, To> Mul<Bearing<To>> for RigidBodyTransform<From, To>
where
From: crate::systems::BearingDefined,
To: crate::systems::BearingDefined,
{
type Output = Bearing<From>;
fn mul(self, rhs: Bearing<To>) -> Self::Output {
(self * rhs.to_unit_vector())
.bearing_at_origin()
.expect("magnitude should still be 1 after rotation")
}
}
impl<From, To> Mul<RigidBodyTransform<From, To>> for Bearing<From>
where
From: crate::systems::BearingDefined,
To: crate::systems::BearingDefined,
{
type Output = Bearing<To>;
fn mul(self, rhs: RigidBodyTransform<From, To>) -> Self::Output {
(self.to_unit_vector() * rhs)
.bearing_at_origin()
.expect("magnitude should still be 1 after rotation")
}
}
impl<From, Over, To> Mul<RigidBodyTransform<Over, To>> for RigidBodyTransform<From, Over> {
type Output = RigidBodyTransform<From, To>;
fn mul(self, rhs: RigidBodyTransform<Over, To>) -> Self::Output {
Self::Output {
inner: self.inner * rhs.inner,
from: PhantomData::<From>,
to: PhantomData::<To>,
}
}
}
impl<From, Over, To> Mul<Rotation<Over, To>> for RigidBodyTransform<From, Over> {
type Output = RigidBodyTransform<From, To>;
fn mul(self, rhs: Rotation<Over, To>) -> Self::Output {
Self::Output {
inner: self.inner * rhs.inner,
from: PhantomData::<From>,
to: PhantomData::<To>,
}
}
}
impl<From, Over, To> Mul<RigidBodyTransform<Over, To>> for Rotation<From, Over> {
type Output = RigidBodyTransform<From, To>;
fn mul(self, rhs: RigidBodyTransform<Over, To>) -> Self::Output {
Self::Output {
inner: self.inner * rhs.inner,
from: PhantomData::<From>,
to: PhantomData::<To>,
}
}
}
#[cfg(any(test, feature = "approx"))]
impl<From, To> AbsDiffEq<Self> for RigidBodyTransform<From, To> {
type Epsilon = <f64 as AbsDiffEq>::Epsilon;
fn default_epsilon() -> Self::Epsilon {
Isometry3::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
self.inner.abs_diff_eq(&other.inner, epsilon)
}
}
#[cfg(any(test, feature = "approx"))]
impl<From, To> RelativeEq for RigidBodyTransform<From, To> {
fn default_max_relative() -> Self::Epsilon {
Isometry3::default_max_relative()
}
fn relative_eq(
&self,
other: &Self,
epsilon: Self::Epsilon,
max_relative: Self::Epsilon,
) -> bool {
self.inner.relative_eq(&other.inner, epsilon, max_relative)
}
}
pub mod tait_bryan_builder {
use super::*;
use crate::engineering::Orientation;
use core::marker::PhantomData;
use uom::ConstZero;
use uom::si::f64::Angle;
pub struct NeedsYaw;
pub struct NeedsPitch;
pub struct NeedsRoll;
pub struct Complete;
pub struct TaitBryanBuilder<State, Target> {
yaw: Angle,
pitch: Angle,
roll: Angle,
_state: PhantomData<State>,
_target: PhantomData<fn() -> Target>,
}
impl<State, Target> Clone for TaitBryanBuilder<State, Target> {
fn clone(&self) -> Self {
*self
}
}
impl<State, Target> Copy for TaitBryanBuilder<State, Target> {}
impl<Target> core::fmt::Debug for TaitBryanBuilder<NeedsYaw, Target> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TaitBryanBuilder<NeedsYaw>").finish()
}
}
impl<Target> core::fmt::Debug for TaitBryanBuilder<NeedsPitch, Target> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TaitBryanBuilder<NeedsPitch>")
.field("yaw", &self.yaw)
.finish()
}
}
impl<Target> core::fmt::Debug for TaitBryanBuilder<NeedsRoll, Target> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TaitBryanBuilder<NeedsRoll>")
.field("yaw", &self.yaw)
.field("pitch", &self.pitch)
.finish()
}
}
impl<Target> core::fmt::Debug for TaitBryanBuilder<Complete, Target> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TaitBryanBuilder<Complete>")
.field("yaw", &self.yaw)
.field("pitch", &self.pitch)
.field("roll", &self.roll)
.finish()
}
}
impl<Target> TaitBryanBuilder<NeedsYaw, Target> {
pub(crate) fn new() -> Self {
Self {
yaw: Angle::ZERO,
pitch: Angle::ZERO,
roll: Angle::ZERO,
_state: PhantomData,
_target: PhantomData,
}
}
pub fn yaw(mut self, angle: impl Into<Angle>) -> TaitBryanBuilder<NeedsPitch, Target> {
self.yaw = angle.into();
TaitBryanBuilder {
yaw: self.yaw,
pitch: self.pitch,
roll: self.roll,
_state: PhantomData,
_target: PhantomData,
}
}
}
impl<Target> TaitBryanBuilder<NeedsPitch, Target> {
pub fn pitch(mut self, angle: impl Into<Angle>) -> TaitBryanBuilder<NeedsRoll, Target> {
self.pitch = angle.into();
TaitBryanBuilder {
yaw: self.yaw,
pitch: self.pitch,
roll: self.roll,
_state: PhantomData,
_target: PhantomData,
}
}
}
impl<Target> TaitBryanBuilder<NeedsRoll, Target> {
pub fn roll(mut self, angle: impl Into<Angle>) -> TaitBryanBuilder<Complete, Target> {
self.roll = angle.into();
TaitBryanBuilder {
yaw: self.yaw,
pitch: self.pitch,
roll: self.roll,
_state: PhantomData,
_target: PhantomData,
}
}
}
impl<In> TaitBryanBuilder<Complete, Orientation<In>> {
pub fn build(self) -> Orientation<In> {
#[allow(deprecated)]
Orientation::from_tait_bryan_angles(self.yaw, self.pitch, self.roll)
}
}
impl<From, To> TaitBryanBuilder<Complete, Rotation<From, To>> {
pub unsafe fn build(self) -> Rotation<From, To> {
unsafe {
#[allow(deprecated)]
Rotation::from_tait_bryan_angles(self.yaw, self.pitch, self.roll)
}
}
}
}
#[cfg(test)]
mod tests {
use crate::builder::bearing::Components;
use crate::coordinate_systems::{Ecef, Enu, Frd, Ned};
use crate::coordinates::Coordinate;
use crate::geodetic::Wgs84;
use crate::math::{RigidBodyTransform, Rotation};
use crate::util::BoundedAngle;
use crate::vectors::Vector;
use crate::{Bearing, Point3, Vector3, coordinate, vector};
use approx::assert_abs_diff_eq;
use approx::assert_relative_eq;
use rstest::rstest;
use std::format;
use uom::si::f64::{Angle, Length};
use uom::si::{angle::degree, length::meter};
fn m(meters: f64) -> Length {
Length::new::<meter>(meters)
}
fn d(degrees: f64) -> Angle {
Angle::new::<degree>(degrees)
}
system!(struct PlaneFrd using FRD);
system!(struct PlaneNed using NED);
system!(struct PlaneEnu using ENU);
system!(struct PlaneBNed using NED);
system!(struct SensorFrd using FRD);
system!(struct EmitterFrd using FRD);
#[rstest]
#[case(
Point3::new(1., 0., 0.),
(d(0.), d(0.), d(0.)),
Point3::new(1., 0., 0.)
)]
#[case(
Point3::new(1., 0., 0.),
(d(90.), d(0.), d(0.)),
Point3::new(0., -1., 0.)
)]
#[case(
Point3::new(1., 0., 0.),
(d(180.), d(0.), d(0.)),
Point3::new(-1., 0., 0.)
)]
#[case(
Point3::new(1., 0., 0.),
(d(270.), d(0.), d(0.)),
Point3::new(0., 1., 0.)
)]
#[case(
Point3::new(1., 0., 0.),
(d(360.), d(0.), d(0.)),
Point3::new(1., 0., 0.)
)]
#[case(
Point3::new(1., 0., 0.),
(d(0.), d(90.), d(0.)),
Point3::new(0., 0., 1.)
)]
#[case(
Point3::new(1., 0., 0.),
(d(0.), d(180.), d(0.)),
Point3::new(-1., 0., 0.)
)]
#[case(
Point3::new(1., 0., 0.),
(d(0.), d(270.), d(0.)),
Point3::new(0., 0., -1.)
)]
#[case(
Point3::new(1., 0., 0.),
(d(0.), d(360.), d(0.)),
Point3::new(1., 0., 0.)
)]
#[case( // No effect on x-axis
Point3::new(1., 0., 0.),
(d(0.), d(0.), d(69.)),
Point3::new(1., 0., 0.)
)]
#[case(
Point3::new(0., 1., 0.),
(d(0.), d(0.), d(90.)),
Point3::new(0., 0., -1.)
)]
#[case(
Point3::new(0., 1., 0.),
(d(0.), d(0.), d(180.)),
Point3::new(0., -1., 0.)
)]
#[case(
Point3::new(0., 1., 0.),
(d(0.), d(0.), d(270.)),
Point3::new(0., 0., 1.)
)]
#[case(
Point3::new(0., 1., 0.),
(d(0.), d(0.), d(360.)),
Point3::new(0., 1., 0.)
)]
#[case(
Point3::new(1., 0., 0.),
(d(90.), d(180.), d(0.)),
Point3::new(0., -1., 0.)
)]
#[case(
Point3::new(0., 1., 0.),
(d(90.), d(180.), d(0.)),
Point3::new(-1., 0., 0.)
)]
#[case(
Point3::new(0., 0., 1.),
(d(90.), d(180.), d(0.)),
Point3::new(0., 0., -1.)
)]
#[case(
Point3::new(0., 10., 0.),
(d(90.), d(45.), d(0.)),
Point3::new(7.0710678118654755, 0., 7.071067811865475)
)]
#[case(
Point3::new(10., 0., 0.),
(d(90.), d(45.), d(0.)),
Point3::new(0., -10., 0.)
)]
#[case(
Point3::new(- 0.00028087357950656866, 10.000385238610235, - 2.2283317053339857e-5),
(d(39.), d(45.), d(55.)),
Point3::new(4.450, 8.103, - 3.814)
)]
fn earth_bound_and_frd_coordinate_transforms_work(
#[case] point_in_a: Point3,
#[case] ypr: (Angle, Angle, Angle),
#[case] point_in_b: Point3,
) {
let (yaw, pitch, roll) = ypr;
let frd_orientation = unsafe {
Rotation::<SensorFrd, EmitterFrd>::tait_bryan_builder()
.yaw(yaw)
.pitch(pitch)
.roll(roll)
.build()
};
if pitch <= d(90.) {
let (y, p, r) = frd_orientation.to_tait_bryan_angles();
for (a, b) in [(y, yaw), (p, pitch), (r, roll)] {
assert_abs_diff_eq!(&BoundedAngle::new(a), &BoundedAngle::new(b));
}
}
let frd_a = Coordinate::<SensorFrd>::from_nalgebra_point(point_in_a);
let frd_b = frd_orientation.transform(frd_a);
let frd_a_again = frd_orientation.inverse_transform(frd_b);
assert_relative_eq!(
frd_b,
Coordinate::<EmitterFrd>::from_nalgebra_point(point_in_b),
);
assert_relative_eq!(frd_a_again, frd_a);
let (yaw_converted, pitch_converted, roll_converted) = frd_orientation.euler_angles();
let frd_orientation_converted = unsafe {
Rotation::<SensorFrd, EmitterFrd>::tait_bryan_builder()
.yaw(yaw_converted)
.pitch(pitch_converted)
.roll(roll_converted)
.build()
};
assert_relative_eq!(frd_orientation, frd_orientation_converted);
let ned_orientation = unsafe {
Rotation::<Ned, Frd>::tait_bryan_builder()
.yaw(yaw)
.pitch(pitch)
.roll(roll)
.build()
};
let ned = Coordinate::<Ned>::from_nalgebra_point(point_in_a);
let frd = ned_orientation.transform(ned);
let ned_again = ned_orientation.inverse_transform(frd);
assert_relative_eq!(frd, Coordinate::<Frd>::from_nalgebra_point(point_in_b));
assert_relative_eq!(ned_again, ned);
let translation = Vector3::new(1., 0., 0.);
let ned_pose = unsafe {
RigidBodyTransform::<Ned, Frd>::new(
Vector::from_nalgebra_vector(translation),
ned_orientation,
)
};
let frd_after_pose = ned_pose.transform(ned);
assert_relative_eq!(
frd_after_pose,
frd - ned_orientation.transform(Vector::<Ned>::from_nalgebra_vector(translation)),
);
let ned_again_after_pose = ned_pose.inverse_transform(frd_after_pose);
assert_relative_eq!(ned, ned_again_after_pose);
let enu_orientation = unsafe {
Rotation::<Enu, Frd>::tait_bryan_builder()
.yaw(yaw)
.pitch(pitch)
.roll(roll)
.build()
};
let enu = Coordinate::<Enu>::from_nalgebra_point(point_in_a);
let frd = enu_orientation.transform(enu);
let enu_again = enu_orientation.inverse_transform(frd);
assert_relative_eq!(frd, Coordinate::<Frd>::from_nalgebra_point(point_in_b));
assert_relative_eq!(enu_again, enu);
let translation = Vector3::new(1., 0., 0.);
let enu_pose = unsafe {
RigidBodyTransform::<Enu, Frd>::new(
Vector::from_nalgebra_vector(translation),
enu_orientation,
)
};
let frd_after_pose = enu_pose.transform(enu);
assert_relative_eq!(
frd_after_pose,
frd - enu_orientation.transform(Vector::<Enu>::from_nalgebra_vector(translation)),
);
let enu_again_after_pose = enu_pose.inverse_transform(frd_after_pose);
assert_relative_eq!(enu, enu_again_after_pose)
}
#[test]
fn orientation_multiplication_works() {
let ned_to_frd = unsafe {
Rotation::<PlaneNed, PlaneFrd>::tait_bryan_builder()
.yaw(d(90.))
.pitch(d(90.))
.roll(d(0.))
.build()
};
let ecef_to_ned = unsafe { Rotation::<Ecef, PlaneNed>::ecef_to_ned_at(d(52.), d(-3.)) };
let ecef_to_frd = ecef_to_ned * ned_to_frd;
let forward = coordinate!(f = m(1.), r = m(0.), d = m(0.); in PlaneFrd);
let right = coordinate!(f = m(0.), r = m(1.), d = m(0.); in PlaneFrd);
let down = coordinate!(f = m(0.), r = m(0.), d = m(1.); in PlaneFrd);
let forward_in_ned = ned_to_frd.inverse_transform(forward);
let right_in_ned = ned_to_frd.inverse_transform(right);
let down_in_ned = ned_to_frd.inverse_transform(down);
assert_relative_eq!(
forward_in_ned,
-coordinate!(n = m(0.), e = m(0.), d = m(1.))
);
assert_relative_eq!(right_in_ned, -coordinate!(n = m(1.), e = m(0.), d = m(0.)));
assert_relative_eq!(down_in_ned, coordinate!(n = m(0.), e = m(1.), d = m(0.)));
let forward_in_ecef = ecef_to_ned.inverse_transform(forward_in_ned);
let right_in_ecef = ecef_to_ned.inverse_transform(right_in_ned);
let down_in_ecef = ecef_to_ned.inverse_transform(down_in_ned);
let frd_to_ecef = ecef_to_frd.inverse();
assert_relative_eq!(forward_in_ecef, ecef_to_frd.inverse_transform(forward));
assert_relative_eq!(
frd_to_ecef.transform(forward),
ecef_to_frd.inverse_transform(forward)
);
assert_relative_eq!(right_in_ecef, ecef_to_frd.inverse_transform(right));
assert_relative_eq!(
frd_to_ecef.transform(right),
ecef_to_frd.inverse_transform(right)
);
assert_relative_eq!(down_in_ecef, ecef_to_frd.inverse_transform(down));
assert_relative_eq!(
frd_to_ecef.transform(down),
ecef_to_frd.inverse_transform(down)
);
let ned_to_frd_2 = unsafe {
Rotation::<PlaneBNed, SensorFrd>::tait_bryan_builder()
.yaw(d(-45.))
.pitch(d(0.))
.roll(d(0.))
.build()
};
let ecef_to_ned_2 =
unsafe { Rotation::<Ecef, PlaneBNed>::ecef_to_ned_at(d(54.), d(-3.67)) };
let ecef_to_frd_2 = ecef_to_ned_2 * ned_to_frd_2;
let frd_1_to_frd_2 = ecef_to_frd.inverse() * ecef_to_frd_2;
let forward_in_ned_2 = ecef_to_ned_2.transform(forward_in_ecef);
let right_in_ned_2 = ecef_to_ned_2.transform(right_in_ecef);
let down_in_ned_2 = ecef_to_ned_2.transform(down_in_ecef);
let forward_in_frd_2 = ned_to_frd_2.transform(forward_in_ned_2);
let right_in_frd_2 = ned_to_frd_2.transform(right_in_ned_2);
let down_in_frd_2 = ned_to_frd_2.transform(down_in_ned_2);
let forward_chained = frd_1_to_frd_2.transform(forward);
let right_chained = frd_1_to_frd_2.transform(right);
let down_chained = frd_1_to_frd_2.transform(down);
assert_relative_eq!(forward_in_frd_2, forward_chained);
assert_relative_eq!(right_in_frd_2, right_chained);
assert_relative_eq!(down_in_frd_2, down_chained);
}
impl From<&nav_types::ECEF<f64>> for Coordinate<Ecef> {
fn from(value: &nav_types::ECEF<f64>) -> Self {
coordinate!(x = m(value.x()), y = m(value.y()), z = m(value.z()))
}
}
#[rstest]
#[case(d(47.9948211), d(7.8211606), m(1000.))]
#[case(d(67.112282), d(19.880389), m(0.))]
#[case(d(84.883074), d(-29.160550), m(2000.))]
#[case(d(-27.270950), d(143.722880), m(100.))]
fn orientation_ecef_to_ned_construction(
#[case] lat: Angle,
#[case] long: Angle,
#[case] alt: Length,
) {
let ecef_to_ned = unsafe { Rotation::<Ecef, PlaneNed>::ecef_to_ned_at(lat, long) };
let location = nav_types::WGS84::from_degrees_and_meters(
lat.get::<degree>(),
long.get::<degree>(),
alt.get::<meter>(),
);
let location_ecef = nav_types::ECEF::from(location);
let north = nav_types::NED::new(1., 0., 0.);
let east = nav_types::NED::new(0., 1., 0.);
let down = nav_types::NED::new(0., 0., 1.);
let origin = nav_types::NED::new(0., 0., 0.);
let ned_origin_in_ecef = location_ecef + origin;
let ned_origin_in_ecef = Coordinate::<Ecef>::from(&ned_origin_in_ecef);
let ned_north_in_ecef = location_ecef + north;
let ned_north_in_ecef = Coordinate::<Ecef>::from(&ned_north_in_ecef);
let ned_east_in_ecef = location_ecef + east;
let ned_east_in_ecef = Coordinate::<Ecef>::from(&ned_east_in_ecef);
let ned_down_in_ecef = location_ecef + down;
let ned_down_in_ecef = Coordinate::<Ecef>::from(&ned_down_in_ecef);
let result_origin = ecef_to_ned.inverse_transform(Coordinate::<PlaneNed>::origin());
assert_relative_eq!(Coordinate::<Ecef>::origin(), result_origin);
assert_relative_eq!(
ecef_to_ned.transform(Coordinate::<Ecef>::origin()),
Coordinate::<PlaneNed>::origin()
);
let point_on_north =
Coordinate::<PlaneNed>::origin() + Vector::<PlaneNed>::ned_north_axis();
let result_north = ecef_to_ned.inverse_transform(point_on_north);
let expected_north = ned_north_in_ecef - ned_origin_in_ecef;
assert_relative_eq!(result_north, Coordinate::<Ecef>::origin() + expected_north);
let point_on_east = Coordinate::<PlaneNed>::origin() + Vector::<PlaneNed>::ned_east_axis();
let result_east = ecef_to_ned.inverse_transform(point_on_east);
let expected_east = ned_east_in_ecef - ned_origin_in_ecef;
assert_relative_eq!(result_east, Coordinate::<Ecef>::origin() + expected_east);
let point_on_down = Coordinate::<PlaneNed>::origin() + Vector::<PlaneNed>::ned_down_axis();
let result_down = ecef_to_ned.inverse_transform(point_on_down);
let expected_down = ned_down_in_ecef - ned_origin_in_ecef;
assert_relative_eq!(result_down, Coordinate::<Ecef>::origin() + expected_down);
let pose = unsafe {
RigidBodyTransform::<Ecef, PlaneNed>::ecef_to_ned_at(
&Wgs84::builder()
.latitude(lat)
.expect("latitude is in-range")
.longitude(long)
.altitude(alt)
.build(),
)
};
let result_ned_north_in_ecef = pose.inverse_transform(point_on_north);
assert_relative_eq!(result_ned_north_in_ecef, ned_north_in_ecef);
let result_ned_east_in_ecef = pose.inverse_transform(point_on_east);
assert_relative_eq!(result_ned_east_in_ecef, ned_east_in_ecef);
let result_ned_down_in_ecef = pose.inverse_transform(point_on_down);
assert_relative_eq!(result_ned_down_in_ecef, ned_down_in_ecef);
}
#[test]
fn pose_serde() {
let pose = unsafe {
RigidBodyTransform::new(
vector!(n = m(50.), e = m(45.), d = m(10.)),
Rotation::<PlaneNed, PlaneFrd>::tait_bryan_builder()
.yaw(d(15.))
.pitch(d(0.))
.roll(d(1.))
.build(),
)
};
let ser = serde_yaml::to_string(&pose).unwrap();
let de = serde_yaml::from_str::<RigidBodyTransform<PlaneNed, PlaneFrd>>(&ser).unwrap();
assert_eq!(pose, de);
}
#[test]
fn bearing_rotation() {
let ned_to_frd = unsafe {
Rotation::<Ned, Frd>::tait_bryan_builder()
.yaw(d(90.))
.pitch(d(0.))
.roll(d(0.))
.build()
};
assert_relative_eq!(
Bearing::<Ned>::build(Components {
azimuth: d(90.),
elevation: d(45.)
})
.unwrap()
* ned_to_frd,
Bearing::<Frd>::build(Components {
azimuth: d(0.),
elevation: d(45.)
})
.unwrap()
);
assert_relative_eq!(
Bearing::<Ned>::build(Components {
azimuth: d(180.),
elevation: d(45.)
})
.unwrap()
* ned_to_frd,
Bearing::<Frd>::build(Components {
azimuth: d(90.),
elevation: d(45.)
})
.unwrap()
);
assert_relative_eq!(
ned_to_frd
* Bearing::<Frd>::build(Components {
azimuth: d(0.),
elevation: d(45.)
})
.unwrap(),
Bearing::<Ned>::build(Components {
azimuth: d(90.),
elevation: d(45.)
})
.unwrap()
);
assert_relative_eq!(
ned_to_frd
* Bearing::<Frd>::build(Components {
azimuth: d(180.),
elevation: d(45.)
})
.unwrap(),
Bearing::<Ned>::build(Components {
azimuth: d(-90.),
elevation: d(45.)
})
.unwrap()
);
let ned_to_frd = unsafe {
Rotation::<Ned, Frd>::tait_bryan_builder()
.yaw(d(0.))
.pitch(d(45.))
.roll(d(0.))
.build()
};
assert_relative_eq!(
Bearing::<Ned>::build(Components {
azimuth: d(0.),
elevation: d(0.)
})
.unwrap()
* ned_to_frd,
Bearing::<Frd>::build(Components {
azimuth: d(0.),
elevation: d(-45.)
})
.unwrap()
);
assert_relative_eq!(
Bearing::<Ned>::build(Components {
azimuth: d(0.),
elevation: d(45.)
})
.unwrap()
* ned_to_frd,
Bearing::<Frd>::build(Components {
azimuth: d(0.),
elevation: d(0.)
})
.unwrap()
);
assert_relative_eq!(
ned_to_frd
* Bearing::<Frd>::build(Components {
azimuth: d(0.),
elevation: d(-45.)
})
.unwrap(),
Bearing::<Ned>::build(Components {
azimuth: d(0.),
elevation: d(0.)
})
.unwrap()
);
assert_relative_eq!(
ned_to_frd
* Bearing::<Frd>::build(Components {
azimuth: d(0.),
elevation: d(0.)
})
.unwrap(),
Bearing::<Ned>::build(Components {
azimuth: d(0.),
elevation: d(45.)
})
.unwrap()
);
let ned_to_frd = unsafe {
Rotation::<Ned, Frd>::tait_bryan_builder()
.yaw(d(180.)) .pitch(d(45.)) .roll(d(0.))
.build()
};
assert_relative_eq!(
ned_to_frd
* Bearing::<Frd>::build(Components {
azimuth: d(0.),
elevation: d(0.)
})
.unwrap(),
Bearing::<Ned>::build(Components {
azimuth: d(180.),
elevation: d(45.)
})
.unwrap()
);
assert_relative_eq!(
ned_to_frd
* Bearing::<Frd>::build(Components {
azimuth: d(180.),
elevation: d(0.)
})
.unwrap(),
Bearing::<Ned>::build(Components {
azimuth: d(0.),
elevation: d(-45.)
})
.unwrap()
);
assert_relative_eq!(
Bearing::<Ned>::build(Components {
azimuth: d(180.),
elevation: d(45.)
})
.unwrap()
* ned_to_frd,
Bearing::<Frd>::build(Components {
azimuth: d(0.),
elevation: d(0.)
})
.unwrap()
);
assert_relative_eq!(
Bearing::<Ned>::build(Components {
azimuth: d(0.),
elevation: d(-45.)
})
.unwrap()
* ned_to_frd,
Bearing::<Frd>::build(Components {
azimuth: d(180.),
elevation: d(0.)
})
.unwrap()
);
assert_relative_eq!(
ned_to_frd
* Bearing::<Frd>::build(Components {
azimuth: d(-90.),
elevation: d(0.)
})
.unwrap(),
Bearing::<Ned>::build(Components {
azimuth: d(90.),
elevation: d(0.)
})
.unwrap()
);
assert_relative_eq!(
Bearing::<Ned>::build(Components {
azimuth: d(90.),
elevation: d(0.)
})
.unwrap()
* ned_to_frd,
Bearing::<Frd>::build(Components {
azimuth: d(-90.),
elevation: d(0.)
})
.unwrap()
);
assert_relative_eq!(
ned_to_frd
* Bearing::<Frd>::build(Components {
azimuth: d(0.),
elevation: d(-90.)
})
.unwrap(),
Bearing::<Ned>::build(Components {
azimuth: d(180.),
elevation: d(-45.)
})
.unwrap()
);
assert_relative_eq!(
BoundedAngle::new(
(Bearing::<Ned>::build(Components {
azimuth: d(180.),
elevation: d(-45.)
})
.unwrap()
* ned_to_frd)
.elevation()
),
BoundedAngle::new(d(-90.))
);
let ned_to_frd = unsafe {
Rotation::<Ned, Frd>::tait_bryan_builder()
.yaw(d(180.)) .pitch(d(45.)) .roll(d(180.)) .build()
};
assert_relative_eq!(
ned_to_frd
* Bearing::<Frd>::build(Components {
azimuth: d(0.),
elevation: d(0.)
})
.unwrap(),
Bearing::<Ned>::build(Components {
azimuth: d(180.),
elevation: d(45.)
})
.unwrap()
);
assert_relative_eq!(
ned_to_frd
* Bearing::<Frd>::build(Components {
azimuth: d(180.),
elevation: d(0.)
})
.unwrap(),
Bearing::<Ned>::build(Components {
azimuth: d(0.),
elevation: d(-45.)
})
.unwrap()
);
assert_relative_eq!(
Bearing::<Ned>::build(Components {
azimuth: d(180.),
elevation: d(45.)
})
.unwrap()
* ned_to_frd,
Bearing::<Frd>::build(Components {
azimuth: d(0.),
elevation: d(0.)
})
.unwrap()
);
assert_relative_eq!(
Bearing::<Ned>::build(Components {
azimuth: d(0.),
elevation: d(-45.)
})
.unwrap()
* ned_to_frd,
Bearing::<Frd>::build(Components {
azimuth: d(180.),
elevation: d(0.)
})
.unwrap()
);
assert_relative_eq!(
ned_to_frd
* Bearing::<Frd>::build(Components {
azimuth: d(-90.),
elevation: d(0.)
})
.unwrap(),
Bearing::<Ned>::build(Components {
azimuth: d(-90.),
elevation: d(0.)
})
.unwrap()
);
assert_relative_eq!(
Bearing::<Ned>::build(Components {
azimuth: d(-90.),
elevation: d(0.)
})
.unwrap()
* ned_to_frd,
Bearing::<Frd>::build(Components {
azimuth: d(-90.),
elevation: d(0.)
})
.unwrap()
);
assert_relative_eq!(
ned_to_frd
* Bearing::<Frd>::build(Components {
azimuth: d(0.),
elevation: d(-90.)
})
.unwrap(),
Bearing::<Ned>::build(Components {
azimuth: d(0.),
elevation: d(45.)
})
.unwrap()
);
assert_relative_eq!(
BoundedAngle::new(
(Bearing::<Ned>::build(Components {
azimuth: d(0.),
elevation: d(45.)
})
.unwrap()
* ned_to_frd)
.elevation()
),
BoundedAngle::new(d(-90.))
);
}
#[rstest]
#[case(d(47.9948211), d(7.8211606), m(1000.))]
#[case(d(67.112282), d(19.880389), m(0.))]
#[case(d(84.883074), d(-29.160550), m(2000.))]
#[case(d(-27.270950), d(143.722880), m(100.))]
fn orientation_ecef_to_enu_construction(
#[case] lat: Angle,
#[case] long: Angle,
#[case] alt: Length,
) {
let ecef_to_enu = unsafe { Rotation::<Ecef, PlaneEnu>::ecef_to_enu_at(lat, long) };
let location = nav_types::WGS84::from_degrees_and_meters(
lat.get::<degree>(),
long.get::<degree>(),
alt.get::<meter>(),
);
let location_ecef = nav_types::ECEF::from(location);
let east = nav_types::ENU::new(1., 0., 0.);
let north = nav_types::ENU::new(0., 1., 0.);
let up = nav_types::ENU::new(0., 0., 1.);
let origin = nav_types::ENU::new(0., 0., 0.);
let enu_origin_in_ecef = location_ecef + origin;
let enu_origin_in_ecef = Coordinate::<Ecef>::from(&enu_origin_in_ecef);
let enu_east_in_ecef = location_ecef + east;
let enu_east_in_ecef = Coordinate::<Ecef>::from(&enu_east_in_ecef);
let enu_north_in_ecef = location_ecef + north;
let enu_north_in_ecef = Coordinate::<Ecef>::from(&enu_north_in_ecef);
let enu_up_in_ecef = location_ecef + up;
let enu_up_in_ecef = Coordinate::<Ecef>::from(&enu_up_in_ecef);
let result_origin = ecef_to_enu.inverse_transform(Coordinate::<PlaneEnu>::origin());
assert_relative_eq!(Coordinate::<Ecef>::origin(), result_origin);
assert_relative_eq!(
ecef_to_enu.transform(Coordinate::<Ecef>::origin()),
Coordinate::<PlaneEnu>::origin()
);
let point_on_east = Coordinate::<PlaneEnu>::origin() + Vector::<PlaneEnu>::enu_east_axis();
let result_east = ecef_to_enu.inverse_transform(point_on_east);
let expected_east = enu_east_in_ecef - enu_origin_in_ecef;
assert_relative_eq!(result_east, Coordinate::<Ecef>::origin() + expected_east);
let point_on_north =
Coordinate::<PlaneEnu>::origin() + Vector::<PlaneEnu>::enu_north_axis();
let result_north = ecef_to_enu.inverse_transform(point_on_north);
let expected_north = enu_north_in_ecef - enu_origin_in_ecef;
assert_relative_eq!(result_north, Coordinate::<Ecef>::origin() + expected_north);
let point_on_up = Coordinate::<PlaneEnu>::origin() + Vector::<PlaneEnu>::enu_up_axis();
let result_up = ecef_to_enu.inverse_transform(point_on_up);
let expected_up = enu_up_in_ecef - enu_origin_in_ecef;
assert_relative_eq!(result_up, Coordinate::<Ecef>::origin() + expected_up);
let pose = unsafe {
RigidBodyTransform::<Ecef, PlaneEnu>::ecef_to_enu_at(
&Wgs84::builder()
.latitude(lat)
.expect("latitude is in-range")
.longitude(long)
.altitude(alt)
.build(),
)
};
let result_enu_east_in_ecef = pose.inverse_transform(point_on_east);
assert_relative_eq!(result_enu_east_in_ecef, enu_east_in_ecef);
let result_enu_north_in_ecef = pose.inverse_transform(point_on_north);
assert_relative_eq!(result_enu_north_in_ecef, enu_north_in_ecef);
let result_enu_up_in_ecef = pose.inverse_transform(point_on_up);
assert_relative_eq!(result_enu_up_in_ecef, enu_up_in_ecef);
}
#[test]
fn enu_ned_conversion_relationship() {
let lat = d(52.);
let long = d(-3.);
let ecef_to_enu = unsafe { Rotation::<Ecef, PlaneEnu>::ecef_to_enu_at(lat, long) };
let ecef_to_ned = unsafe { Rotation::<Ecef, PlaneNed>::ecef_to_ned_at(lat, long) };
let enu_point = coordinate!(e = m(10.), n = m(20.), u = m(5.); in PlaneEnu);
let ned_point = coordinate!(n = m(20.), e = m(10.), d = m(-5.); in PlaneNed);
let enu_in_ecef = ecef_to_enu.inverse_transform(enu_point);
let ned_in_ecef = ecef_to_ned.inverse_transform(ned_point);
assert_relative_eq!(enu_in_ecef, ned_in_ecef);
}
#[test]
fn enu_pose_serde() {
let pose = unsafe {
RigidBodyTransform::new(
vector!(e = m(50.), n = m(45.), u = m(10.)),
Rotation::<PlaneEnu, PlaneFrd>::tait_bryan_builder()
.yaw(d(15.))
.pitch(d(0.))
.roll(d(1.))
.build(),
)
};
let ser = serde_yaml::to_string(&pose).unwrap();
let de = serde_yaml::from_str::<RigidBodyTransform<PlaneEnu, PlaneFrd>>(&ser).unwrap();
assert_eq!(pose, de);
}
#[test]
fn bearing_rotation_enu_to_frd() {
let enu_to_frd_pointing_north_rolled_upright = unsafe {
Rotation::<Enu, Frd>::tait_bryan_builder()
.yaw(d(90.))
.pitch(d(0.))
.roll(d(180.))
.build()
};
assert_relative_eq!(
Bearing::<Enu>::build(Components {
azimuth: d(0.),
elevation: d(0.)
})
.unwrap()
* enu_to_frd_pointing_north_rolled_upright,
Bearing::<Frd>::build(Components {
azimuth: d(0.),
elevation: d(0.)
})
.unwrap()
);
assert_relative_eq!(
Bearing::<Enu>::build(Components {
azimuth: d(93.),
elevation: d(0.)
})
.unwrap()
* enu_to_frd_pointing_north_rolled_upright,
Bearing::<Frd>::build(Components {
azimuth: d(93.),
elevation: d(0.)
})
.unwrap()
);
let enu_to_frd_pointing_west_rolled_upright = unsafe {
Rotation::<Enu, Frd>::tait_bryan_builder()
.yaw(d(180.)) .pitch(d(0.))
.roll(d(180.))
.build()
};
assert_relative_eq!(
Bearing::<Enu>::build(Components {
azimuth: d(270.),
elevation: d(30.)
})
.unwrap()
* enu_to_frd_pointing_west_rolled_upright,
Bearing::<Frd>::build(Components {
azimuth: d(0.),
elevation: d(30.)
})
.unwrap()
);
assert_relative_eq!(
Bearing::<Enu>::build(Components {
azimuth: d(180.),
elevation: d(-62.)
})
.unwrap()
* enu_to_frd_pointing_west_rolled_upright,
Bearing::<Frd>::build(Components {
azimuth: d(-90.),
elevation: d(-62.)
})
.unwrap()
);
let enu_to_frd_pointing_north_east_and_up_rolled_upright = unsafe {
Rotation::<Enu, Frd>::tait_bryan_builder()
.yaw(d(45.))
.pitch(d(-30.)) .roll(d(180.))
.build()
};
assert_relative_eq!(
Bearing::<Enu>::build(Components {
azimuth: d(46.),
elevation: d(-10.)
})
.unwrap()
* enu_to_frd_pointing_north_east_and_up_rolled_upright,
Bearing::<Frd>::build(Components {
azimuth: d(1.2855),
elevation: d(-39.9944)
})
.unwrap(),
epsilon = 0.0001_f64.to_radians() );
let enu_to_frd_pointing_north_inverted = unsafe {
Rotation::<Enu, Frd>::tait_bryan_builder()
.yaw(d(90.))
.pitch(d(0.))
.roll(d(0.))
.build()
};
assert_relative_eq!(
Bearing::<Enu>::build(Components {
azimuth: d(93.),
elevation: d(0.)
})
.unwrap()
* enu_to_frd_pointing_north_inverted,
Bearing::<Frd>::build(Components {
azimuth: d(267.),
elevation: d(0.)
})
.unwrap()
);
let enu_to_frd_pointing_north_east_and_inverted = unsafe {
Rotation::<Enu, Frd>::tait_bryan_builder()
.yaw(d(45.))
.pitch(d(-30.)) .roll(d(0.))
.build()
};
assert_relative_eq!(
Bearing::<Enu>::build(Components {
azimuth: d(45.),
elevation: d(-10.)
})
.unwrap()
* enu_to_frd_pointing_north_east_and_inverted,
Bearing::<Frd>::build(Components {
azimuth: d(0.),
elevation: d(40.)
})
.unwrap()
);
}
#[test]
fn enu_to_ned_equivalent_gives_same_rotation() {
let lat = d(52.);
let lon = d(-3.);
let ecef_to_ned = unsafe { Rotation::<Ecef, PlaneNed>::ecef_to_ned_at(lat, lon) };
let ecef_to_enu = unsafe { Rotation::<Ecef, PlaneEnu>::ecef_to_enu_at(lat, lon) };
let ecef_to_ned_via_enu = unsafe { ecef_to_enu.into_ned_equivalent::<PlaneNed>() };
assert_relative_eq!(
ecef_to_ned_via_enu.inner,
ecef_to_ned.inner,
epsilon = 1e-10
);
let enu_coord_actual = coordinate!(e = m(4.), n = m(7.), u = m(1.); in PlaneEnu);
let ned_coord_expected = coordinate!(n = m(7.), e = m(4.), d = m(-1.); in PlaneNed);
let ned_coord_actual =
ecef_to_ned_via_enu.transform(ecef_to_enu.inverse_transform(enu_coord_actual));
assert_relative_eq!(ned_coord_actual, ned_coord_expected);
let enu_round_trip = unsafe { ecef_to_ned_via_enu.into_enu_equivalent::<PlaneEnu>() };
assert_relative_eq!(enu_round_trip.inner, ecef_to_enu.inner, epsilon = 1e-10);
}
#[test]
fn ned_to_enu_equivalent_gives_same_rotation() {
let lat = d(47.);
let lon = d(26.);
let ecef_to_enu = unsafe { Rotation::<Ecef, PlaneEnu>::ecef_to_enu_at(lat, lon) };
let ecef_to_ned = unsafe { Rotation::<Ecef, PlaneNed>::ecef_to_ned_at(lat, lon) };
let ecef_to_enu_via_ned = unsafe { ecef_to_ned.into_enu_equivalent::<PlaneEnu>() };
assert_relative_eq!(
ecef_to_enu_via_ned.inner,
ecef_to_enu.inner,
epsilon = 1e-10
);
let ned_coord_actual = coordinate!(n = m(18.), e = m(-2.), d = m(5.); in PlaneNed);
let enu_coord_expected = coordinate!(e = m(-2.), n = m(18.), u = m(-5.); in PlaneEnu);
let enu_coord_actual =
ecef_to_enu_via_ned.transform(ecef_to_ned.inverse_transform(ned_coord_actual));
assert_relative_eq!(enu_coord_actual, enu_coord_expected);
let ned_round_trip = unsafe { ecef_to_enu_via_ned.into_ned_equivalent::<PlaneNed>() };
assert_relative_eq!(ned_round_trip.inner, ecef_to_ned.inner, epsilon = 1e-10);
}
#[test]
fn tait_bryan_builder_works_for_rotation() {
let yaw = d(90.);
let pitch = d(45.);
let roll = d(30.);
#[allow(deprecated)]
let rotation_direct =
unsafe { Rotation::<PlaneNed, PlaneFrd>::from_tait_bryan_angles(yaw, pitch, roll) };
let rotation_builder = unsafe {
Rotation::<PlaneNed, PlaneFrd>::tait_bryan_builder()
.yaw(yaw)
.pitch(pitch)
.roll(roll)
.build()
};
assert_relative_eq!(rotation_direct.inner, rotation_builder.inner);
}
#[test]
fn tait_bryan_builder_works_for_orientation() {
use crate::engineering::Orientation;
let yaw = d(45.);
let pitch = d(-30.);
let roll = d(15.);
#[allow(deprecated)]
let orientation_direct = Orientation::<PlaneNed>::from_tait_bryan_angles(yaw, pitch, roll);
let orientation_builder = Orientation::<PlaneNed>::tait_bryan_builder()
.yaw(yaw)
.pitch(pitch)
.roll(roll)
.build();
assert_relative_eq!(
orientation_direct.inner.inner,
orientation_builder.inner.inner
);
}
#[test]
fn tait_bryan_builder_enforces_order() {
let builder = Rotation::<PlaneNed, PlaneFrd>::tait_bryan_builder();
let builder = builder.yaw(d(10.));
let builder = builder.pitch(d(20.));
let builder = builder.roll(d(30.));
let _rotation = unsafe { builder.build() };
}
#[test]
fn tait_bryan_builder_debug() {
let builder_needs_yaw = Rotation::<PlaneNed, PlaneFrd>::tait_bryan_builder();
let debug_str = format!("{builder_needs_yaw:?}");
assert!(debug_str.contains("TaitBryanBuilder<NeedsYaw>"));
assert!(!debug_str.contains("yaw"));
assert!(!debug_str.contains("pitch"));
assert!(!debug_str.contains("roll"));
let builder_after_yaw = builder_needs_yaw.yaw(d(90.));
let debug_str = format!("{builder_after_yaw:?}");
assert!(debug_str.contains("TaitBryanBuilder<NeedsPitch>"));
assert!(debug_str.contains("yaw"));
assert!(!debug_str.contains("pitch"));
assert!(!debug_str.contains("roll"));
let builder_after_pitch = builder_after_yaw.pitch(d(30.));
let debug_str = format!("{builder_after_pitch:?}");
assert!(debug_str.contains("TaitBryanBuilder<NeedsRoll>"));
assert!(debug_str.contains("yaw"));
assert!(debug_str.contains("pitch"));
assert!(!debug_str.contains("roll"));
let builder_complete = builder_after_pitch.roll(d(15.));
let debug_str = format!("{builder_complete:?}");
assert!(debug_str.contains("TaitBryanBuilder<Complete>"));
assert!(debug_str.contains("yaw"));
assert!(debug_str.contains("pitch"));
assert!(debug_str.contains("roll"));
}
#[test]
fn rotation_quaternion_identity() {
let identity =
unsafe { Rotation::<PlaneNed, PlaneFrd>::from_quaternion(1.0, 0.0, 0.0, 0.0) };
let expected = unsafe { Rotation::<PlaneNed, PlaneFrd>::identity() };
assert_relative_eq!(identity, expected);
}
#[test]
fn rotation_quaternion_normalization() {
let from_unit =
unsafe { Rotation::<PlaneNed, PlaneFrd>::from_quaternion(1.0, 0.0, 0.0, 0.0) };
let from_scaled =
unsafe { Rotation::<PlaneNed, PlaneFrd>::from_quaternion(5.0, 0.0, 0.0, 0.0) };
assert_relative_eq!(from_unit, from_scaled);
}
#[test]
fn rotation_quaternion_transform_equivalence() {
let yaw = d(90.);
let pitch = d(45.);
let roll = d(5.);
let from_angles = unsafe {
Rotation::<PlaneNed, PlaneFrd>::tait_bryan_builder()
.yaw(yaw)
.pitch(pitch)
.roll(roll)
.build()
};
let (w, i, j, k) = from_angles.to_quaternion();
let from_quat = unsafe { Rotation::<PlaneNed, PlaneFrd>::from_quaternion(w, i, j, k) };
let point = coordinate!(n = m(100.), e = m(50.), d = m(25.); in PlaneNed);
assert_relative_eq!(from_angles.transform(point), from_quat.transform(point),);
}
#[test]
fn manual_quaternion_construction_compound_rotate() {
let rotation = unsafe { Rotation::<Ecef, Ecef>::from_quaternion(0.5, 0.5, 0.5, 0.5) };
let a = coordinate!(x = m(1.0), y = m(0.0), z = m(0.0); in Ecef);
let b = coordinate!(x = m(0.0), y = m(1.0), z = m(0.0); in Ecef);
let c = coordinate!(x = m(0.0), y = m(0.0), z = m(1.0); in Ecef);
let a_rot = rotation.transform(a);
let b_rot = rotation.transform(b);
let c_rot = rotation.transform(c);
assert_relative_eq!(a_rot, b);
assert_relative_eq!(b_rot, c);
assert_relative_eq!(c_rot, a);
}
#[rstest]
#[case((1.0, 0.0, 0.0, 0.0), (d(0.0), d(0.0), d(0.0)))]
#[case((0.0, 1.0, 0.0, 0.0), (d(0.0), d(0.0), d(180.0)))]
#[case((0.0, 0.0, 1.0, 0.0), (d(0.0), d(180.0), d(0.0)))]
#[case((0.0, 0.0, 0.0, 1.0), (d(180.0), d(0.0), d(0.0)))]
#[case((std::f64::consts::FRAC_1_SQRT_2, std::f64::consts::FRAC_1_SQRT_2, 0.0, 0.0), (d(0.0), d(0.0), d(90.0)))]
#[case((std::f64::consts::FRAC_1_SQRT_2, 0.0, std::f64::consts::FRAC_1_SQRT_2, 0.0), (d(0.0), d(90.0), d(0.0)))]
#[case((std::f64::consts::FRAC_1_SQRT_2, 0.0, 0.0, std::f64::consts::FRAC_1_SQRT_2), (d(90.0), d(0.0), d(0.0)))]
#[case((-std::f64::consts::FRAC_1_SQRT_2, std::f64::consts::FRAC_1_SQRT_2, 0.0, 0.0), (d(0.0), d(0.0), d(-90.0)))]
#[case((-std::f64::consts::FRAC_1_SQRT_2, 0.0, std::f64::consts::FRAC_1_SQRT_2, 0.0), (d(0.0), d(-90.0), d(0.0)))]
#[case((-std::f64::consts::FRAC_1_SQRT_2, 0.0, 0.0, std::f64::consts::FRAC_1_SQRT_2), (d(-90.0), d(0.0), d(0.0)))]
fn manual_quaternion_construction(
#[case] q: (f64, f64, f64, f64),
#[case] ypr: (Angle, Angle, Angle),
) {
let q_rotation = unsafe { Rotation::<Ecef, Ecef>::from_quaternion(q.0, q.1, q.2, q.3) };
let ypr_rotation = unsafe {
Rotation::<Ecef, Ecef>::tait_bryan_builder()
.yaw(-ypr.0)
.pitch(-ypr.1)
.roll(-ypr.2)
.build()
};
let x = coordinate!(x = m(1.0), y = m(0.0), z = m(0.0); in Ecef);
let y = coordinate!(x = m(0.0), y = m(1.0), z = m(0.0); in Ecef);
let z = coordinate!(x = m(0.0), y = m(0.0), z = m(1.0); in Ecef);
assert_relative_eq!(q_rotation.transform(x), ypr_rotation.transform(x));
assert_relative_eq!(q_rotation.transform(y), ypr_rotation.transform(y));
assert_relative_eq!(q_rotation.transform(z), ypr_rotation.transform(z));
}
#[test]
fn rotation_and_then_rotation() {
let ecef_to_ned = unsafe { Rotation::<Ecef, PlaneNed>::ecef_to_ned_at(d(52.), d(-3.)) };
let ned_to_frd = unsafe {
Rotation::<PlaneNed, PlaneFrd>::tait_bryan_builder()
.yaw(d(90.))
.pitch(d(45.))
.roll(d(0.))
.build()
};
let ecef_to_frd_mul = ecef_to_ned * ned_to_frd;
let ecef_to_frd_and_then = ecef_to_ned.and_then(ned_to_frd);
let point = coordinate!(x = m(100.), y = m(200.), z = m(300.); in Ecef);
assert_relative_eq!(
ecef_to_frd_mul.transform(point),
ecef_to_frd_and_then.transform(point),
);
}
#[test]
fn rotation_and_then_rigid_body_transform() {
let ecef_to_ned = unsafe { Rotation::<Ecef, PlaneNed>::ecef_to_ned_at(d(52.), d(-3.)) };
let ned_to_frd = unsafe {
RigidBodyTransform::<PlaneNed, PlaneFrd>::new(
vector!(n = m(10.), e = m(20.), d = m(5.)),
Rotation::<PlaneNed, PlaneFrd>::tait_bryan_builder()
.yaw(d(90.))
.pitch(d(45.))
.roll(d(0.))
.build(),
)
};
let ecef_to_frd = ecef_to_ned.and_then(ned_to_frd);
let point = coordinate!(x = m(100.), y = m(200.), z = m(300.); in Ecef);
let via_chain = ecef_to_frd.transform(point);
let via_steps = ned_to_frd.transform(ecef_to_ned.transform(point));
assert_relative_eq!(via_chain, via_steps);
}
#[test]
fn rotation_mul_rigid_body_transform() {
let ecef_to_ned = unsafe { Rotation::<Ecef, PlaneNed>::ecef_to_ned_at(d(52.), d(-3.)) };
let ned_to_frd = unsafe {
RigidBodyTransform::<PlaneNed, PlaneFrd>::new(
vector!(n = m(10.), e = m(20.), d = m(5.)),
Rotation::<PlaneNed, PlaneFrd>::tait_bryan_builder()
.yaw(d(90.))
.pitch(d(45.))
.roll(d(0.))
.build(),
)
};
let ecef_to_frd = ecef_to_ned * ned_to_frd;
let point = coordinate!(x = m(100.), y = m(200.), z = m(300.); in Ecef);
let in_frd = ecef_to_frd.transform(point);
let back = ecef_to_frd.inverse_transform(in_frd);
assert_relative_eq!(point, back);
}
#[test]
fn velocity_acceleration_vector_multiplication_works() {
use crate::vector;
use crate::vector::{AccelerationVector, VelocityVector};
use uom::si::acceleration::meter_per_second_squared;
use uom::si::f64::{Acceleration, Velocity};
use uom::si::velocity::meter_per_second;
let rotation = unsafe {
Rotation::<PlaneFrd, SensorFrd>::tait_bryan_builder()
.yaw(d(90.))
.pitch(d(0.))
.roll(d(0.))
.build()
};
let transform = unsafe {
RigidBodyTransform::new(
vector!(f = m(10.), r = m(0.), d = m(0.); in PlaneFrd),
rotation,
)
};
let vel: VelocityVector<PlaneFrd> = vector!(
f = Velocity::new::<meter_per_second>(5.0),
r = Velocity::new::<meter_per_second>(0.0),
d = Velocity::new::<meter_per_second>(0.0);
in PlaneFrd
);
let acc: AccelerationVector<PlaneFrd> = vector!(
f = Acceleration::new::<meter_per_second_squared>(3.0),
r = Acceleration::new::<meter_per_second_squared>(0.0),
d = Acceleration::new::<meter_per_second_squared>(0.0);
in PlaneFrd
);
let vel_rotated: VelocityVector<SensorFrd> = vel * rotation;
let vel_transformed: VelocityVector<SensorFrd> = vel * transform;
let expected_vel: VelocityVector<SensorFrd> = vector!(
f = Velocity::new::<meter_per_second>(0.0),
r = Velocity::new::<meter_per_second>(-5.0),
d = Velocity::new::<meter_per_second>(0.0);
in SensorFrd
);
assert_relative_eq!(vel_rotated, expected_vel);
assert_relative_eq!(vel_transformed, expected_vel);
let vel_rev_rotated: VelocityVector<PlaneFrd> = rotation * expected_vel;
let vel_rev_transformed: VelocityVector<PlaneFrd> = transform * expected_vel;
assert_relative_eq!(vel_rev_rotated, vel);
assert_relative_eq!(vel_rev_transformed, vel);
let acc_rotated: AccelerationVector<SensorFrd> = acc * rotation;
let acc_transformed: AccelerationVector<SensorFrd> = acc * transform;
let expected_acc: AccelerationVector<SensorFrd> = vector!(
f = Acceleration::new::<meter_per_second_squared>(0.0),
r = Acceleration::new::<meter_per_second_squared>(-3.0),
d = Acceleration::new::<meter_per_second_squared>(0.0);
in SensorFrd
);
assert_relative_eq!(acc_rotated, expected_acc);
assert_relative_eq!(acc_transformed, expected_acc);
let acc_rev_rotated: AccelerationVector<PlaneFrd> = rotation * expected_acc;
let acc_rev_transformed: AccelerationVector<PlaneFrd> = transform * expected_acc;
assert_relative_eq!(acc_rev_rotated, acc);
assert_relative_eq!(acc_rev_transformed, acc);
}
}