Orbit Interpolation

This library facilitates the interpolation of orbit ephemerides from a template file that contains satellite positions for a single orbit cycle. It supports the propagation of the orbit over time, making it useful for simulations and other applications that require orbit propagation.

To begin, we will load the orbit ephemerides from the template file. In this example, we will create a simple function to load the data from the test file.

import pathlib

import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

import pyinterp
import pyinterp.tests


def load_test_ephemeris(
    filename: pathlib.Path
) -> tuple[float, np.ndarray, np.ndarray, np.ndarray, np.timedelta64]:
    """Loads the ephemeris from a text file.

    Args:
        filename: Name of the file to be loaded.

    Returns:
        A tuple containing the height of the orbit, the ephemeris and the
        duration of the cycle.
    """
    with open(filename) as stream:
        lines = stream.readlines()

    def to_dict(comments) -> dict[str, float]:
        """Returns a dictionary describing the parameters of the orbit."""
        result = {}
        for item in comments:
            assert item.startswith('#'), 'Comments must start with #'
            key, value = item[1:].split('=')
            result[key.strip()] = float(value)
        return result

    # The two first lines are the header and contain the height and the
    # duration of the cycle in fractional days.
    settings = to_dict(lines[:2])
    del lines[:2]

    # The rest of the lines are the ephemeris
    ephemeris = np.loadtxt(lines,
                           delimiter=' ',
                           dtype={
                               'names':
                               ('time', 'longitude', 'latitude', 'height'),
                               'formats': ('f8', 'f8', 'f8', 'f8')
                           })

    return (
        settings['height'],
        ephemeris['longitude'],
        ephemeris['latitude'],
        ephemeris['time'].astype('timedelta64[s]'),
        np.timedelta64(int(settings['cycle_duration'] * 86400.0 * 1e9), 'ns'),
    )

Set the path to the test file

Compute the orbit properties from the provided ephemeris

orbit = pyinterp.calculate_orbit(*ephemeris)

The orbit object provides a method to calculate the number of passes per cycle

print(orbit.passes_per_cycle())
28

To get the cycle duration, and the orbit duration we can use the cycle_duration method and the orbit_duration method respectively.

print(orbit.cycle_duration().astype('m8[ms]').item())
print(orbit.orbit_duration().astype('m8[ms]').item())
23:25:23.172000
1:40:23.083000

We can also retrieve the pass duration for a given pass number:

print(orbit.pass_duration(2).astype('m8[ms]').item())
0:55:44.350000

A utility function is provided to compute an absolute pass number from a relative pass number and to decode the absolute pass number back into a relative pass number. This function is useful for storing the pass number in a database or indexing the passes in a file, among other applications.

absolute_pass_number = orbit.encode_absolute_pass_number(cycle_number=11,
                                                         pass_number=2)
print(absolute_pass_number)
cycle_number, pass_number = orbit.decode_absolute_pass_number(
    absolute_pass_number)
print(cycle_number, pass_number)
282
11 2

The online documentation provides more information about the available methods for the this object: Orbit.

The next step is to interpolate the orbit ephemerides over time to get the satellite positions for a given relative pass number.

Note

Is it possible to iterate over the relative pass numbers over time periods using the iterate method.

for cycle_number, pass_number, first_location_date in orbit.iterate(
        start_date, end_date):
    ...
nadir_pass_corrdinates = pyinterp.calculate_pass(2, orbit)
assert nadir_pass_corrdinates is not None
pd.DataFrame({
    'time': nadir_pass_corrdinates.time,
    'lon_nadir': nadir_pass_corrdinates.lon_nadir,
    'lat_nadir': nadir_pass_corrdinates.lat_nadir,
})
time lon_nadir lat_nadir
0 0 days 00:25:53.799409419 -29.393586 -77.497916
1 0 days 00:25:54.519992305 -29.310591 -77.497883
2 0 days 00:25:55.241146762 -29.227597 -77.497825
3 0 days 00:25:55.962772250 -29.144604 -77.497741
4 0 days 00:25:56.684960867 -29.061612 -77.497632
... ... ... ...
9953 0 days 01:21:25.850882766 146.412747 77.727877
9954 0 days 01:21:28.310619399 146.497271 77.727982
9955 0 days 01:21:30.770464201 146.581797 77.728059
9956 0 days 01:21:33.230367978 146.666324 77.728106
9957 0 days 01:21:35.690281545 146.750851 77.728124

9958 rows × 3 columns



print(nadir_pass_corrdinates.equator_coordinates)
EquatorCoordinates(longitude=59.20360095529902, time=np.timedelta64(3087382968771,'ns'))

The variable nadir_pass_corrdinates contains the satellite positions for the given pass: - The nadir longitude in degrees - The nadir latitude in degrees - The time for each position in a numpy.datetime64 array - The along track distance in meters - And the coordinates of the satellite at the equator.

Note

The variable nadir_pass_corrdinates could be None if the pass number is outside the bounding box defined during the instantiation of the Orbit object.

See the online documentation for more information about the available methods for the Pass object.

Finally, we can calculate the satellite positions for a given pass number over a swath.

assert nadir_pass_corrdinates is not None
swath = pyinterp.calculate_swath(nadir_pass_corrdinates)

The swath object contains the properties of the pass, similar to the previous example. Additionally, it includes the coordinates of the satellite over the swath for each location on the nadir track.

pd.DataFrame({
    'time': swath.time,
    'lon_nadir': swath.lon_nadir,
    'lat_nadir': swath.lat_nadir,
})
time lon_nadir lat_nadir
0 0 days 00:25:53.799409419 -29.393586 -77.497916
1 0 days 00:25:54.519992305 -29.310591 -77.497883
2 0 days 00:25:55.241146762 -29.227597 -77.497825
3 0 days 00:25:55.962772250 -29.144604 -77.497741
4 0 days 00:25:56.684960867 -29.061612 -77.497632
... ... ... ...
9953 0 days 01:21:25.850882766 146.412747 77.727877
9954 0 days 01:21:28.310619399 146.497271 77.727982
9955 0 days 01:21:30.770464201 146.581797 77.728059
9956 0 days 01:21:33.230367978 146.666324 77.728106
9957 0 days 01:21:35.690281545 146.750851 77.728124

9958 rows × 3 columns



The DataFrame df shows the longitude and latitude coordinates of the satellite for the first two lines of the swath. The index x_ac represents the across-track distance in meters.

df = pd.DataFrame(
    {
        'lon_0': swath.lon[0, :],
        'lon_1': swath.lon[1, :],
        'lat_0': swath.lat[0, :],
        'lat_1': swath.lat[1, :],
    },
    index=swath.x_ac[0, :],
)
df.index.name = 'x_ac'
df
lon_0 lon_1 lat_0 lat_1
x_ac
-70000.0 -29.396648 -29.317550 -76.870888 -76.870857
-68000.0 -29.396565 -29.317360 -76.888803 -76.888772
-66000.0 -29.396481 -29.317170 -76.906719 -76.906688
-64000.0 -29.396397 -29.316979 -76.924634 -76.924603
-62000.0 -29.396312 -29.316788 -76.942550 -76.942519
... ... ... ... ...
62000.0 -29.390618 -29.303828 -78.053251 -78.053217
64000.0 -29.390518 -29.303599 -78.071164 -78.071130
66000.0 -29.390417 -29.303370 -78.089078 -78.089044
68000.0 -29.390317 -29.303140 -78.106991 -78.106957
70000.0 -29.390216 -29.302910 -78.124905 -78.124871

70 rows × 4 columns



We can plot the satellite positions over the swath using the following code:

fig, (ax1, ax2) = plt.subplots(1,
                               2,
                               figsize=(20, 10),
                               subplot_kw={'projection': ccrs.PlateCarree()})

# Zoomed plot
ax1.set_extent([58, 70, 12, 32], crs=ccrs.PlateCarree())
ax1.plot(swath.lon[::4, ::4].ravel(),
         swath.lat[::4, ::4].ravel(),
         'b.',
         markersize=1)
ax1.plot(swath.lon_nadir, swath.lat_nadir, 'r.', markersize=0.5)
ax1.set_title('Satellite positions - Zoomed')
ax1.coastlines()
ax1.gridlines(draw_labels=True)

# Full swath plot
ax2.plot(swath.lon.ravel(), swath.lat.ravel(), 'b.', markersize=1)
ax2.plot(swath.lon_nadir, swath.lat_nadir, 'r.', markersize=0.5)
ax2.set_title('Satellite positions - Full Swath')
ax2.coastlines()
ax2.gridlines(draw_labels=True)
ax1.set_aspect('auto')
ax2.set_aspect('auto')

plt.show()
Satellite positions - Zoomed, Satellite positions - Full Swath

Total running time of the script: (0 minutes 0.885 seconds)

Gallery generated by Sphinx-Gallery