2. Eotvos correction#

The Eotvos correction is a necessary correction to apply for any gravity surveys conducted on a moving platform, such as a ship or airplane. The correction accounts for the relative motion between the vehicle and the Earth’s surface, which generates an additional centrifugal force known as the Eotvos effect. We offer several methods of computing the correction, which we show here, generally from simpliest to most complex.

[1]:
# %load_ext autoreload
# %autoreload 2

import pandas as pd
import plotly.io as pio
import verde as vd

import airbornegeo

pio.renderers.default = "notebook"
/home/sungw937/airbornegeo/.pixi/envs/default/lib/python3.14/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm

2.1. Load data#

This is a subset of the BAS AGAP survey over Antarctica’s Gamburtsev Subglacial Mountains. The file is downloaded and subset in the notebook AGAP_gravity_survey. It has a pre-computed Eotvos correction, which we will compare our computed values to.

[2]:
data_df = pd.read_csv("data/AGAP_gravity_survey.csv")
print(data_df.columns)
data_df.head()
Index(['Lon', 'Lat', 'Height_WGS1984', 'Date', 'Time', 'ST', 'CC', 'RB',
       'XACC', 'LACC', 'Still', 'Base', 'ST_real', 'Beam_vel', 'rec_grav',
       'Abs_grav', 'VaccCor', 'EotvosCor', 'LatCor', 'FaCor', 'HaccCor',
       'Free_air', 'FAA_filt', 'FAA_clip', 'Level_cor', 'FAA_level',
       'Fa_4600m', 'easting', 'northing', 'line_name', 'line', 'unixtime'],
      dtype='str')
[2]:
Lon Lat Height_WGS1984 Date Time ST CC RB XACC LACC ... FAA_filt FAA_clip Level_cor FAA_level Fa_4600m easting northing line_name line unixtime
0 77.252450 -80.583923 4156.1 2008-12-17 0 days 09:42:48 11934.47 2.61 -659.0 -49.0 273.0 ... 49.38 49.38 7.03 42.4 40.8 1.000024e+06 226237.330771 11_DA500 1 1.229507e+09
1 77.252672 -80.583377 4156.0 2008-12-17 0 days 09:42:49 11934.47 2.72 -368.6 -321.0 230.0 ... 49.45 49.45 7.04 42.4 40.8 1.000083e+06 226246.631269 11_DA500 1 1.229507e+09
2 77.252901 -80.582831 4156.1 2008-12-17 0 days 09:42:50 11888.95 -2.08 703.1 433.0 146.0 ... 49.52 49.52 7.04 42.5 40.9 1.000142e+06 226255.809132 11_DA500 1 1.229507e+09
3 77.253131 -80.582285 4156.4 2008-12-17 0 days 09:42:51 11888.95 0.50 625.1 566.0 223.0 ... 49.58 49.58 7.03 42.5 40.9 1.000201e+06 226264.969079 11_DA500 1 1.229507e+09
4 77.253358 -80.581740 4156.6 2008-12-17 0 days 09:42:52 11888.95 -1.73 575.1 108.0 205.0 ... 49.65 49.65 7.04 42.6 41.0 1.000260e+06 226274.156809 11_DA500 1 1.229507e+09

5 rows × 32 columns

[3]:
data_df["distance_along_line"] = airbornegeo.along_track_distance(
    data_df,
    groupby_column="line",
)
[4]:
# get only the raw columns
# we will perform the corrections ourselves and compare to their values
data_df = data_df[
    [
        "EotvosCor",
        "Lon",
        "Lat",
        "Height_WGS1984",
        "easting",
        "northing",
        "unixtime",
        "line",
        "distance_along_line",
    ]
]
data_df.head()
[4]:
EotvosCor Lon Lat Height_WGS1984 easting northing unixtime line distance_along_line
0 68.15 77.252450 -80.583923 4156.1 1.000024e+06 226237.330771 1.229507e+09 1 0.000000
1 68.37 77.252672 -80.583377 4156.0 1.000083e+06 226246.631269 1.229507e+09 1 59.842447
2 68.37 77.252901 -80.582831 4156.1 1.000142e+06 226255.809132 1.229507e+09 1 119.693401
3 68.16 77.253131 -80.582285 4156.4 1.000201e+06 226264.969079 1.229507e+09 1 179.545645
4 68.12 77.253358 -80.581740 4156.6 1.000260e+06 226274.156809 1.229507e+09 1 239.285174

2.2. Eotvos correction following Glicken 1962#

The most simple method of calculating the Eotvos correction is the Glicken 1962 formula, which depends on only the latitudes, the track angle, and the ground speed. This approximation ignores the effects of aircraft altitude, and the flattening of the ellipsoid. It does this by assuming heights are neglible compared to the radius of the Earth, and the change in Earth radius with latitude (flatenning) is also negligible, so the mean radius of the Earth is used. This results in a simple formula of

E = 7.503 V \cos\phi \sin\tau + 0.00415V^2

where \(V\) is the ground speed of the aircraft, \(\phi\) is the geodetic latitude, and \(\tau\) is the track angle.

The track angle is similar to the aircraft’s heading. The heading is the angle the nose of the plane points, which due to wind, can differ from the angle the path of the plane makes. This is in degrees clockwise from geographic (true) north.

By setting ellipsoid=True in the below function, we calculate the track using the WGS84 ellipsoid, instead of using a simplified spheric model of the Earth. It is more accurate, but a bit slower to compute.

[5]:
data_df["track"] = airbornegeo.track(
    data_df,
    latitude_column="Lat",
    longitude_column="Lon",
    groupby_column="line",
    ellipsoid=False,  # setting this to False is faster but less accurate
)
data_df.head()
Segments: 100%|██████████| 100/100 [00:00<00:00, 1707.48it/s]
[5]:
EotvosCor Lon Lat Height_WGS1984 easting northing unixtime line distance_along_line track
0 68.15 77.252450 -80.583923 4156.1 1.000024e+06 226237.330771 1.229507e+09 1 0.000000 3.925778
1 68.37 77.252672 -80.583377 4156.0 1.000083e+06 226246.631269 1.229507e+09 1 59.842447 3.805916
2 68.37 77.252901 -80.582831 4156.1 1.000142e+06 226255.809132 1.229507e+09 1 119.693401 3.925778
3 68.16 77.253131 -80.582285 4156.4 1.000201e+06 226264.969079 1.229507e+09 1 179.545645 3.943093
4 68.12 77.253358 -80.581740 4156.6 1.000260e+06 226274.156809 1.229507e+09 1 239.285174 3.899163
[6]:
ax = data_df.plot.scatter(
    "easting",
    "northing",
    c="track",
    s=0.2,
)
ax.set_aspect("equal")
_images/eotvos_correction_9_0.png

The Glicken formulation also requiresthe ground speed of the aircraft. The below function ground_speed calculates the relative distance between successive rows in the dataframe. This assumes the dataframe is sorted by line, and then by time. From the relative distance, it then calculate the ground speed of the aircraft.

[7]:
data_df["ground_speed"] = airbornegeo.ground_speed(
    data_df,
    easting_column="easting",
    northing_column="northing",
    time_column="unixtime",
    groupby_column="line",
)
data_df.head()
Segments: 100%|██████████| 100/100 [00:00<00:00, 6647.60it/s]
Segments: 100%|██████████| 100/100 [00:00<00:00, 2661.61it/s]
[7]:
EotvosCor Lon Lat Height_WGS1984 easting northing unixtime line distance_along_line track ground_speed
0 68.15 77.252450 -80.583923 4156.1 1.000024e+06 226237.330771 1.229507e+09 1 0.000000 3.925778 59.842447
1 68.37 77.252672 -80.583377 4156.0 1.000083e+06 226246.631269 1.229507e+09 1 59.842447 3.805916 59.846701
2 68.37 77.252901 -80.582831 4156.1 1.000142e+06 226255.809132 1.229507e+09 1 119.693401 3.925778 59.851599
3 68.16 77.253131 -80.582285 4156.4 1.000201e+06 226264.969079 1.229507e+09 1 179.545645 3.943093 59.795886
4 68.12 77.253358 -80.581740 4156.6 1.000260e+06 226274.156809 1.229507e+09 1 239.285174 3.899163 59.740782
[8]:
ax = data_df.plot.scatter(
    "easting",
    "northing",
    c="ground_speed",
    s=0.2,
)
ax.set_aspect("equal")
_images/eotvos_correction_12_0.png
[9]:
data_df["eotvos_correction_glicken"] = airbornegeo.eotvos_correction_glicken(
    data_df.Lat.values,
    data_df.track.values,
    data_df.ground_speed.values,
)
data_df.head()
[9]:
EotvosCor Lon Lat Height_WGS1984 easting northing unixtime line distance_along_line track ground_speed eotvos_correction_glicken
0 68.15 77.252450 -80.583923 4156.1 1.000024e+06 226237.330771 1.229507e+09 1 0.000000 3.925778 59.842447 65.984990
1 68.37 77.252672 -80.583377 4156.0 1.000083e+06 226246.631269 1.229507e+09 1 59.842447 3.805916 59.846701 65.696167
2 68.37 77.252901 -80.582831 4156.1 1.000142e+06 226255.809132 1.229507e+09 1 119.693401 3.925778 59.851599 66.004804
3 68.16 77.253131 -80.582285 4156.4 1.000201e+06 226264.969079 1.229507e+09 1 179.545645 3.943093 59.795886 65.934659
4 68.12 77.253358 -80.581740 4156.6 1.000260e+06 226274.156809 1.229507e+09 1 239.285174 3.899163 59.740782 65.713724
[10]:
ax = data_df.plot.scatter(
    "easting",
    "northing",
    c="eotvos_correction_glicken",
    s=0.2,
)
ax.set_aspect("equal")
_images/eotvos_correction_14_0.png
[11]:
df = data_df[data_df.line == 4]
ylim = vd.minmax(df.EotvosCor)
ax = df.plot.line(
    "distance_along_line",
    "EotvosCor",
    style="bp",
    ms=0.6,
    label="Published values",
    title=f"Line {df.line.unique()[0]}",
    ylim=ylim,
)
ax = df.plot.line(
    "distance_along_line",
    "eotvos_correction_glicken",
    style="rp",
    ms=0.6,
    title=f"Line {df.line.unique()[0]}",
    label="Calculated values",
    ax=ax,
    ylim=ylim,
)
ax.set_ylabel("Eotvos correction (mGal)")
[11]:
Text(0, 0.5, 'Eotvos correction (mGal)')
_images/eotvos_correction_15_1.png

2.3. Eotvos correction following Harlan 1968#

Another common method to compute the correction is from Harlan 1968. The paper offers several equations, but first we will follow equation 15.

2.3.1. Equation 15:#

This formulation requires the aircraft latitude, longtide, time (to calculate time derivatives) and height.

E = \frac{V_N^2}{a} [ 1 + \frac{h}{a} + f (2-3\sin^{2}\phi) ] + \frac{V_E^2}{a} [ 1 + \frac{h}{a} - f \sin^{2}\phi) ] + 2 V_E \omega \cos \phi (1 + \frac{h}{a})

where \(V_N\), \(V_E\), \(h\), and \(\phi\), $ are the aircraft’s eastward and northward velocities, height, and geodetic latitude, and \(a\), \(f\), and \(\omega\) are the ellipsoid’s semimajor axis, flattening, and rotation rate.

V_N = r' \dot{\phi_{c}} sec(D)
V_E = r' \dot{l} cos(\phi_{c})

where \(r'\) is the ellipsoid’s geocentric radius at the latitude of the aircraft, \(l\) is the aircraft’s longitude, \(\phi_{c}\) is the geocentric radius of the aircraft, \(\dot{\phi_{c}}\) is the first time derivative of the geocentric radius of the aircraft, and \(D\) is the deviation between the aircraft’s geodetic latitude and geocentric latitude.

Compared to the Glicken formulation, it is more exact since it accounts for aircraft height as well as the flattening of the ellipsoid. However it still makes a few assumptions which simplify the calculations. These include a simplification for the deviation between geocentric and geodetic latitudes, and thus the time derivatives of this deviation, as well as a simplification of the geocentric radius, and thus the time derivatives of the geocentric radius.

[33]:
data_df["eotvos_correction_harlan"] = airbornegeo.eotvos_correction_harlan(
    data_df.Lat.values,
    data_df.Lon.values,
    data_df.unixtime.values,
    data_df.Height_WGS1984.values,
)
data_df.head()
[33]:
EotvosCor Lon Lat Height_WGS1984 easting northing unixtime line distance_along_line track ground_speed eotvos_correction_glicken eotvos_correction_harlan velocity_longitudinal velocity_latitudinal eotvos_correction_harlan_velocity eotvos_correction_harlan_at_altitude dif
0 68.15 77.252450 -80.583923 4156.1 1.000024e+06 226237.330771 1.229507e+09 1 0.000000 3.925778 59.842447 65.984990 68.079899 0.000222 0.000546 68.079384 68.079384 0.000515
1 68.37 77.252672 -80.583377 4156.0 1.000083e+06 226246.631269 1.229507e+09 1 59.842447 3.805916 59.846701 65.696167 68.241925 0.000225 0.000546 68.241409 68.241409 0.000516
2 68.37 77.252901 -80.582831 4156.1 1.000142e+06 226255.809132 1.229507e+09 1 119.693401 3.925778 59.851599 66.004804 68.427131 0.000229 0.000546 68.426614 68.426614 0.000516
3 68.16 77.253131 -80.582285 4156.4 1.000201e+06 226264.969079 1.229507e+09 1 179.545645 3.943093 59.795886 65.934659 68.275851 0.000229 0.000546 68.275336 68.275336 0.000515
4 68.12 77.253358 -80.581740 4156.6 1.000260e+06 226274.156809 1.229507e+09 1 239.285174 3.899163 59.740782 65.713724 68.147679 0.000228 0.000545 68.147164 68.147164 0.000514
[34]:
ax = data_df.plot.scatter(
    "easting",
    "northing",
    c="eotvos_correction_harlan",
    s=0.2,
)
ax.set_aspect("equal")
_images/eotvos_correction_18_0.png
[35]:
df = data_df[data_df.line == 4]
ylim = vd.minmax(df.EotvosCor)
ax = df.plot.line(
    "distance_along_line",
    "EotvosCor",
    style="bp",
    ms=0.6,
    label="Published values",
    title=f"Line {df.line.unique()[0]}",
    ylim=ylim,
)
ax = df.plot.line(
    "distance_along_line",
    "eotvos_correction_harlan",
    style="rp",
    ms=0.6,
    title=f"Line {df.line.unique()[0]}",
    label="Calculated values",
    ax=ax,
    ylim=ylim,
)
ax.set_ylabel("Eotvos correction (mGal)")
[35]:
Text(0, 0.5, 'Eotvos correction (mGal)')
_images/eotvos_correction_19_1.png