From f93bc90a2a8c8be6894274f92a3d8cac837ff2c1 Mon Sep 17 00:00:00 2001 From: Gunvir Ranu Date: Sat, 23 Nov 2024 01:39:29 -0800 Subject: [PATCH] Ref partially TLE parsing from C++ to C --- CMakeLists.txt | 2 +- include/perturb/perturb.h | 10 ---- include/perturb/perturb.hpp | 11 ++-- include/perturb/sgp4.h | 2 +- include/perturb/tle.h | 114 +++++++++++++++++++++++------------- src/common_private.h | 6 +- src/perturb.c | 2 + src/perturb.cpp | 11 ++-- src/{tle.cpp => tle.c} | 100 +++++++++++++++++++++++++++---- tests/test_perturb.cpp | 2 +- 10 files changed, 184 insertions(+), 76 deletions(-) rename src/{tle.cpp => tle.c} (69%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 257c146..02a6611 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,7 @@ endif() add_library( perturb - src/perturb.c src/perturb.cpp + src/perturb.c src/perturb.cpp src/tle.c ) target_include_directories( diff --git a/include/perturb/perturb.h b/include/perturb/perturb.h index eca274a..386ea95 100644 --- a/include/perturb/perturb.h +++ b/include/perturb/perturb.h @@ -33,16 +33,6 @@ namespace c_internal { extern "C" { #endif // __cplusplus -#if !(defined(__cplusplus) && defined(PERTURB_ENABLE_CPP_INTERFACE)) -/// Both lines of a TLE **must** be this length, for TLE constructors. -/// -/// It is assumed that this memory can be safely accessed. -/// Lines can be longer for verification mode, but that's for internal testing -/// purposes only and doesn't pertain to general usage. -/// Macro definition is excluded in C++ cases to not pollute globals. -# define PERTURB_TLE_LINE_LEN 69U -#endif - /// Alias to allow possibility of supporting 32-bit and 64-bit floating point /// /// TODO: Support float32_t and float64_t diff --git a/include/perturb/perturb.hpp b/include/perturb/perturb.hpp index feb17f7..3e8dca8 100644 --- a/include/perturb/perturb.hpp +++ b/include/perturb/perturb.hpp @@ -56,10 +56,11 @@ constexpr std::size_t TLE_LINE_LEN = 69; enum class TLEParseError { NONE, ///< If no issues when parsing - SHOULD_BE_SPACE, ///< If there is a lack of space in the TLE - INVALID_FORMAT, ///< If general parsing was unsuccessfully - INVALID_VALUE, ///< If a parsed value doesn't make sense CHECKSUM_MISMATCH, ///< If the checksum doesn't match + INVALID_VALUE, ///< If a parsed value doesn't make sense + INVALID_FORMAT, ///< If general parsing was unsuccessfully + SHOULD_BE_SPACE, ///< If there is a lack of space in the TLE + INVALID_INPUT, ///< If any inputs are null pointers }; enum class GravModel { @@ -79,7 +80,7 @@ enum class Sgp4Error { SEMI_LATUS_RECTUM, EPOCH_ELEMENTS_SUB_ORBITAL, DECAYED, - INVALID_TLE, + INVALID_INPUT, UNKNOWN }; @@ -177,7 +178,7 @@ struct ClassicalOrbitalElements { }; struct TwoLineElement { - c_internal::perturb_Tle internal; /// Internal C data + c_internal::perturb_TwoLineElement internal; /// Internal C data #ifndef PERTURB_DISABLE_IO /// Parse a TLE record string. diff --git a/include/perturb/sgp4.h b/include/perturb/sgp4.h index f9defb5..8ebda7c 100644 --- a/include/perturb/sgp4.h +++ b/include/perturb/sgp4.h @@ -106,7 +106,7 @@ enum perturb_Sgp4Error { PERTURB_SGP4_ERROR_SEMI_LATUS_RECTUM, PERTURB_SGP4_ERROR_EPOCH_ELEMENTS_SUB_ORBITAL, PERTURB_SGP4_ERROR_DECAYED, - PERTURB_SGP4_ERROR_INVALID_TLE, ///< Not from base impl, added in + PERTURB_SGP4_ERROR_INVALID_INPUT, ///< Not from base impl, added in PERTURB_SGP4_ERROR_UNKNOWN }; diff --git a/include/perturb/tle.h b/include/perturb/tle.h index 5d42331..662ccf9 100644 --- a/include/perturb/tle.h +++ b/include/perturb/tle.h @@ -21,7 +21,7 @@ #include "perturb/perturb.h" #include "perturb/sgp4.h" -#if __cplusplus +#ifdef __cplusplus # ifdef PERTURB_ENABLE_CPP_INTERFACE namespace perturb { namespace c_internal { @@ -29,25 +29,36 @@ namespace c_internal { extern "C" { #endif +#if !(defined(__cplusplus) && defined(PERTURB_ENABLE_CPP_INTERFACE)) +/// Both lines of a TLE **must** be this length, for TLE constructors. +/// +/// It is assumed that this memory can be safely accessed. +/// Lines can be longer for verification mode, but that's for internal testing +/// purposes only and doesn't pertain to general usage. +/// +/// Macro definition is excluded in C++ cases to not pollute globals. +/// The C++ library redefines this as a proper type. +# define PERTURB_TLE_LINE_LEN 69U +#endif + /// Possible errors when parsing a TLE. /// /// Returned by `TwoLineElement::parse` after processing a TLE record string. /// /// @post -/// Errors (excluding `NONE`) are guaranteed to occur in definition order. -/// Meaning that spaces are checked first, then invalid format, then invalid -/// values, and lastly checksum. This allows you to assume, for example, that -/// if there is a `CHECKSUM_MISMATCH`, then none of the previous errors -/// occurred first, and so the invalid checksum is the *only* issue. +/// Errors are guaranteed to occur in reverse definition order, in that higher +/// enum values are checked first. Meaning invalid input is checked first, then +/// spaces, and so on. This allows you to assume, for example, that if there is +/// a `CHECKSUM_MISMATCH`, none of the previous errors occured first. enum perturb_TleParseError { PERTURB_TLE_PARSE_ERROR_NONE, ///< If no issues when parsing - PERTURB_TLE_PARSE_ERROR_SHOULD_BE_SPACE, ///< If there is a lack of space in the TLE - PERTURB_TLE_PARSE_ERROR_INVALID_FORMAT, ///< If general parsing was unsuccessfully - PERTURB_TLE_PARSE_ERROR_INVALID_VALUE, ///< If a parsed value doesn't make sense PERTURB_TLE_PARSE_ERROR_CHECKSUM_MISMATCH, ///< If the checksum doesn't match + PERTURB_TLE_PARSE_ERROR_INVALID_VALUE, ///< If a parsed value doesn't make sense + PERTURB_TLE_PARSE_ERROR_INVALID_FORMAT, ///< If general parsing was unsuccessfully + PERTURB_TLE_PARSE_ERROR_SHOULD_BE_SPACE, ///< If there is a lack of space in the TLE + PERTURB_TLE_PARSE_ERROR_INVALID_INPUT, ///< If any inputs are null pointers }; - /// Represents a pre-parsed TLE record. /// /// Can be generated via `TwoLineElement::parse`, but not particularly useful @@ -60,46 +71,67 @@ enum perturb_TleParseError { /// a case where all I/O and string processing is removed, this type still /// allows you to construct and initialize a `Satellite` manually. However, you /// must handle your own method of creating the `TwoLineElement`s. -struct perturb_Tle { -// clang-format off - // Line 1 - char catalog_number[6]; ///< Satellite catalog number - char classification; ///< Classification {U: Unclassified, C: Classified, S: Secret} - unsigned int launch_year; ///< International Designator - Launch year (last two digits) - unsigned int launch_number; ///< International Designator - Launch number of the year - char launch_piece[4]; ///< International Designator - Piece of launch - unsigned int epoch_year; ///< Epoch year (last two digits) - double epoch_day_of_year; ///< Epoch fractional day of year - double n_dot; ///< First derivative of mean motion (ballistic coefficient) [rev/day^2] - double n_ddot; ///< Second derivative of mean motion [rev/day^3] - double b_star; ///< B* radiation pressure coefficient [1 / (earth radii)] - unsigned char ephemeris_type; ///< Orbital model used to generate data (usually 0) - unsigned int element_set_number; ///< Element set number - unsigned char line_1_checksum; ///< Line 1 check-sum - - // Line 2 - double inclination; ///< Inclination, 0 ≤ [deg] ≤ 180 - double raan; ///< Right ascension of the ascending node, 0 ≤ [deg] ≤ 360 - double eccentricity; ///< Eccentricity (0 ≤ [] ≤ 1) - double arg_of_perigee; ///< Argument of perigee, 0 ≤ [deg] ≤ 360 - double mean_anomaly; ///< Mean anomaly, 0 ≤ [deg] ≤ 360 - double mean_motion; ///< Mean motion, 0 < [rev/day] - unsigned long revolution_number; ///< Revolution number at epoch, 0 ≤ [rev] ≤ 99999 - unsigned char line_2_checksum; ///< Line 2 check-sum -// clang-format on +struct perturb_TwoLineElement { + // These are ordered same as a TLE in string format + // clang-format off + + // Line 1 - Metadata + char catalog_number[6]; ///< Satellite catalog number as a string + char classification; ///< Classification {U: Unclassified, C: Classified, S: Secret} + + // Line 1 - Launch + uint8_t launch_year; ///< International Designator - Launch year (last two digits) + uint16_t launch_number; ///< International Designator - Launch number of the year + char launch_piece[4]; ///< International Designator - Piece of launch + + // Line 1 - Epoch Time + uint8_t epoch_year; ///< Epoch year (last two digits) + perturb_real_t epoch_day_of_year; ///< Epoch fractional day of year + + // Line 1 - Trajectory + perturb_real_t n_dot; ///< First derivative of mean motion (ballistic coefficient) [rev/day^2] + perturb_real_t n_ddot; ///< Second derivative of mean motion [rev/day^3] + perturb_real_t b_star; ///< B* radiation pressure coefficient [1 / (earth radii)] + + // Line 1 - Metadata + uint8_t ephemeris_type; ///< Orbital model used to generate data (usually 0) + uint16_t element_set_number; ///< Element set number + uint8_t line_1_checksum; ///< Line 1 check-sum + + // Line 2 - Orbit + perturb_real_t inclination; ///< Inclination, 0 ≤ [deg] ≤ 180 + perturb_real_t raan; ///< Right ascension of the ascending node, 0 ≤ [deg] ≤ 360 + perturb_real_t eccentricity; ///< Eccentricity (0 ≤ [] ≤ 1) + perturb_real_t arg_of_perigee; ///< Argument of perigee, 0 ≤ [deg] ≤ 360 + perturb_real_t mean_anomaly; ///< Mean anomaly, 0 ≤ [deg] ≤ 360 + perturb_real_t mean_motion; ///< Mean motion, 0 < [rev/day] + + // Line 2 - Metadata + uint32_t revolution_number; ///< Revolution number at epoch, 0 ≤ [rev] ≤ 99999 + int8_t line_2_checksum; ///< Line 2 check-sum + + // clang-format on }; -enum perturb_TleParseError perturb_init_sat_from_tle( - struct perturb_Tle tle, enum perturb_GravityModel grav_model, struct perturb_Satellite * sat +enum perturb_Sgp4Error perturb_init_sat_from_tle( + struct perturb_TwoLineElement tle, + enum perturb_GravityModel grav_model, + struct perturb_Satellite * sat ); #ifndef PERTURB_DISABLE_IO -enum perturb_TleParseError perturb_parse_tle(const char * line_1, const char * line_2, struct perturb_Tle * tle); +enum perturb_TleParseError perturb_parse_tle( + const char * line_1, const char * line_2, + struct perturb_TwoLineElement * tle +); #endif #ifndef PERTURB_DISABLE_IO +/// TODO: Think about if there's value to having this enum perturb_TleParseError perturb_parse_tle_and_init_sat( - char * line_1, char * line_2, enum perturb_GravityModel grav_model, struct perturb_Satellite * sat + char * line_1, char * line_2, + enum perturb_GravityModel grav_model, + struct perturb_Satellite * sat ); #endif diff --git a/src/common_private.h b/src/common_private.h index 55076f5..fe3ed51 100644 --- a/src/common_private.h +++ b/src/common_private.h @@ -17,8 +17,6 @@ #ifndef PERTURB_COMMON_PRIVATE_H #define PERTURB_COMMON_PRIVATE_H -typedef perturb_real_t real_t; - #define PI 3.14159265358979323846 #define MINS_PER_DAY (24 * 60) @@ -26,4 +24,8 @@ typedef perturb_real_t real_t; #define FABS(x) fabs(x) #define FLOOR(x) floor(x) +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +typedef perturb_real_t real_t; + #endif // PERTURB_COMMON_PRIVATE_H diff --git a/src/perturb.c b/src/perturb.c index 0da4f17..6cbf87e 100644 --- a/src/perturb.c +++ b/src/perturb.c @@ -22,6 +22,8 @@ # error "Hah someone messed up, why r u compiling this C as C++" #endif +// FIXME: Reformat files for C and C++ + struct perturb_JulianDate perturb_datetime_to_julian(const struct perturb_DateTime t) { struct perturb_JulianDate jd; jday_SGP4(t.year, t.month, t.day, t.hour, t.min, t.sec, &jd.jd, &jd.jd_frac); diff --git a/src/perturb.cpp b/src/perturb.cpp index c3cdbe2..10b3d2f 100644 --- a/src/perturb.cpp +++ b/src/perturb.cpp @@ -23,10 +23,11 @@ namespace perturb { #define CHECK_ENUM_MATCHES(A, B) static_assert(static_cast(A) == c_internal::PERTURB_ ## B, "Enum mismatch!") CHECK_ENUM_MATCHES(TLEParseError::NONE, TLE_PARSE_ERROR_NONE); -CHECK_ENUM_MATCHES(TLEParseError::SHOULD_BE_SPACE, TLE_PARSE_ERROR_SHOULD_BE_SPACE); -CHECK_ENUM_MATCHES(TLEParseError::INVALID_FORMAT, TLE_PARSE_ERROR_INVALID_FORMAT); -CHECK_ENUM_MATCHES(TLEParseError::INVALID_VALUE, TLE_PARSE_ERROR_INVALID_VALUE); CHECK_ENUM_MATCHES(TLEParseError::CHECKSUM_MISMATCH, TLE_PARSE_ERROR_CHECKSUM_MISMATCH); +CHECK_ENUM_MATCHES(TLEParseError::INVALID_VALUE, TLE_PARSE_ERROR_INVALID_VALUE); +CHECK_ENUM_MATCHES(TLEParseError::INVALID_FORMAT, TLE_PARSE_ERROR_INVALID_FORMAT); +CHECK_ENUM_MATCHES(TLEParseError::SHOULD_BE_SPACE, TLE_PARSE_ERROR_SHOULD_BE_SPACE); +CHECK_ENUM_MATCHES(TLEParseError::INVALID_INPUT, TLE_PARSE_ERROR_INVALID_INPUT); CHECK_ENUM_MATCHES(GravModel::WGS72_OLD, GRAVITY_MODEL_WGS72_OLD); CHECK_ENUM_MATCHES(GravModel::WGS72, GRAVITY_MODEL_WGS72); @@ -39,7 +40,7 @@ CHECK_ENUM_MATCHES(Sgp4Error::PERT_ELEMENTS, SGP4_ERROR_PERT_ELEMENTS); CHECK_ENUM_MATCHES(Sgp4Error::SEMI_LATUS_RECTUM, SGP4_ERROR_SEMI_LATUS_RECTUM); CHECK_ENUM_MATCHES(Sgp4Error::EPOCH_ELEMENTS_SUB_ORBITAL, SGP4_ERROR_EPOCH_ELEMENTS_SUB_ORBITAL); CHECK_ENUM_MATCHES(Sgp4Error::DECAYED, SGP4_ERROR_DECAYED); -CHECK_ENUM_MATCHES(Sgp4Error::INVALID_TLE, SGP4_ERROR_INVALID_TLE); +CHECK_ENUM_MATCHES(Sgp4Error::INVALID_INPUT, SGP4_ERROR_INVALID_INPUT); CHECK_ENUM_MATCHES(Sgp4Error::UNKNOWN, SGP4_ERROR_UNKNOWN); static Sgp4Error convert_sgp4_error_code(const int error_code) { @@ -133,7 +134,7 @@ ClassicalOrbitalElements::ClassicalOrbitalElements(StateVector sv, GravModel gra #ifndef PERTURB_DISABLE_IO TLEParseError TwoLineElement::parse(const char * line_1, const char * line_2) { // Pass parsing call directly to underlying C impl - c_internal::perturb_Tle * tle = &this->internal; + c_internal::perturb_TwoLineElement * tle = &this->internal; const c_internal::perturb_TleParseError err = c_internal::perturb_parse_tle(line_1, line_2, tle); return static_cast(err); } diff --git a/src/tle.cpp b/src/tle.c similarity index 69% rename from src/tle.cpp rename to src/tle.c index a3d1efe..77cfcec 100644 --- a/src/tle.cpp +++ b/src/tle.c @@ -11,20 +11,102 @@ #include "perturb/tle.h" -#include +#include #ifndef PERTURB_DISABLE_IO -# include -# include -# include -# include +# include #endif -namespace perturb { +#include "common_private.h" + +#ifdef __cplusplus +# error "I kindly request you compile this as C instead of C++" +#endif + +static bool check_for_missing_spaces( + const char * line, + const size_t * spaces, + const size_t n_spaces +) { + for (size_t i = 0U; i < n_spaces; ++i) { + const size_t idx_space = spaces[i]; + + if (line[idx_space] != ' ') { + return true; + } + } + return false; +} + +enum perturb_Sgp4Error perturb_init_sat_from_tle( + const struct perturb_TwoLineElement tle, + const enum perturb_GravityModel grav_model, + struct perturb_Satellite * sat +) { + if (sat == NULL) { + return PERTURB_SGP4_ERROR_INVALID_INPUT; + } + + // FIXME: impl + return PERTURB_SGP4_ERROR_INVALID_INPUT; +} + +#ifndef PERTURB_DISABLE_IO +enum perturb_TleParseError perturb_parse_tle( + const char * line_1, const char * line_2, + struct perturb_TwoLineElement * tle +) { + const bool bad_ptrs = (line_1 == NULL) || (line_2 == NULL) || (tle == NULL); + if (bad_ptrs) { + return PERTURB_TLE_PARSE_ERROR_INVALID_INPUT; + } + + // Make sure there are spaces in the right places + const size_t LINE_1_SPACES[] = { 1, 8, 17, 32, 43, 52, 61, 64 }; + const size_t LINE_2_SPACES[] = { 1, 7, 16, 25, 33, 42, 51 }; + + const bool is_space_missing = ( + check_for_missing_spaces(line_1, LINE_1_SPACES, ARRAY_SIZE(LINE_1_SPACES)) || + check_for_missing_spaces(line_2, LINE_2_SPACES, ARRAY_SIZE(LINE_2_SPACES)) + ); + if (is_space_missing) { + return PERTURB_TLE_PARSE_ERROR_SHOULD_BE_SPACE; + } + + // Parse format - Line 1 + const char LINE_1_FMT_STR[] = "%1hhu %5s %1c %2u %3u %3s %2u %12lf %10lf %6lf %2d %6lf %2d %1hhu %4u %n %1hhu"; + + // FIXME: swap these to use normal int types and then convert to fixed-size for saving + unsigned char line1_num; + int n_ddot_exp, b_star_exp, l1_pre_checksum; + + int l1_scanned = sscanf( // bruh C and C++ both suck at string processing :( + line_1, LINE_1_FMT_STR, &line1_num, tle.catalog_number, &tle.classification, + &tle.launch_year, &tle.launch_number, tle.launch_piece, &tle.epoch_year, + &tle.epoch_day_of_year, &tle.n_dot, &tle.n_ddot, &n_ddot_exp, + &tle.b_star, &b_star_exp, &tle.ephemeris_type, &tle.element_set_number, + &l1_pre_checksum, &tle.line_1_checksum + ); + + + return PERTURB_TLE_PARSE_ERROR_INVALID_INPUT; +} +#endif #ifndef PERTURB_DISABLE_IO +enum perturb_TleParseError perturb_parse_tle_and_init_sat( + char * line_1, char * line_2, + enum perturb_GravityModel grav_model, + struct perturb_Satellite * sat +) { + // FIXME: impl +} +#endif + +// FIXME: clean up +#if 1 static unsigned int calc_tle_line_checksum(const char *line) { unsigned int checksum = 0U; - for (std::size_t i = 0; i < (TLE_LINE_LEN - 1); ++i) { + for (size_t i = 0; i < (PERTURB_TLE_LINE_LEN - 1); ++i) { if (std::isdigit(line[i])) { checksum += static_cast(line[i] - '0'); } @@ -34,7 +116,6 @@ static unsigned int calc_tle_line_checksum(const char *line) { } return (checksum % 10U); } -#endif // PERTURB_DISABLE_IO #ifndef PERTURB_DISABLE_IO // FIXME: Use a more robust parsing method. I wish string_view existed :( @@ -172,5 +253,4 @@ TLEParseError TwoLineElement::parse(const char *line_1, const char *line_2) { return TLEParseError::NONE; } #endif // PERTURB_DISABLE_IO - -} // namespace perturb +#endif \ No newline at end of file diff --git a/tests/test_perturb.cpp b/tests/test_perturb.cpp index c9cbcaf..c583ff1 100644 --- a/tests/test_perturb.cpp +++ b/tests/test_perturb.cpp @@ -499,7 +499,7 @@ TEST_CASE( // Parse and construct `sat_orig` using Vallado's impl auto sat_orig = Satellite::from_tle(line_1, line_2); - CHECK(sat_orig.last_error() != Sgp4Error::INVALID_TLE); + CHECK(sat_orig.last_error() != Sgp4Error::INVALID_INPUT); // Correct some unimportant differences sat_tle.internal.elnum = (sat_tle.internal.elnum * 10 + tle.internal.line_1_checksum);