Nadir Tracking (With Yaw Compensation)

This tutorial demonstrates how to generate a nadir tracking profile, and generate Yaw-Pitch-Roll (YPR) angles.

Setup

import math

import pandas as pd

import plotly.express as px

from ostk.mathematics.geometry.d3.transformation.rotation import EulerAngle

from ostk.physics import Environment
from ostk.physics.unit import Length
from ostk.physics.unit import Angle
from ostk.physics.time import Scale
from ostk.physics.time import Instant
from ostk.physics.time import Duration
from ostk.physics.time import Interval
from ostk.physics.time import DateTime
from ostk.physics.time import Time
from ostk.physics.coordinate import Frame
from ostk.physics.coordinate.spherical import LLA

from ostk.astrodynamics import Trajectory
from ostk.astrodynamics.trajectory import State
from ostk.astrodynamics.trajectory import Orbit
from ostk.astrodynamics.trajectory.model import Nadir
from ostk.astrodynamics.flight import Profile

Computation

Initialize an environment:

environment = Environment.default(True)
earth = environment.access_celestial_object_with_name("Earth")

Construct an orbit:

orbit = Orbit.sun_synchronous(
    epoch=Instant.date_time(DateTime(2024, 1, 1, 0, 0, 0), Scale.UTC),
    altitude=Length.kilometers(550.0),
    local_time_at_descending_node=Time(14, 30, 0),
    celestial_object=earth,
)

Define an interval of interest:

interval = Interval.closed(
    start_instant=Instant.date_time(DateTime(2024, 1, 1, 0, 0, 0), Scale.UTC),
    end_instant=Instant.date_time(DateTime(2024, 1, 1, 2, 0, 0), Scale.UTC),
)

Construct a Vehicle Velocity, Local Horizontal (VVLH) frame from the orbit:

vvlh_frame = orbit.get_orbital_frame(Orbit.FrameType.VVLH)

Generate a flight profile:

instants = interval.generate_grid(Duration.seconds(20.0))
nadir_trajectory = Trajectory(
    model=Nadir(
        orbit=orbit,
    )
)
uncompensated_profile = Profile.local_orbital_frame_pointing(
    orbit=orbit,
    orbital_frame_type=Orbit.FrameType.VVLH,
)
yaw_compensated_profile = Profile.custom_pointing(
    orbit=orbit,
    alignment_target=Profile.TrajectoryTarget.target_position(
        trajectory=nadir_trajectory,
        axis=Profile.Axis.Z,
    ),
    clocking_target=Profile.TrajectoryTarget.target_velocity(
        trajectory=nadir_trajectory,
        axis=Profile.Axis.X,
    ),
)

Calculate and plot the Yaw-Pitch-Roll (YPR) angles in the VVLH frame during the access:

def reduce_angle(angle: Angle):
    # Reduce angle to be within the range 0 to 2*pi
    reduced_angle = float(angle.in_radians()) % (2 * math.pi)

    # If angle is negative, add 2*pi to make it positive
    if reduced_angle < 0:
        reduced_angle += 2 * math.pi

    return Angle.radians(reduced_angle)


def calculate_ypr(state: State):
    state_vvlh = state.in_frame(vvlh_frame)

    q_B_VVLH = state_vvlh.get_attitude()

    ypr = EulerAngle.quaternion(q_B_VVLH, EulerAngle.AxisSequence.ZYX)

    phi = ypr.phi
    theta = ypr.theta
    psi = ypr.psi

    if phi.in_radians() > Angle.half_pi().in_radians():
        phi = reduce_angle(phi + Angle.pi())
        theta = reduce_angle(Angle.pi() - theta)
        psi = reduce_angle(psi + Angle.pi())

    if theta.in_radians() > Angle.pi().in_radians():
        theta = theta - Angle.two_pi()

    satellite_lla = LLA.cartesian(
        cartesian_coordinates=state.in_frame(Frame.ITRF())
        .get_position()
        .get_coordinates(),
        ellipsoid_equatorial_radius=earth.get_equatorial_radius(),
        ellipsoid_flattening=earth.get_flattening(),
    )

    ascending = state.get_velocity().get_coordinates()[2] > 0

    return (
        state.get_instant().get_date_time(Scale.UTC),
        float(satellite_lla.get_longitude().in_degrees()),
        float(satellite_lla.get_latitude().in_degrees()),
        float(satellite_lla.get_altitude().in_meters()),
        float(phi.in_degrees()) if not ascending else float(phi.in_degrees() - 360.0),
        float(theta.in_degrees()),
        float(psi.in_degrees()),
        ascending
    )
yprs = pd.DataFrame(
    data=[
        calculate_ypr(state)
        for state in yaw_compensated_profile.get_states_at(
            instants=instants,
        )
    ],
    columns=[
        "Timestamp",
        "Longitude [deg]",
        "Latitude [deg]",
        "Altitude [m]",
        "Yaw [deg]",
        "Pitch [deg]",
        "Roll [deg]",
        "Ascending"
    ],
)

px.scatter(
    yprs,
    x="Latitude [deg]",
    y="Yaw [deg]",
    title="Yaw Evolution by Latitude",
    color="Ascending",
)