Program Listing for File orbit_utils.cc¶
↰ Return to documentation for file (environment/plasma/env/orbit_utils.cc)
#include "lupnt/environment/plasma/env/orbit_utils.h"
#include <cctype>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include "lupnt/environment/plasma/core/string_file_utils.h"
#include "lupnt/environment/plasma/core/user_filepath.h"
#include "lupnt/environment/plasma/env/time_utils.h"
#include "lupnt/environment/plasma/gcpm/constants_gcpm.h"
namespace pecsim {
double wrap2pi(double angle) {
while (angle < 0.0) {
angle += 2 * M_PI;
}
while (angle >= 2 * M_PI) {
angle -= 2 * M_PI;
}
return angle;
}
Mat3d RotXd(double angle) {
double c = cos(angle);
double s = sin(angle);
Mat3d R{
{1.0, 0.0, 0.0},
{0.0, c, s},
{0.0, -s, c},
};
return R;
}
Mat3d RotYd(double angle) {
double c = cos(angle);
double s = sin(angle);
Mat3d R{
{c, 0.0, -s},
{0.0, 1.0, 0.0},
{s, 0.0, c},
};
return R;
}
Mat3d RotZd(double angle) {
double c = cos(angle);
double s = sin(angle);
Mat3d R{
{c, s, 0.0},
{-s, c, 0.0},
{0.0, 0.0, 1.0},
};
return R;
}
/******************************************************************************
* Conversion between different orbital anomalies
* Eccentric anomaly (E), true anomaly (nu), and mean anomaly (M)
* ***************************************************************************/
double ecc2true(double E, double e) { return atan2(sqrt(1 - pow(e, 2)) * sin(E), cos(E) - e); }
double ecc2mean(double E, double e) { return wrap2pi(E - e * sin(E)); }
double mean2ecc(double M, double e) {
double MM = wrap2pi(M);
double EPS = 1e-10; // Convergence criterion
// Initial estimate of E
double E = MM;
double Eest = E - (E - e * sin(E) - MM) / (1.0 - e * cos(E));
int max_itr = 100;
int itr = 0;
while ((abs(Eest - E) >= EPS) && (itr <= max_itr)) {
E = Eest;
Eest = E - (E - e * sin(E) - M) / (1.0 - e * cos(E));
itr++;
}
E = Eest;
return wrap2pi(E);
}
double mean2true(double M, double e) {
double E = mean2ecc(M, e);
double nu = ecc2true(E, e);
return wrap2pi(nu);
}
double true2ecc(double nu, double e) {
double E = 2 * atan(sqrt((1 - e) / (1 + e)) * tan(nu / 2));
return E;
}
double true2mean(double nu, double e) {
double E = true2ecc(nu, e);
double M = ecc2mean(E, e);
return M;
}
/******************************************************************************
* Conversion between classical orbital elements (COE) and Cartesian coordinates
* ***************************************************************************/
Vec6d coe2cart(const Vec6d &coe, double GM) {
double a = coe[0]; // Semi-major axis
double e = coe[1]; // eccentricity
double i = coe[2]; // Inclination
double Omega = coe[3]; // Right ascension of ascending node
double omega = coe[4]; // Argument of perigee
double M = coe[5]; // mean anomaly
double E = mean2ecc(M, e); // eccentric anomaly
double cosE = cos(E);
double sinE = sin(E);
double fac = sqrt((1.0 - e) * (1.0 + e));
double R = a * (1.0 - e * cosE); // Distance
double V = sqrt(GM * a) / R; // Velocity
Vec3d r(a * (cosE - e), a * fac * sinE, 0.0);
Vec3d v(-V * sinE, +V * fac * cosE, 0.0);
Mat3d PQW = RotZd(-Omega) * RotXd(-i) * RotZd(-omega);
r = PQW * r;
v = PQW * v;
Vec6d rv = Vec6d::Zero();
rv.head<3>() = r; // Position vector in ECEF coordinates
rv.tail<3>() = v; // Velocity vector in ECEF coordinates
return rv;
}
Vec6d cart2coe(const Vec6d &rv, double GM) {
Vec3d r = rv.head(3); // Position
Vec3d v = rv.tail(3); // Velocity
Vec3d h = r.cross(v); // Areal velocity
double H = h.norm(); // Angular momentum
double Omega = atan2(h(0), -h(1)); // Long. ascend. node
double i = atan2(sqrt(h(0) * h(0) + h(1) * h(1)), h(2)); // Inclination
double u = atan2(r(2) * H, -r(0) * h(1) + r(1) * h(0)); // Arg. of latitude
double R = r.norm(); // Distance
double a = 1.0 / (2.0 / R - v.squaredNorm() / GM); // Semi-major axis
double eCosE = 1.0 - R / a; // e*cos(E)
double eSinE = r.dot(v) / sqrt(GM * a); // e*sin(E)
double e2 = eCosE * eCosE + eSinE * eSinE;
double e = sqrt(e2); // eccentricity
double E = atan2(eSinE, eCosE); // eccentric anomaly
double M = wrap2pi(E - eSinE); // mean anomaly
double nu = atan2(sqrt(1.0 - e2) * eSinE, eCosE - e2); // true anomaly
double omega = wrap2pi(u - nu); // Arg. of perihelion
Vec6d coe;
coe << a, e, i, Omega, omega, M; // Classical orbital elements
return coe;
}
Vec6d propagate_coe(const Vec6d &coe, double GM, double dt) {
// Propagate classical orbital elements by time dt
double a = coe[0]; // Semi-major axis
double e = coe[1]; // eccentricity
double i = coe[2]; // Inclination
double Omega = coe[3]; // Right ascension of ascending node
double omega = coe[4]; // Argument of perigee
double M = coe[5]; // mean anomaly
double n = sqrt(GM / pow(a, 3)); // mean motion
M = wrap2pi(M + n * dt); // Update mean anomaly
Vec6d new_coe;
new_coe << a, e, i, Omega, omega, M; // Updated classical orbital elements
return new_coe;
}
/******************************************************************************
* Satellite class implementation
* This class represents a satellite in orbit, defined by its classical orbital
* elements (COE) and provides methods to propagate its state and compute its
* position at a given time.
* ***************************************************************************/
Satellite::Satellite(int id, const Vec6d &coe, double epoch_utc, double GM)
: id_(id), coe_(coe), epoch_utc_(epoch_utc), GM_(GM) {
// Convert COE to Cartesian position and velocity
posvel_ = coe2cart(coe_, GM_);
}
void Satellite::propagate(double epoch_utc_new) { // propagate to a new epoch in two-body
// Propagate the satellite state to a new epoch
Vec6d coe_new = propagate_coe(coe_, GM_, epoch_utc_new - epoch_utc_);
// Update states
coe_ = coe_new;
posvel_ = coe2cart(coe_new, GM_EARTH);
epoch_utc_ = epoch_utc_new;
}
Vec3d Satellite::get_pos_epoch(double epoch_utc_new) {
// Propagate the satellite position by a time step dt
Vec6d coe_new = propagate_coe(coe_, GM_EARTH, epoch_utc_new - epoch_utc_);
Vec6d posvel = coe2cart(coe_new, GM_EARTH);
return posvel.head<3>(); // Return new position
}
Vec3d Satellite::get_pos() const {
// Get the position of the satellite in ECEF coordinates
return posvel_.head<3>();
}
/******************************************************************************
* Visbility function
* ***************************************************************************/
bool compute_vis(const Vec3d &tx_pos, const Vec3d &rx_pos, double radius, double max_angle) {
Vec3d tx2rx = rx_pos - tx_pos; // Vector from transmitter to receiver
Vec3d tx2earth = -tx_pos; // Vector from transmitter to Earth center
double dot = tx2rx.dot(tx2earth);
double mag_tx2rx = tx2rx.norm();
double mag_tx2earth = tx2earth.norm();
double angle = acos(dot / (mag_tx2rx * mag_tx2earth)) * 180 / M_PI;
double angle_earth = asin(radius / mag_tx2earth) * 180 / M_PI;
bool vis = true;
if (angle > max_angle) {
// std::cout << "Not within the mainlobe" << std::endl;
vis = false;
}
if (angle < angle_earth) {
// std::cout << "Blocked by Earth" << std::endl;
vis = false;
}
return vis;
}
double compute_min_altitude(const Vec3d &pos1, const Vec3d &pos2, double radius) {
Vec3d d = pos2 - pos1;
double d_norm_sq = d.squaredNorm();
if (d_norm_sq == 0.0) {
// Degenerate case: same position
return pos1.norm() - radius;
}
// Projection scalar t (clamped to [0,1] for line segment)
double t = (-(pos1.dot(d))) / d_norm_sq;
t = std::clamp(t, 0.0, 1.0);
// Closest point on the segment
Vec3d closest_point = pos1 + t * d;
// Altitude = distance to Earth center - Earth radius
return closest_point.norm() - radius;
}
Vec3d solve_lt(Satellite sat, const Vec3d &rx_pos, double epoch_utc) {
// Solve light time for the transmission position
Vec3d tx_pos = sat.get_pos_epoch(epoch_utc);
Vec3d dir = (rx_pos - tx_pos).normalized();
double L = (rx_pos - tx_pos).norm();
double t_prop = L / C; // Light time in seconds
double t_tx = epoch_utc - t_prop; // Transmission time in seconds since J2000
// Recompute the transmission position at the transmission time
tx_pos = sat.get_pos_epoch(t_tx);
int n_iter = 2; // Number of iterations for convergence
for (int i = 0; i < n_iter; i++) {
// Recompute the distance and light time
L = (rx_pos - tx_pos).norm();
t_prop = L / C;
t_tx = epoch_utc - t_prop;
// Update the transmission position
tx_pos = sat.get_pos_epoch(t_tx);
}
return tx_pos;
}
/******************************************************************************
* GNSS constellation setup
* This function reads TLE data from a file and sets up the GNSS constellation
* by creating Satellite objects for each satellite in the TLE data.
* ***************************************************************************/
std::vector<Satellite> setup_gnss_constellation(std::string filename) {
int sat_num = 0;
double epoch_utc = 0.0;
std::vector<Satellite> satellites;
for (auto tle : TLE::FromFile(filename)) {
double sat_epoch = tle.epoch_utc;
if (sat_epoch > epoch_utc) {
epoch_utc = sat_epoch; // Update epoch to the latest satellite epoch
}
double dt_epoch = sat_epoch - epoch_utc;
double T = SECS_DAY / tle.mean_motion;
// Classical orbital elements
double a = pow((T * T * GM_EARTH) / (4.0 * PI * PI), 1.0 / 3.0);
double e = tle.eccentricity;
double inc = tle.inclination * DEG2RAD;
double Omega = tle.raan * DEG2RAD;
double w = tle.arg_perigee * DEG2RAD;
double rad_per_sec = tle.mean_motion * 2 * PI / SECS_DAY; // TLE mean motion is in revs/day
double M = wrap2pi(tle.mean_anomaly * DEG2RAD + dt_epoch * rad_per_sec); // [rad]
// Convert to Cartesian
Vec6d coe = {a, e, inc, Omega, w, M};
Satellite sat = Satellite(tle.prn, coe,
sat_epoch); // Create a Satellite object with PRN and posvel
satellites.push_back(sat);
sat_num++;
}
// propagate all satellites to the latest epoch
for (auto &sat : satellites) {
sat.propagate(epoch_utc);
}
std::cout << "Total number of satellites: " << sat_num << std::endl;
std::cout << "Epoch UTC: " << epoch_utc << " seconds since J2000 epoch" << std::endl;
return satellites;
}
} // namespace pecsim