ExMgrs

Elixir library for converting between latitude/longitude coordinates and MGRS (Military Grid Reference System) coordinates. Built with Rust NIFs for maximum speed and accuracy using the embedded geoconvert-rs Rust library.

Features

Understanding Altitude: HAE vs MSL

This library works with two altitude systems. Understanding the difference matters when altitude accuracy is important (aviation, surveying, GPS integration):

HAE (Height Above Ellipsoid) -- also called "ellipsoidal height" or simply "alt" in GPS contexts. This is the raw geometric distance above the WGS84 reference ellipsoid, a smooth mathematical surface that approximates the Earth's shape. GPS receivers natively output HAE.

MSL (Mean Sea Level) -- the height above the geoid, a gravity-based surface that approximates actual sea level. This is what altimeters, aviation charts, topographic maps, and most real-world applications use.

The two differ by the geoid undulation (N) at each point on Earth:

HAE = MSL + N
MSL = HAE - N

The undulation varies from roughly -106m to +85m depending on location. For example, in Los Angeles the geoid is about 33m below the ellipsoid, so a GPS reading of 0m HAE corresponds to about 33m MSL.

This library uses the EGM96 geoid model (15-minute grid, ~2MB embedded at compile time) to convert between the two systems.

Naming conventions

Installation

From Hex (Recommended)

Add ex_mgrs to your list of dependencies in mix.exs:

def deps do
  [
    {:ex_mgrs, "~> 0.0.5"}
  ]
end

Then run:

mix deps.get
mix compile

Note: This package uses precompiled Rust NIFs for fast installation. No Rust toolchain is required for most platforms (macOS, Linux, Windows). Precompiled binaries are automatically downloaded during installation.

If precompiled binaries are not available for your platform, the package will automatically fall back to compiling from source, which requires the Rust toolchain to be installed.

Supported Platforms

Precompiled binaries are provided for:

Force Build from Source

To force compilation from source instead of using precompiled binaries:

RUSTLER_PRECOMPILATION_EXAMPLE_BUILD=true mix deps.compile ex_mgrs --force

From Source (Development)

If you want to contribute or use the latest development version:

# Clone with submodules to get the geoconvert library
git clone --recursive https://github.com/cortfritz/ex_mgrs.git
cd ex_mgrs

# If you already cloned without --recursive, initialize submodules:
git submodule update --init --recursive

# Install dependencies and compile
mix deps.get
mix compile

# Run tests
mix test

Usage

MGRS Conversions

# Convert latitude/longitude to MGRS
iex> ExMgrs.latlon_to_mgrs(34.0, -118.24, 5)
{:ok, "11SLT8548562848"}

# Convert MGRS to latitude/longitude
iex> ExMgrs.mgrs_to_latlon("11SLT8548562848")
{:ok, {34.0, -118.24}}

# Format MGRS string with proper spacing
iex> ExMgrs.format_mgrs("11SLT8548562848")
{:ok, "11S LT 85485 62848"}

# Use different precision levels
iex> ExMgrs.latlon_to_mgrs(34.0, -118.24, 3)
{:ok, "11SLT854628"}

ECEF Conversions

# Lat/lon to ECEF (height is HAE, defaults to 0)
iex> ExMgrs.Ecef.from_latlon_hae(34.0, -118.24, 500.0)
{:ok, {-2_493_090.59, -4_655_089.08, 3_553_494.47}}

# Same thing using the alt alias
iex> ExMgrs.Ecef.from_latlon(34.0, -118.24, 500.0)
{:ok, {-2_493_090.59, -4_655_089.08, 3_553_494.47}}

# ECEF back to lat/lon/HAE
iex> ExMgrs.Ecef.to_latlon_hae(-2_493_090.59, -4_655_089.08, 3_553_494.47)
{:ok, {34.0, -118.24, 500.0}}

# MGRS to ECEF
iex> ExMgrs.Ecef.from_mgrs_hae("11SLT8548562848", 500.0)
{:ok, {-2_493_090.59, -4_655_089.08, 3_553_494.47}}

# ECEF to MGRS (returns {mgrs, hae})
iex> ExMgrs.Ecef.to_mgrs_hae(-2_493_090.59, -4_655_089.08, 3_553_494.47)
{:ok, {"11SLT8548562848", 500.0}}

# Euclidean distance between two ECEF points
iex> ExMgrs.Ecef.distance(point1, point2)
1234.56

Geoid / MSL Conversions

# Get geoid undulation at a location
iex> ExMgrs.Geoid.undulation(34.0, -118.24)
{:ok, -32.87}

# Convert between HAE and MSL
iex> ExMgrs.Geoid.hae_to_msl(34.0, -118.24, 500.0)
{:ok, 532.87}

iex> ExMgrs.Geoid.msl_to_hae(34.0, -118.24, 532.87)
{:ok, 500.0}

# alt aliases work the same way
iex> ExMgrs.Geoid.alt_to_msl(34.0, -118.24, 500.0)
{:ok, 532.87}

# Lat/lon + MSL to ECEF (converts through geoid automatically)
iex> ExMgrs.Geoid.latlon_msl_to_ecef(34.0, -118.24, 500.0)
{:ok, {x, y, z}}

# ECEF to lat/lon + MSL
iex> ExMgrs.Geoid.ecef_to_latlon_msl(x, y, z)
{:ok, {34.0, -118.24, 500.0}}

# MGRS + MSL to ECEF
iex> ExMgrs.Geoid.mgrs_msl_to_ecef("11SLT8548562848", 500.0)
{:ok, {x, y, z}}

# ECEF to MGRS + MSL
iex> ExMgrs.Geoid.ecef_to_mgrs_msl(x, y, z)
{:ok, {"11SLT8548562848", 500.0}}

# MGRS with altitude conversions
iex> ExMgrs.Geoid.mgrs_hae_to_msl("11SLT8548562848", 500.0)
{:ok, {34.0, -118.24, 532.87}}

iex> ExMgrs.Geoid.mgrs_msl_to_hae("11SLT8548562848", 532.87)
{:ok, {34.0, -118.24, 500.0}}

Conversion Matrix

From To Function
lat/lon + HAE ECEF Ecef.from_latlon_hae/3 or Ecef.from_latlon/3
ECEF lat/lon + HAE Ecef.to_latlon_hae/3 or Ecef.to_latlon/3
MGRS + HAE ECEF Ecef.from_mgrs_hae/2 or Ecef.from_mgrs/2
ECEF MGRS + HAE Ecef.to_mgrs_hae/4 or Ecef.to_mgrs/4
lat/lon + MSL ECEF Geoid.latlon_msl_to_ecef/3
ECEF lat/lon + MSL Geoid.ecef_to_latlon_msl/3
MGRS + MSL ECEF Geoid.mgrs_msl_to_ecef/2
ECEF MGRS + MSL Geoid.ecef_to_mgrs_msl/4
HAE MSL Geoid.hae_to_msl/3 or Geoid.alt_to_msl/3
MSL HAE Geoid.msl_to_hae/3 or Geoid.msl_to_alt/3
MGRS + HAE lat/lon + MSL Geoid.mgrs_hae_to_msl/2 or Geoid.mgrs_alt_to_msl/2
MGRS + MSL lat/lon + HAE Geoid.mgrs_msl_to_hae/2 or Geoid.mgrs_msl_to_alt/2

Development

Prerequisites

Setup

# Clone the repository with submodules (for development)
git clone --recursive https://github.com/cortfritz/ex_mgrs.git
cd ex_mgrs

# If you already cloned without --recursive, initialize submodules:
git submodule update --init --recursive

# Install dependencies
mix deps.get

# Compile (includes Rust NIF compilation)
mix compile

# Run tests
mix test

# Format code
mix format

Note: This project includes a mise.toml file configured for Elixir 1.19.2-otp-28. If you use mise, it will automatically use this version. You can override this locally or use any version within the required range (>= 1.18.2 and <= 1.19.2) with your preferred version manager.

Architecture

This library includes coordinate conversion functionality in two ways:

For Development:

For Hex Packages:

Common Structure:

Contributing

We welcome contributions! Please follow these guidelines:

Getting Started

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-feature
  3. Make your changes following the project conventions
  4. Ensure tests pass: mix test
  5. Format your code: mix format
  6. Commit with a descriptive message
  7. Push and create a pull request

Development Guidelines

Reporting Issues

Please use GitHub Issues to report bugs or request features. Include:

Safety

This NIF is safe for your Elixir/Erlang host process:

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Documentation can be generated with ExDoc:

mix docs

Once published to Hex, docs will be available at https://hexdocs.pm/ex_mgrs.