Geoid height

Back to Geocentric coordinates. Forward to Utility Programs. Up to Contents.

The gravitational equipotential surface approximately coinciding with mean sea level is called the geoid. The GeographicLib::Geoid class evaluates the height of the geoid above the WGS84 ellipsoid. This can be used to convert heights above mean sea level to heights above the WGS84 ellipoid. Because the normal to the ellipsoid differs from the normal to the geoid (the direction of a plumb line) there is a slight ambiguity in the measurement of heights; however for heights up to 10km this ambiguity is only 1mm.

The geoid is usually approximated by an "earth gravity model". The models published by the NGA are:

GeographicLib::Geoid offers a uniform way to handle all 3 geoids at a variety of grid resolutions. (In contrast, the software tools that NGA offers are different for each geoid, and the interpolation programs are different for each grid resolution. In addition these tools are written in Fortran with is nowadays difficult to integrate with other software.)

Unlike other components of GeographicLib, there is a appreciable error in the results obtained (at best, the RMS error is 1mm). However the class provides methods to report the maximum and RMS errors in the results.

The class also returns the gradient of the geoid. This can be used to estimate the direction of a plumb line relative to the WGS84 ellipsoid.

Installing the datasets

The geoid heights are computed using interpolation into a rectangular grid. The grids are read from data files which have been are computed using the NGA synthesis programs in the case of the EGM84 and EGM96 models and using the NGA binary gridded data files in the case of EGM2008. These data files are available for download:

Download either the tar.bz2 file or zip file (they have the same contents). If possible use the tar.bz2 format, since bzip2 compresses these files about 2 times better than zip. The data in tar and zip files both unpack into a "geoids" directory and, for convenience, all the data should be unpacked in the same parent directory. For example

   mkdir -p /usr/local/share/GeographicLib
   tar xfjC egm96-5.tar.bz2 /usr/local/share/GeographicLib
   tar xfjC egm2008-2_5.tar.bz2 /usr/local/share/GeographicLib
   etc.

or

   mkdir -p /usr/local/share/GeographicLib
   cd /usr/local/share/GeographicLib
   unzip ~-/egm96-5.zip
   unzip ~-/egm2008-2_5.zip
   etc.

Geoid uses a compile time default (specified by the GEOID_DEFAULT_PATH macro) to locate the datasets. This is

This may be overridden at run-time by defining the GEOID_PATH environment variable. Finally, the path may be set using the optional second argument to the GeographicLib::Geoid::Geoid constructor.

The sizes of these datasets are

   name         geoid    grid           sizes (MB)
                                 tar.bz2    zip    disk
   egm84-30     EGM84    30'      0.5       0.5     0.5
   egm84-15     EGM84    15'      1.5       1.9     2.0
   egm96-15     EGM96    15'      1.5       1.9     2.0
   egm96-5      EGM96     5'      9.8        16      18
   egm2008-5    EGM2008   5'       10        16      18
   egm2008-2_5  EGM2008   2.5'     34        62      72
   egm2008-1    EGM2008   1'      155       372     445

The final "disk" column is the size of the uncompressed data and it also gives the memory requirements for caching the entire dataset using the GeographicLib::Geoid::CacheAll method.

The format of the geoid data files

The gridded data used by the GeographicLib::Geoid class is stored in 16-bit PGM files. Thus the data for egm96-5 might be stored in the file

PGM simple graphic format with the following properties

The major drawback of this format is that since there are only 65535 possible pixel values, the height must be quantized to 3mm. However, the resulting quantization error (up to 1.5mm) is typically smaller than the linear interpolation errors. The comments in the header for egm96-5 are

   # Geoid file in PGM format for the GeographicLib::Geoid class
   # Description WGS84 EGM96, 5-minute grid
   # URL http://earth-info.nga.mil/GandG/wgs84/gravitymod/egm96/egm96.html
   # DateTime 2009-08-29 18:45:03
   # MaxBilinearError 0.140
   # RMSBilinearError 0.005
   # MaxCubicError 0.003
   # RMSCubicError 0.001
   # Offset -108
   # Scale 0.003
   # Origin 90N 0E
   # AREA_OR_POINT Point
   # Vertical_Datum WGS84

Of these lines, the Scale and Offset lines are required and define the conversion from pixel value to height (in meters) using height = offset + scale pixel. The Geoid constructor also reads the Description, DataTime, and error lines (if present) and stores the resulting data so that it can be returned by GeographicLib::Geoid::Description, GeographicLib::Geoid::DateTime, GeographicLib::Geoid::MaxError, and GeographicLib::Geoid::RMSError methods. The other lines serve as additional documentation but are not used by this class. Accompanying egm96-5.pgm (and similarly with the other geoid data files) are two files egm96-5.wld and egm96-5.pgm.aux.xml. The first is an ESRI "world" file and the second supplies complete projection metadata for use by GDAL. Neither of these files is read by GeographicLib::Geoid.

You can use gdal_translate to convert the data files to a standard GeoTiff, e.g., with

   gdal_translate -ot Float32 -scale 0 65000 -108 87 egm96-5.pgm egm96-5.tif

The arguments to -scale here are specific to the Offset and Scale parameters used in the pgm file (note 65000 * 0.003 - 108 = 87). You can check these by running GeoidEval with the "-v" option.

Finally, here is a sample script which uses GDAL to create a 1-degree squared grid of geoid heights at 3" resolution (matching DTED1) by bilinear interpolation.

   #! /bin/sh
   lat=37
   lon=067
   res=3                           # resolution in seconds
   TEMP=`mktemp junkXXXXXXXXXX`    # temporary file for GDAL
   gdalwarp -q -te `echo $lon $lat $res | awk '{
       lon = $1; lat = $2; res = $3;
       printf "%.14f %.14f %.14f %.14f",
           lon  -0.5*res/3600, lat  -0.5*res/3600,
           lon+1+0.5*res/3600, lat+1+0.5*res/3600;
   }'` -ts $((3600/res+1)) $((3600/res+1)) -r bilinear egm96-5.tif $TEMP
   gdal_translate -quiet \
       -mo AREA_OR_POINT=Point \
       -mo Description="WGS84 EGM96, $res-second grid" \
       -mo Vertical_Datum=WGS84 \
       -mo Tie_Point_Location=pixel_corner \
       $TEMP e$lon-n$lat.tif
   rm -f $TEMP

Interpolating the geoid data

GeographicLib::Geoid evaluates the geoid height is by bilinear or cubic interpolation. The gradient of the geoid height is obtained by differentiating the interpolated height and referencing the result to distance on the WGS84 ellipsoid.

The bilinear interpolation is based on the values at the 4 corners of the enclosing cell. The interpolated height is a continuous function of position; however the gradient has discontinuities are cell boundaries. The quantization of the data files exacerbates the errors in the gradients.

The cubic interpolation is a least-squares fit to the values on a 12-point stencil with weights as follows:

   . 1 1 .
   1 2 2 1
   1 2 2 1
   . 1 1 .

The cubic is constrained to be independent of longitude when evaluating the height at one of the poles. Cubic interpolation is considerably more accurate than bilinear interpolation; however, in this implementation there are small discontinuities in the heights are cell boundaries. The over-constrained cubic fit slightly reduces the quantization errors on average.

The algorithm for the least squares fit is taken from, F. H. Lesh, Multi-dimensional least-squares polynomial curve fitting, CACM 2, 29-30 (1959). This algorithm is not part of Geographic::Geoid; instead it is implemented as Maxima code which is used to precompute the matrices to convert the function values on the stencil into the coefficients from the cubic polynomial. This code is included as a comment in Geoid.cpp.

The interpolations methods are quick and give good accuracy. Here is a summary of the combined quantization and interpolation errors for the heights.

                                  bilinear error    cubic error
   name         geoid    grid     max     rms       max     rms
   egm84-30     EGM84    30'      1.546m  70mm      0.274m  14mm
   egm84-15     EGM84    15'      0.413m  18mm      0.020m   1mm
   egm96-15     EGM96    15'      1.152m  40mm      0.169m   7mm
   egm96-5      EGM96     5'      0.140m   5mm      0.003m   1mm
   egm2008-5    EGM2008   5'      0.478m  12mm      0.294m   5mm
   egm2008-2_5  EGM2008   2.5'    0.135m   3mm      0.031m   1mm
   egm2008-1    EGM2008   1'      0.025m   1mm      0.003m   1mm

The errors are with respect the the specific NGA earth gravity model (not to any "real" geoid). The RMS values are obtained using 5 million uniformly distributed random points. The maximum values are obtained by evaluating the errors using a different grid with points at or near the centers of the original grid. (The RMS difference between EGM96 and EGM2008 is about 0.5m. The RMS difference between EGM84 and EGM96 is about 1.5m.)

Caching the geoid data

A simple way of calling Geoid is as follows

   #include "GeographicLib/Geoid.hpp"
   #include <iostream>
   ...
   GeographicLib::Geoid g("egm96-5");
   double lat, lon;
   while (std::cin >> lat >> lon)
      std::cout << g(lat, lon) << "\n";
   ...

The first call to g(lat, lon) causes the data for the stencil points (4 points for bilinear interpolation and 12 for cubic interpolation) to be read and the interpolated value returned. A simple 0th-order caching scheme is provided by default, so that, if the second and subsequent points falls within the same grid cell, the data values are not reread from the file. This is adequate for most interactive use and also minimizes disk accesses for the case when a continuous track is being followed.

If a large quantity of geoid calculations are needed, the calculation can be speeded up by preloading the data for a rectangular block with GeographicLib::Geoid::CacheArea or the entire dataset with GeographicLib::Geoid::CacheAll. If the requested points lie within the cached area, the cached data values are used; otherwise the data is read from disk as before. Caching all the data is a reasonable choice for the 5' grids and coarser. Caching all the data for the 1' grid will require 0.5GB of RAM and should only be used on systems with sufficient memory.

The use of caching does not affect the values returned. Because of the caching and the random file access, this class is not normally thread safe; i.e., a single instantiation cannot be safely used by multiple threads. If multiple threads need to calculate geoid heights, there are two alternatives:

Test data for geoids

A test set for the geoid models is available at

This is about 10 MB (compressed). This test set consists of a set of 500000 geographic coordinates together with the corresponding geoid heights according to various earth gravity models.

Each line of the test set gives 6 space delimited numbers

The latitude and longitude are all multiples of 10-6 deg and should be regarded as exact. The geoid heights are computed using the harmonic NGA synthesis programs. In the case of the EGM84 and EGM96, the programs were compiled (with gfortran) and run under Linux. In the case of EGM2008, the prescription results in NaNs (possibly because of uninitialized variables in the program?), so the data was generated with the precompiled binary that NGA supplies for Windows.

Back to Geocentric coordinates. Forward to Utility Programs. Up to Contents.