# How to Correct Ellipsoidal Altitude to MSL Using Geoid Undulation in Gods-Eye-View

> Correct ellipsoidal altitude to MSL using geoid undulation with Gods-Eye-View. Learn how to use the ellipsoidalToMslDisplayM function and ensure geoid grid is loaded.

- Repository: [Bilawal Sidhu/gods-eye-view](https://github.com/bilawalsidhu/gods-eye-view)
- Tags: how-to-guide
- Published: 2026-08-30

---

**To correct ellipsoidal altitude to MSL using geoid undulation, subtract the local geoid height N from the WGS-84 ellipsoidal height using the `ellipsoidalToMslDisplayM()` function in [`src/data/geoid.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/geoid.js) after ensuring the EGM96 grid is loaded via `ensureGeoidReady()`.**

The [Gods-Eye-View](https://github.com/bilawalsidhu/gods-eye-view) repository tracks aircraft and vessel positions using the WGS-84 ellipsoid model, but displaying altitudes relative to Mean Sea Level (MSL) requires accounting for the vertical offset between the ellipsoid and the geoid. This article explains how to correct ellipsoidal altitude to MSL using geoid undulation as implemented in the codebase's geoid utility module.

## Understanding the Geoid Correction Model

GPS and modern tracking systems record **ellipsoidal altitude**—the vertical distance above the mathematical WGS-84 ellipsoid. However, maps and aviation charts reference **MSL altitude**, which measures height above the geoid (a theoretical equipotential surface approximating sea level). The difference between these surfaces is the **geoid undulation** (N), which varies by location.

According to the Gods-Eye-View source code, the conversion follows the standard geodetic formula:

```

MSL Altitude = Ellipsoidal Altitude - N

```

Where *N* represents the geoid height in meters at a specific latitude and longitude. When the geoid lies below the ellipsoid (negative N), the MSL altitude is higher than the ellipsoidal measurement.

## Core Geoid Functions in src/data/geoid.js

The [`src/data/geoid.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/geoid.js) module encapsulates the entire conversion workflow, from loading the EGM96 geoid-undulation grid to performing the mathematical correction.

### Initializing the Geoid Grid with ensureGeoidReady()

Before querying any undulation values, the system must load the bundled EGM96 geoid grid or establish a network proxy. The `ensureGeoidReady()` function returns a **Promise** that resolves when the grid is available.

```javascript
import { ensureGeoidReady } from './data/geoid.js';

// Load the geoid grid before performing conversions
await ensureGeoidReady();

```

Attempting to query heights before this Promise resolves results in thrown errors, ensuring the application never applies corrections with uninitialized data.

### Querying Undulation Values with geoidHeight()

Once initialized, the `geoidHeight(latDeg, lonDeg)` function returns the geoid undulation *N* in meters for any given coordinate pair.

```javascript
import { geoidHeight } from './data/geoid.js';

const latitude = 51.5;   // London
const longitude = -0.13;
const N = geoidHeight(latitude, longitude);  // Returns approximately -45 meters

```

This value represents the signed distance from the ellipsoid to the geoid at that specific location.

### Converting Heights with ellipsoidalToMslDisplayM()

The `ellipsoidalToMslDisplayM(hEllipsoidalM, geoidUndulationM)` function applies the correction algorithm. If a finite `geoidUndulationM` value is provided, it returns `hEllipsoidalM - geoidUndulationM`; otherwise, it returns the original ellipsoidal height unchanged to prevent erroneous corrections during grid failures.

```javascript
import { ellipsoidalToMslDisplayM } from './data/geoid.js';

const ellipsoidalAlt = 120;  // meters above WGS-84
const geoidN = -45;          // meters (geoid below ellipsoid)
const mslAlt = ellipsoidalToMslDisplayM(ellipsoidalAlt, geoidN);
// Result: 165 meters MSL

```

## Step-by-Step Implementation Examples

These practical examples demonstrate how to correct ellipsoidal altitude to MSL using geoid undulation in different scenarios.

### Basic Conversion Workflow

This pattern loads the grid, queries the local undulation, and converts the altitude:

```javascript
import { ensureGeoidReady, geoidHeight, ellipsoidalToMslDisplayM } from './data/geoid.js';

async function ellipsoidalToMsl(lat, lon, ellipsoidalAltM) {
  // Ensure EGM96 grid is available
  await ensureGeoidReady();
  
  // Get geoid undulation N at location
  const N = geoidHeight(lat, lon);
  
  // Apply correction
  return ellipsoidalToMslDisplayM(ellipsoidalAltM, N);
}

// Example: Houston, TX
(async () => {
  const lat = 29.72;
  const lon = -95.08;
  const ellipsoidalAltM = 1500;
  
  const mslAlt = await ellipsoidalToMsl(lat, lon, ellipsoidalAltM);
  console.log(`MSL altitude: ${mslAlt.toFixed(1)} m`);
})();

```

### Direct Conversion with Pre-Loaded Grid

When the grid is already initialized elsewhere in the application, you can query and convert synchronously:

```javascript
import { geoidHeight, ellipsoidalToMslDisplayM } from './data/geoid.js';

const lat = 51.5;
const lon = -0.13;
const ellipsoidalAlt = 120;

const N = geoidHeight(lat, lon);  // ≈ -45 m for London
const mslAlt = ellipsoidalToMslDisplayM(ellipsoidalAlt, N);
console.log(`Ellipsoidal: ${ellipsoidalAlt}m → MSL: ${mslAlt}m`);

```

### Handling Missing Geoid Data

The function safely handles cases where the geoid undulation cannot be determined, preserving the original ellipsoidal value rather than applying a null correction:

```javascript
import { ellipsoidalToMslDisplayM } from './data/geoid.js';

const ellipsoidalAlt = 800;
// Pass null or undefined when geoid data is unavailable
const mslAlt = ellipsoidalToMslDisplayM(ellipsoidalAlt, null);
console.log(mslAlt);  // Output: 800 (unchanged)

```

## Integration in the Heads-Up Display (src/hud.js)

The conversion logic appears in the user interface within [`src/hud.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/hud.js) (lines 339-340), where the real-time altitude read-out is assembled for aircraft visualization:

```javascript
const geoidN = this._geoidUndulationM(latDeg, lonDeg);
const altMslM = ellipsoidalToMslDisplayM(altM, geoidN);

```

This integration demonstrates the production pattern used throughout the application: query the undulation at the current position, then pass both values to the conversion function to display accurate MSL altitudes in the heads-up display.

## Summary

- **Geoid undulation (N)** represents the vertical distance between the WGS-84 ellipsoid and the geoid at a specific location, essential for converting GPS ellipsoidal heights to MSL altitudes.
- The [`src/data/geoid.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/geoid.js) module provides three key functions: `ensureGeoidReady()` for grid initialization, `geoidHeight()` for querying N values, and `ellipsoidalToMslDisplayM()` for applying the correction.
- The conversion formula implemented is `MSL = Ellipsoidal - N`, with the codebase handling edge cases by returning original values when geoid data is unavailable.
- Production usage in [`src/hud.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/hud.js) shows the pattern scales to real-time tracking applications requiring immediate altitude corrections.

## Frequently Asked Questions

### What is the difference between ellipsoidal and MSL altitude?

**Ellipsoidal altitude** measures height above the WGS-84 mathematical ellipsoid used by GPS systems, while **MSL (Mean Sea Level) altitude** measures height above the geoid—the equipotential surface that best approximates global sea level. The difference between these two values at any coordinate is the **geoid undulation (N)**, which can range from +100 meters to -100 meters depending on local gravitational variations.

### Why does Gods-Eye-View use EGM96 for geoid undulation?

The repository utilizes the **EGM96 (Earth Gravitational Model 1996)** geoid-undulation grid because it provides a globally consistent, high-resolution reference surface that balances accuracy with reasonable file size for web deployment. According to the source code in [`src/data/geoid.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/geoid.js), this model is bundled with the application or retrieved via network proxy to ensure offline capability while maintaining sub-meter precision for most aviation and maritime applications.

### What happens if the geoid grid fails to load?

If the EGM96 grid fails to initialize, the `ellipsoidalToMslDisplayM()` function includes defensive logic that detects null or undefined undulation values and returns the original ellipsoidal altitude unchanged. This **fail-safe behavior** prevents the application from displaying dangerously incorrect altitudes (such as negative MSL values) when geoid data is unavailable, as seen in the fallback handling within the conversion utility.

### Can I use this conversion without loading the full geoid grid?

No, the `geoidHeight()` function explicitly requires the grid to be loaded via `ensureGeoidReady()` and will throw an error if called prematurely. However, once `ensureGeoidReady()` resolves successfully, subsequent calls to `geoidHeight()` operate synchronously without additional network requests, making the system efficient for real-time tracking scenarios where the grid is loaded once during application initialization.