Expand description
This library provides hard-to-misuse rigid body transforms (aka “spatial math”) for engineers with other things to worry about than linear algebra.
First and foremost, the library provides Coordinate
and Vector
types for representing
points and vectors in coordinate spaces respectively. They are all generic over a
CoordinateSystem
so that coordinates from one system cannot (easily) be incorrectly misused
as though they were in a different one. The system!
macro allows you to define additional
coordinate systems with particular semantics (eg, NedLike
or
FrdLike
) such that you can distinguish between coordinates in, say,
PlaneFrd
and EmitterFrd
.
To move between coordinate systems, you’ll want to use the mathematical constructs from the
math
submodule like rigid body transforms and
rotations.
Now technically, those are all you need to express anything in coordinate system math,
including poses and orientation. It turns out that everything is an isometry if you think hard
enough about it. But if your brain is more used to thinking about orientation and poses (ie,
position + orientation), you’ll want to make use of the engineering
module which has
easier-to-grasp types like Pose
and
Orientation
.
§Primer on coordinate systems
If you’re new to working with coordinate systems and frames of reference, you may wonder what coordinate systems there are in the first place, and how they differ. There are a wide variety of ways to describe the locations of objects in space, all of which have their own slight peculiarities about representation and conversion. At the time of writing, the four coordinate systems this crate supports are: WGS84 (latitude and longitude), ECEF (“Earth-centered, Earth-fixed”), NED (“North, East, Down”), and FRD (“Front, Right, Down”).
WGS84 (Wgs84
) and ECEF (Ecef
) are both Earth-bound
coordinate systems that describe points in space on or near Earth. They do this by describing
positions relative to Earth’s major and minor axes, often by making slightly simplifying
assumptions about the Earth’s shape. WGS84 does this by using latitude and longitude (degrees
north/south of the equator and east-west of the prime meridian), while ECEF does it by placing
a coordinate system at the center of the earth and locating the X, Y, and Z axes
towards specific points on the Earth’s surface. One can convert between them without too much
trouble.
NED (NedLike
) and FRD (FrdLike
) on the other
hand are “local” coordinate systems that are descriptions of relative positions to the location
of the observer. NED is still Earth-bound in that it describes positions in terms of how far
North, East, and Down (towards Earth’s core) they are relative to the observer. FRD,
meanwhile, is a “body frame”, and just describes positions relative to the observer’s concept
of Forward (eg, the direction pointing in the same direction as the nose of a plane), Right
(eg, the direction 90º to the right when viewing along Forward), and Down (eg, down through the
belly of the plane). Converting between FRD and NED usually requires knowing the
orientation of the observer relative to North, East, and Down, and converting between NED and
ECEF (or WGS84) requires also knowing the position of the observer in Earth-bound
coordinates.
§Use of unsafe
Sguaba requires you to use unsafe
in order to construct most transformations between
coordinate systems (eg,
RigidBodyTransform::ecef_to_ned_at
or
Orientation::map_as_zero_in
). This is because
once one of these transforms have been constructed, they allow you to freely convert between
the types representing each coordinate system. Thus, if a transform is constructed with
incorrect parameters, such as giving a coordinate to ecef_to_ned_at
that does not correspond
to the location of the origin of the To
NedLike
system, type safety
would be violated. This is a slight abuse of Rust’s unsafe
mechanism, which tends to focus on
memory safety, but has proven to be valuable in highlighting areas where frame of reference
bugs are most likely to manifest.
§Examples
Assume that a pilot of a plane observes something out of their window at a given bearing and elevation angles (ie, measured in the plane’s FRD) and wants to know the location of that thing in terms of Earth-bound Latitude and Longitude coordinates (ie, WGS84.
use uom::si::f64::{Angle, Length};
use uom::si::{angle::degree, length::meter};
// FRD and NED systems are "local" coordinate systems, meaning a given coordinate in the FRD of
// one plane will have a completely different coordinate if it were to be expressed in the FRD
// of another. so, to guard against accidentally getting them mixed up, we construct a new type
// for this plane's FRD and NED:
// the pilot observes things in FRD of the plane
system!(struct PlaneFrd using FRD);
// the pilot's instruments indicate the plane's orientation in NED
system!(struct PlaneNed using NED);
// what the pilot saw:
let observation = Coordinate::<PlaneFrd>::from_bearing(
Bearing::new(
Angle::new::<degree>(20.), // clockwise from forward
Angle::new::<degree>(10.), // upwards from straight-ahead
).expect("elevation is in [-90, 90]"),
Length::new::<meter>(400.), // at this range
);
// where the plane was at the time (eg, from GPS):
let wgs84 = Wgs84::new(
Angle::new::<degree>(12.),
Angle::new::<degree>(30.),
Length::new::<meter>(1000.)
).expect("latitude is in [-90, 90]");
// where the plane was facing at the time (eg, from instrument panel);
// expressed in yaw, pitch, roll relative to North-East-Down:
let orientation_in_ned = Orientation::<PlaneNed>::from_tait_bryan_angles(
Angle::new::<degree>(8.), // yaw
Angle::new::<degree>(45.), // pitch
Angle::new::<degree>(0.), // roll
);
From there, there are two possible paths forward, one using an API that will appeal more to folks with an engineering background, and one that will appeal more to a math-oriented crowd. We’ll explore each in turn.
§Using the engineering-focused API
In the “engineering-focused” API, we can directly talk about an object’s
orientation and its “pose” (ie, position + orientation) in the world. Using these, we can
transform between different coordinate systems to go from PlaneFrd
to PlaneNed
to
Ecef
(cartesian world location) to Wgs84
. Note that one
must know the observer’s body orientation relative to NED to go from FRD to NED, and the
observer’s ECEF position to go from NED to ECEF.
// to convert between NED and ECEF, we need a transform between the two.
// this transform depends on where on the globe you are, so it takes the WGS84 position:
// SAFETY: we're claiming that `wgs84` is the location of `PlaneNed`'s origin.
let ecef_to_plane_ned = unsafe { RigidBodyTransform::ecef_to_ned_at(&wgs84) };
// to convert between FRD (which the observation was made in) and NED,
// we just need the plane's orientation, which we have from the instruments!
// SAFETY: we're claiming that the given NED orientation makes up the axes of `PlaneFrd`.
let plane_ned_to_plane_frd = unsafe { orientation_in_ned.map_as_zero_in::<PlaneFrd>() };
// these transformations can be chained to go from ECEF to NED.
// this chaining would fail to compile if you got the arguments wrong!
let ecef_to_plane_frd = ecef_to_plane_ned.and_then(plane_ned_to_plane_frd);
// this transform lets you go from ECEF to FRD, but transforms work both ways,
// so we can apply it in inverse to take our `Coordinate<PlaneFrd>` and produce
// a `Coordinate<Ecef>`:
let observation_in_ecef = ecef_to_plane_frd.inverse_transform(observation);
// we can then turn that into WGS84 lat/lon/altitude!
println!("{:?}", observation_in_ecef.to_wgs84());
§Using the math-focused API
In the “math-focused” API, everything is represented in terms of transforms between coordinate systems and the components of those transforms. For example:
// we need to find the ECEF<>NED transform for the plane's location
// SAFETY: we're claiming that `wgs84` is the location of `PlaneNed`'s origin.
let ecef_to_plane_ned = unsafe { RigidBodyTransform::ecef_to_ned_at(&wgs84) };
// the plane's orientation in NED is really a rotation and translation in ECEF
let pose_in_ecef = ecef_to_plane_ned * orientation_in_ned;
// that rotation and translation is exactly equal to the FRD of the plane
// we could also have just constructed this rotation directly instead of an `Orientation`
// SAFETY: `PlaneNed` is the orientation of the plane's FRD body axes (ie, `PlaneFrd`).
let ecef_to_frd = unsafe { pose_in_ecef.map_as_zero_in::<PlaneFrd>() };
// and we can apply that transform to the original observation to get it in ECEF
let observation_in_ecef: Coordinate<Ecef> = ecef_to_frd * observation;
// which we can then turn into WGS84 lat/lon/altitude!
println!("{:?}", observation_in_ecef.to_wgs84());
Modules§
- engineering
- Spatial operations expressed in engineering language.
- math
- Spatial operations expressed in mathematical language.
- systems
- Well-known coordinate systems and conventions.
Macros§
- system
- Defines a new coordinate system and its conventions.
Structs§
- Bearing
- A direction (conceptually represented as a unit vector) in the
CoordinateSystem
In
. - Coordinate
- Defines a point (ie, position) in the coordinate system specified by
In
. - Vector
- Defines a vector (ie, direction with magnitude) in the coordinate system specified by
In
.
Traits§
- Coordinate
System - Defines how a coordinate system behaves.