Skip to content

UAV: Multiple Altitude Reference Systems

Library:

Example:

This advanced example demonstrates a critical challenge in aviation software: managing multiple altitude reference systems simultaneously. In aviation, altitudes can be measured relative to different reference points (mean sea level, ground, ellipsoid, launch point), and confusing these can have catastrophic consequences. mp-units makes these distinctions type-safe and impossible to mix up.

The Problem: Multiple Altitude Datums

In aviation and UAV operations, altitude can be expressed relative to several different reference points:

  • MSL (Mean Sea Level) - standard barometric altitude
  • AGL (Above Ground Level) - height above local terrain
  • HAE (Height Above Ellipsoid) - GPS altitude relative to WGS84 ellipsoid
  • HAL (Height Above Launch) - relative altitude from takeoff point

Each serves a different purpose, and mixing them is dangerous. For example:

  • ATC (Air Traffic Control) uses MSL for separation
  • Obstacle clearance requires AGL
  • GPS provides HAE
  • UAVs often track HAL for relative navigation

Type-Safe Altitude References

Mean Sea Level (MSL)

The example uses MSL altitude from the geographic module as the base reference:

geographic.h
inline constexpr struct mean_sea_level final : mp_units::absolute_point_origin<mp_units::isq::altitude> {
} mean_sea_level;

using msl_altitude = mp_units::quantity_point<mp_units::isq::altitude[mp_units::si::metre], mean_sea_level>;

This defines the standard barometric altitude reference used in aviation, with custom formatting that appends "AMSL" (Above Mean Sea Level) to clearly identify the reference system.

Multiple Earth Gravity Models

HAE altitudes depend on which Earth gravity model is used. The example defines an enum for different Earth gravity models and a templated point origin:

unmanned_aerial_vehicle.cpp
enum class earth_gravity_model : std::int8_t { egm84_15, egm95_5, egm2008_1 };

template<earth_gravity_model M>
struct height_above_ellipsoid_t final : absolute_point_origin<isq::altitude> {
  static constexpr earth_gravity_model egm = M;
};
template<earth_gravity_model M>
constexpr height_above_ellipsoid_t<M> height_above_ellipsoid;  // NOLINT(google-readability-casting)

template<earth_gravity_model M>
using hae_altitude = quantity_point<isq::altitude[si::metre], height_above_ellipsoid<M>>;

The template parameter ensures that HAE altitudes using different models are distinct types that cannot be accidentally mixed. Each combination of altitude reference and gravity model creates a unique type, making it impossible to accidentally mix them.

Height Above Launch

For UAVs, tracking altitude relative to the launch point is critical:

unmanned_aerial_vehicle.cpp
inline constexpr struct height_above_launch final : absolute_point_origin<isq::altitude> {} height_above_launch;
// clang-format on

using hal_altitude = quantity_point<isq::altitude[si::metre], height_above_launch>;

This creates a completely separate altitude reference system for UAV operations, with its own absolute point origin distinct from MSL, HAE, or any other reference.

Converting Between Reference Systems

The type system prevents accidental conversions, but allows intentional ones:

MSL to HAE Conversion

Converting from MSL to HAE requires accounting for the geoid undulation (the difference between the ellipsoid and mean sea level at a specific location):

unmanned_aerial_vehicle.cpp
template<earth_gravity_model M>
hae_altitude<M> to_hae(msl_altitude msl, position<long double> pos)
{
  const auto geoid_undulation =
    isq::height(GeographicLibWhatsMyOffset(pos.lat.quantity_from_zero().numerical_value_in(si::degree),
                                           pos.lon.quantity_from_zero().numerical_value_in(si::degree)) *
                si::metre);
  return height_above_ellipsoid<M> + (msl - mean_sea_level - geoid_undulation);
}

This conversion:

  • Requires geographic position (latitude/longitude)
  • Accounts for local geoid undulation
  • Returns altitude of the correct HAE type for the specified Earth model

Relative Altitude Calculations

The UAV class demonstrates safe relative altitude tracking:

unmanned_aerial_vehicle.cpp
class unmanned_aerial_vehicle {
  msl_altitude current_ = mean_sea_level + 0 * si::metre;
  msl_altitude launch_ = current_;
public:
  void take_off(msl_altitude alt) { launch_ = alt; }
  [[nodiscard]] msl_altitude take_off() const { return launch_; }

  void current(msl_altitude alt) { current_ = alt; }
  [[nodiscard]] msl_altitude current() const { return current_; }

  [[nodiscard]] hal_altitude hal() const { return height_above_launch + current_.quantity_from(launch_); }
};

The quantity_from() method safely computes the difference between two points in the same reference system, returning a relative altitude (height) quantity.

Custom Formatting for Each System

Each altitude reference system has custom formatting to make output unambiguous:

unmanned_aerial_vehicle.cpp
template<class CharT, class Traits>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const hal_altitude& a)
{
  return os << a.quantity_from(height_above_launch) << " HAL";
}

template<typename Char>
struct MP_UNITS_STD_FMT::formatter<hal_altitude, Char> : formatter<hal_altitude::quantity_type, Char> {
  template<typename FormatContext>
  auto format(const hal_altitude& a, FormatContext& ctx) const -> decltype(ctx.out())
  {
    formatter<hal_altitude::quantity_type, Char>::format(a.quantity_from(height_above_launch), ctx);
    return MP_UNITS_STD_FMT::format_to(ctx.out(), " HAL");
  }
};

Output clearly shows which reference system is being used:

hal = 1219.20 m HAL
agl = 2925.00 m
EPPR: 54.24772° N 18.6745° E, 4.88 m AMSL, -24.61 m HAE(EGM2008-1)

Practical UAV Operations

The example shows realistic UAV scenarios:

unmanned_aerial_vehicle.cpp
  using namespace mp_units::si::unit_symbols;
  using namespace mp_units::international::unit_symbols;

  unmanned_aerial_vehicle uav;
  uav.take_off(mean_sea_level + 6'000 * ft);
  uav.current(mean_sea_level + 10'000 * ft);
  std::cout << MP_UNITS_STD_FMT::format("hal = {::N[.2f]}\n", uav.hal());

  const msl_altitude ground_level = mean_sea_level + 123 * m;
  std::cout << MP_UNITS_STD_FMT::format("agl = {::N[.2f]}\n", uav.current().quantity_from(ground_level));

  struct waypoint {
    std::string name;
    geographic::position<long double> pos;
    msl_altitude msl_alt;
  };

  const waypoint wpt = {"EPPR", {54.24772_N, 18.6745_E}, mean_sea_level + 16. * ft};
  std::cout << MP_UNITS_STD_FMT::format("{}: {} {}, {::N[.2f]}, {::N[.2f]}\n", wpt.name, wpt.pos.lat, wpt.pos.lon,
                                        wpt.msl_alt, to_hae<earth_gravity_model::egm2008_1>(wpt.msl_alt, wpt.pos));

Each altitude is:

  • Type-safe - Cannot be accidentally mixed
  • Self-documenting - The type tells you which reference system
  • Correctly formatted - Output makes the reference system explicit
  • Efficiently stored - Zero runtime overhead for the type safety

Real-World Safety Impact

In actual UAV operations, altitude confusion can cause:

  • Crashes - Flying into terrain thinking you're higher than you are
  • Airspace violations - Using wrong altitude reference for clearances
  • Loss of aircraft - GPS (HAE) vs barometric (MSL) confusion
  • Regulatory violations - Incorrect altitude reporting

mp-units makes these errors impossible at compile-time:

msl_altitude msl = mean_sea_level + 1000 * m;
hal_altitude hal = height_above_launch + 500 * m;

// auto sum = msl + hal;              // ✗ Compile error!
// auto diff = msl - hal;             // ✗ Compile error!
// if (msl < hal) { /* ... */ }       // ✗ Compile error!

The compiler prevents all accidental mixing of altitude references.

Key Features Demonstrated

1. Multiple Absolute Point Origins

Different origins create incompatible types, preventing dangerous mistakes:

  • mean_sea_level (MSL)
  • height_above_ellipsoid<Model> (HAE, templated by gravity model)
  • height_above_launch (HAL)

2. Template Parameters for Configuration

template<earth_gravity_model M>
struct height_above_ellipsoid_t final : absolute_point_origin<isq::altitude> {
  static constexpr earth_gravity_model egm = M;
};

The Earth model becomes part of the type, ensuring that HAE altitudes using different models cannot be confused.

3. Safe Relative Calculations

quantity<isq::altitude[m]> hal = current_.quantity_from(launch_);

The quantity_from() method safely computes relative altitudes between points in the same reference system.

4. Custom Formatting Per Reference

Each altitude type has specialized formatting showing its reference system, making output unambiguous and safe to interpret.

5. Integration with Geographic Systems

The example integrates with geographic coordinates and Earth models. The geoid undulation calculation is simplified (using a stub function that returns a constant value), but in production code this would call a proper geodetic library like GeographicLib.

Key Takeaways

  • Multiple altitude references are a critical safety issue in aviation
  • mp-units makes mixing different references a compile error
  • Type safety extends to template parameters (gravity models)
  • Zero runtime overhead for this additional safety
  • Custom formatting prevents interpretation errors
  • The type system documents which reference is being used
  • Integration with geographic libraries is seamless

For UAV and aviation software, this level of compile-time safety is invaluable and can literally save lives by preventing altitude confusion errors.