// // cTle.cpp // This class encapsulates a single set of standard NORAD two line elements. // // Copyright 1996-2005 Michael F. Henry // #include "stdafx.h" #include "cTle.h" // Note: The column offsets are ZERO based. // Name const int TLE_LEN_LINE_DATA = 69; const int TLE_LEN_LINE_NAME = 22; // Line 1 const int TLE1_COL_SATNUM = 2; const int TLE1_LEN_SATNUM = 5; const int TLE1_COL_INTLDESC_A = 9; const int TLE1_LEN_INTLDESC_A = 2; const int TLE1_COL_INTLDESC_B = 11; const int TLE1_LEN_INTLDESC_B = 3; const int TLE1_COL_INTLDESC_C = 14; const int TLE1_LEN_INTLDESC_C = 3; const int TLE1_COL_EPOCH_A = 18; const int TLE1_LEN_EPOCH_A = 2; const int TLE1_COL_EPOCH_B = 20; const int TLE1_LEN_EPOCH_B = 12; const int TLE1_COL_MEANMOTIONDT = 33; const int TLE1_LEN_MEANMOTIONDT = 10; const int TLE1_COL_MEANMOTIONDT2 = 44; const int TLE1_LEN_MEANMOTIONDT2 = 8; const int TLE1_COL_BSTAR = 53; const int TLE1_LEN_BSTAR = 8; const int TLE1_COL_EPHEMTYPE = 62; const int TLE1_LEN_EPHEMTYPE = 1; const int TLE1_COL_ELNUM = 64; const int TLE1_LEN_ELNUM = 4; // Line 2 const int TLE2_COL_SATNUM = 2; const int TLE2_LEN_SATNUM = 5; const int TLE2_COL_INCLINATION = 8; const int TLE2_LEN_INCLINATION = 8; const int TLE2_COL_RAASCENDNODE = 17; const int TLE2_LEN_RAASCENDNODE = 8; const int TLE2_COL_ECCENTRICITY = 26; const int TLE2_LEN_ECCENTRICITY = 7; const int TLE2_COL_ARGPERIGEE = 34; const int TLE2_LEN_ARGPERIGEE = 8; const int TLE2_COL_MEANANOMALY = 43; const int TLE2_LEN_MEANANOMALY = 8; const int TLE2_COL_MEANMOTION = 52; const int TLE2_LEN_MEANMOTION = 11; const int TLE2_COL_REVATEPOCH = 63; const int TLE2_LEN_REVATEPOCH = 5; ///////////////////////////////////////////////////////////////////////////// cTle::cTle(string& strName, string& strLine1, string& strLine2) { m_strName = strName; m_strLine1 = strLine1; m_strLine2 = strLine2; Initialize(); } ///////////////////////////////////////////////////////////////////////////// cTle::cTle(const cTle &tle) { m_strName = tle.m_strName; m_strLine1 = tle.m_strLine1; m_strLine2 = tle.m_strLine2; for (int fld = FLD_FIRST; fld < FLD_LAST; fld++) { m_Field[fld] = tle.m_Field[fld]; } m_mapCache = tle.m_mapCache; } ///////////////////////////////////////////////////////////////////////////// cTle::~cTle() { } ///////////////////////////////////////////////////////////////////////////// // getField() // Return requested field as a double (function return value) or as a text // string (*pstr) in the units requested (eUnit). Set 'bStrUnits' to true // to have units appended to text string. // // Note: numeric return values are cached; asking for the same field more // than once incurs minimal overhead. double cTle::getField(eField fld, eUnits units, /* = U_NATIVE */ string *pstr /* = NULL */, bool bStrUnits /* = false */) const { assert((FLD_FIRST <= fld) && (fld < FLD_LAST)); assert((U_FIRST <= units) && (units < U_LAST)); if (pstr) { // Return requested field in string form. *pstr = m_Field[fld]; if (bStrUnits) *pstr += getUnits(fld); return 0.0; } else { // Return requested field in floating-point form. // Return cache contents if it exists, else populate cache FldKey key = Key(units, fld); if (m_mapCache.find(key) == m_mapCache.end()) { // Value not in cache; add it double valNative = atof(m_Field[fld].c_str()); double valConv = ConvertUnits(valNative, fld, units); m_mapCache[key] = valConv; return valConv; } else { // return cached value return m_mapCache[key]; } } } ////////////////////////////////////////////////////////////////////////////// // Convert the given field into the requested units. It is assumed that // the value being converted is in the TLE format's "native" form. double cTle::ConvertUnits(double valNative, // value to convert eField fld, // what field the value is eUnits units) // what units to convert to { switch (fld) { case FLD_I: case FLD_RAAN: case FLD_ARGPER: case FLD_M: { // The native TLE format is DEGREES if (units == U_RAD) return valNative * RADS_PER_DEG; } case FLD_NORADNUM: case FLD_INTLDESC: case FLD_SET: case FLD_EPOCHYEAR: case FLD_EPOCHDAY: case FLD_ORBITNUM: case FLD_E: case FLD_MMOTION: case FLD_MMOTIONDT: case FLD_MMOTIONDT2: case FLD_BSTAR: case FLD_LAST: { // do nothing } } return valNative; // return value in unconverted native format } ////////////////////////////////////////////////////////////////////////////// string cTle::getUnits(eField fld) const { static const string strDegrees = " degrees"; static const string strRevsPerDay = " revs / day"; static const string strNull; switch (fld) { case FLD_I: case FLD_RAAN: case FLD_ARGPER: case FLD_M: return strDegrees; case FLD_MMOTION: return strRevsPerDay; default: return strNull; } } ///////////////////////////////////////////////////////////////////////////// // ExpToDecimal() // Converts TLE-style exponential notation of the form [ |-]00000[+|-]0 to // decimal notation. Assumes implied decimal point to the left of the first // number in the string, i.e., // " 12345-3" = 0.00012345 // "-23429-5" = -0.0000023429 // " 40436+1" = 4.0436 string cTle::ExpToDecimal(const string &str) { const int COL_EXP_SIGN = 6; const int LEN_EXP = 2; const int LEN_BUFREAL = 32; // max length of buffer to hold floating point // representation of input string. int nMan; int nExp; // sscanf(%d) will read up to the exponent sign sscanf(str.c_str(), "%d", &nMan); double dblMan = nMan; bool bNeg = (nMan < 0); if (bNeg) dblMan *= -1; // Move decimal place to left of first digit while (dblMan >= 1.0) dblMan /= 10.0; if (bNeg) dblMan *= -1; // now read exponent sscanf(str.substr(COL_EXP_SIGN, LEN_EXP).c_str(), "%d", &nExp); double dblVal = dblMan * pow(10.0, nExp); char szVal[LEN_BUFREAL]; snprintf(szVal, sizeof(szVal), "%.9f", dblVal); string strVal = szVal; return strVal; } // ExpToDecimal() ///////////////////////////////////////////////////////////////////////////// // Initialize() // Initialize the string array. void cTle::Initialize() { // Have we already been initialized? if (m_Field[FLD_NORADNUM].size()) return; assert(!m_strName.empty()); assert(!m_strLine1.empty()); assert(!m_strLine2.empty()); m_Field[FLD_NORADNUM] = m_strLine1.substr(TLE1_COL_SATNUM, TLE1_LEN_SATNUM); m_Field[FLD_INTLDESC] = m_strLine1.substr(TLE1_COL_INTLDESC_A, TLE1_LEN_INTLDESC_A + TLE1_LEN_INTLDESC_B + TLE1_LEN_INTLDESC_C); m_Field[FLD_EPOCHYEAR] = m_strLine1.substr(TLE1_COL_EPOCH_A, TLE1_LEN_EPOCH_A); m_Field[FLD_EPOCHDAY] = m_strLine1.substr(TLE1_COL_EPOCH_B, TLE1_LEN_EPOCH_B); if (m_strLine1[TLE1_COL_MEANMOTIONDT] == '-') { // value is negative m_Field[FLD_MMOTIONDT] = "-0"; } else m_Field[FLD_MMOTIONDT] = "0"; m_Field[FLD_MMOTIONDT] += m_strLine1.substr(TLE1_COL_MEANMOTIONDT + 1, TLE1_LEN_MEANMOTIONDT); // decimal point assumed; exponential notation m_Field[FLD_MMOTIONDT2] = ExpToDecimal( m_strLine1.substr(TLE1_COL_MEANMOTIONDT2, TLE1_LEN_MEANMOTIONDT2)); // decimal point assumed; exponential notation m_Field[FLD_BSTAR] = ExpToDecimal(m_strLine1.substr(TLE1_COL_BSTAR, TLE1_LEN_BSTAR)); //TLE1_COL_EPHEMTYPE //TLE1_LEN_EPHEMTYPE m_Field[FLD_SET] = m_strLine1.substr(TLE1_COL_ELNUM, TLE1_LEN_ELNUM); TrimLeft(m_Field[FLD_SET]); //TLE2_COL_SATNUM //TLE2_LEN_SATNUM m_Field[FLD_I] = m_strLine2.substr(TLE2_COL_INCLINATION, TLE2_LEN_INCLINATION); TrimLeft(m_Field[FLD_I]); m_Field[FLD_RAAN] = m_strLine2.substr(TLE2_COL_RAASCENDNODE, TLE2_LEN_RAASCENDNODE); TrimLeft(m_Field[FLD_RAAN]); // decimal point is assumed m_Field[FLD_E] = "0."; m_Field[FLD_E] += m_strLine2.substr(TLE2_COL_ECCENTRICITY, TLE2_LEN_ECCENTRICITY); m_Field[FLD_ARGPER] = m_strLine2.substr(TLE2_COL_ARGPERIGEE, TLE2_LEN_ARGPERIGEE); TrimLeft(m_Field[FLD_ARGPER]); m_Field[FLD_M] = m_strLine2.substr(TLE2_COL_MEANANOMALY, TLE2_LEN_MEANANOMALY); TrimLeft(m_Field[FLD_M]); m_Field[FLD_MMOTION] = m_strLine2.substr(TLE2_COL_MEANMOTION, TLE2_LEN_MEANMOTION); TrimLeft(m_Field[FLD_MMOTION]); m_Field[FLD_ORBITNUM] = m_strLine2.substr(TLE2_COL_REVATEPOCH, TLE2_LEN_REVATEPOCH); TrimLeft(m_Field[FLD_ORBITNUM]); } // InitStrVars() ///////////////////////////////////////////////////////////////////////////// // IsTleFormat() // Returns true if "str" is a valid data line of a two-line element set, // else false. // // To be valid a line must: // Have as the first character the line number // Have as the second character a blank // Be TLE_LEN_LINE_DATA characters long // Have a valid checksum (note: no longer required as of 12/96) // bool cTle::IsValidLine(string& str, eTleLine line) { TrimLeft(str); TrimRight(str); size_t nLen = str.size(); if (nLen != (uint)TLE_LEN_LINE_DATA) return false; // First char in string must be line number if ((str[0] - '0') != line) return false; // Second char in string must be blank if (str[1] != ' ') return false; /* NOTE: 12/96 The requirement that the last char in the line data must be a valid checksum is too restrictive. // Last char in string must be checksum int nSum = CheckSum(str); if (nSum != (str[TLE_LEN_LINE_DATA - 1] - '0')) return false; */ return true; } // IsTleFormat() ///////////////////////////////////////////////////////////////////////////// // CheckSum() // Calculate the check sum for a given line of TLE data, the last character // of which is the current checksum. (Although there is no check here, // the current checksum should match the one we calculate.) // The checksum algorithm: // Each number in the data line is summed, modulo 10. // Non-numeric characters are zero, except minus signs, which are 1. // int cTle::CheckSum(const string& str) { // The length is "- 1" because we don't include the current (existing) // checksum character in the checksum calculation. size_t len = str.size() - 1; int xsum = 0; for (size_t i = 0; i < len; i++) { char ch = str[i]; if (isdigit(ch)) xsum += (ch - '0'); else if (ch == '-') xsum++; } return (xsum % 10); } // CheckSum() ///////////////////////////////////////////////////////////////////////////// void cTle::TrimLeft(string& s) { while (s[0] == ' ') s.erase(0, 1); } ///////////////////////////////////////////////////////////////////////////// void cTle::TrimRight(string& s) { while (s[s.size() - 1] == ' ') s.erase(s.size() - 1); }