Skip to content

File AtmosphericForce.cpp

File List > astrea > astro > astro > propagation > force_models > AtmosphericForce.cpp

Go to the documentation of this file

/*
 * The GNU Lesser General Public License (LGPL)
 *
 * Copyright (c) 2025 Jay Iuliano
 *
 * This file is part of Astrea.
 * Astrea is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 * Astrea is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should
 * have received a copy of the GNU General Public License along with Astrea. If not, see <https://www.gnu.org/licenses/>.
 */

#include <astro/propagation/force_models/AtmosphericForce.hpp>

// mp-units
#include <mp-units/math.h>
#include <mp-units/systems/angular.h>
#include <mp-units/systems/angular/math.h>
#include <mp-units/systems/isq_angle.h>
#include <mp-units/systems/si.h>
#include <mp-units/systems/si/math.h>

#include <astro/frames/CartesianVector.hpp>
#include <astro/frames/frames.hpp>
#include <astro/platforms/Vehicle.hpp>
#include <astro/state/State.hpp>
#include <astro/state/angular_elements/angular_elements.hpp>
#include <astro/state/orbital_elements/OrbitalElements.hpp>
#include <astro/state/orbital_elements/instances/Keplerian.hpp>
#include <astro/systems/AstrodynamicsSystem.hpp>
#include <astro/time/Date.hpp>
#include <astro/utilities/conversions.hpp>

namespace astrea {
namespace astro {

using namespace mp_units;
using mp_units::pow;
using mp_units::angular::atan2;
using mp_units::angular::sin;
using mp_units::angular::unit_symbols::deg;
using mp_units::angular::unit_symbols::rad;
using mp_units::si::unit_symbols::cm;
using mp_units::si::unit_symbols::g;
using mp_units::si::unit_symbols::kg;
using mp_units::si::unit_symbols::km;
using mp_units::si::unit_symbols::m;
using mp_units::si::unit_symbols::s;


AccelerationVector<frames::earth::icrf> AtmosphericForce::compute_force(const State& state, const Vehicle& vehicle) const
{
    // Extract
    const AstrodynamicsSystem& sys       = state.get_system();
    const CelestialBodyUniquePtr& center = sys.get_central_body();
    const AngularRate& bodyRotationRate  = center->get_rotation_rate();

    const RadiusVector<frames::earth::icrf>& r   = state.get_position();
    const VelocityVector<frames::earth::icrf>& v = state.get_velocity();

    const Distance& x = r.get_x();
    const Distance& y = r.get_y();
    const Distance R  = r.norm();

    const Velocity& vx = v.get_x();
    const Velocity& vy = v.get_y();
    const Velocity& vz = v.get_z();

    // Find velocity relative to atmosphere
    const VelocityVector<frames::earth::icrf> relVelocity = { vx + y * bodyRotationRate.in(rad / s) / (isq_angle::cotes_angle),
                                                              vy - x * bodyRotationRate.in(rad / s) / (isq_angle::cotes_angle),
                                                              vz };

    // Exponential Drag Model
    const Density atmosphericDensity = find_atmospheric_density(state, center);

    // Accel due to drag
    const Velocity relVelMag         = relVelocity.norm();
    const Unitless coefficientOfDrag = vehicle.get_coefficient_of_drag();
    const SurfaceArea areaRam        = vehicle.get_ram_area();
    const Mass mass                  = vehicle.get_mass();
    const Acceleration dragAccelMag = -0.5 * coefficientOfDrag * (areaRam) / mass * atmosphericDensity * pow<2>(relVelMag);

    const AccelerationVector<frames::earth::icrf> accelDrag = dragAccelMag * (relVelocity / relVelMag);

    // accel due to lift
    const Angle angleOfAttack        = atan2(relVelocity.get_z(), relVelocity.get_x());
    const Unitless coefficientOfLift = vehicle.get_coefficient_of_lift();
    const SurfaceArea areaLift       = vehicle.get_lift_area();
    const Acceleration liftAccelMag =
        0.5 * coefficientOfLift * areaLift / mass * atmosphericDensity * pow<2>(relVelMag) * sin(angleOfAttack);
    const AccelerationVector<frames::earth::icrf> accelLift = liftAccelMag * (r / R); // just assume radial lift for now

    return { accelDrag[0] + accelLift[0], accelDrag[1] + accelLift[1], accelDrag[2] + accelLift[2] };
}


const Density AtmosphericForce::find_atmospheric_density(const State& state, const CelestialBodyUniquePtr& center) const
{
    // Find altitude
    const RadiusVector<frames::earth::earth_fixed> rEcef = state.get_position_in_frame<frames::earth::earth_fixed>();
    const auto [latitude, longitude, altitude] =
        convert_earth_fixed_to_geodetic(rEcef, center->get_equitorial_radius(), center->get_polar_radius());

    return center->find_atmospheric_density(state.get_epoch(), altitude);
}


} // namespace astro
} // namespace astrea