1 |
//
|
2 |
// cTle.cpp
|
3 |
// This class encapsulates a single set of standard NORAD two line elements.
|
4 |
//
|
5 |
// Copyright 1996-2005 Michael F. Henry
|
6 |
//
|
7 |
#include "stdafx.h"
|
8 |
|
9 |
#include "cTle.h"
|
10 |
|
11 |
// Note: The column offsets are ZERO based.
|
12 |
|
13 |
// Name
|
14 |
const int TLE_LEN_LINE_DATA = 69; const int TLE_LEN_LINE_NAME = 22;
|
15 |
|
16 |
// Line 1
|
17 |
const int TLE1_COL_SATNUM = 2; const int TLE1_LEN_SATNUM = 5;
|
18 |
const int TLE1_COL_INTLDESC_A = 9; const int TLE1_LEN_INTLDESC_A = 2;
|
19 |
const int TLE1_COL_INTLDESC_B = 11; const int TLE1_LEN_INTLDESC_B = 3;
|
20 |
const int TLE1_COL_INTLDESC_C = 14; const int TLE1_LEN_INTLDESC_C = 3;
|
21 |
const int TLE1_COL_EPOCH_A = 18; const int TLE1_LEN_EPOCH_A = 2;
|
22 |
const int TLE1_COL_EPOCH_B = 20; const int TLE1_LEN_EPOCH_B = 12;
|
23 |
const int TLE1_COL_MEANMOTIONDT = 33; const int TLE1_LEN_MEANMOTIONDT = 10;
|
24 |
const int TLE1_COL_MEANMOTIONDT2 = 44; const int TLE1_LEN_MEANMOTIONDT2 = 8;
|
25 |
const int TLE1_COL_BSTAR = 53; const int TLE1_LEN_BSTAR = 8;
|
26 |
const int TLE1_COL_EPHEMTYPE = 62; const int TLE1_LEN_EPHEMTYPE = 1;
|
27 |
const int TLE1_COL_ELNUM = 64; const int TLE1_LEN_ELNUM = 4;
|
28 |
|
29 |
// Line 2
|
30 |
const int TLE2_COL_SATNUM = 2; const int TLE2_LEN_SATNUM = 5;
|
31 |
const int TLE2_COL_INCLINATION = 8; const int TLE2_LEN_INCLINATION = 8;
|
32 |
const int TLE2_COL_RAASCENDNODE = 17; const int TLE2_LEN_RAASCENDNODE = 8;
|
33 |
const int TLE2_COL_ECCENTRICITY = 26; const int TLE2_LEN_ECCENTRICITY = 7;
|
34 |
const int TLE2_COL_ARGPERIGEE = 34; const int TLE2_LEN_ARGPERIGEE = 8;
|
35 |
const int TLE2_COL_MEANANOMALY = 43; const int TLE2_LEN_MEANANOMALY = 8;
|
36 |
const int TLE2_COL_MEANMOTION = 52; const int TLE2_LEN_MEANMOTION = 11;
|
37 |
const int TLE2_COL_REVATEPOCH = 63; const int TLE2_LEN_REVATEPOCH = 5;
|
38 |
|
39 |
/////////////////////////////////////////////////////////////////////////////
|
40 |
cTle::cTle(string& strName, string& strLine1, string& strLine2)
|
41 |
{
|
42 |
m_strName = strName;
|
43 |
m_strLine1 = strLine1;
|
44 |
m_strLine2 = strLine2;
|
45 |
|
46 |
Initialize();
|
47 |
}
|
48 |
|
49 |
/////////////////////////////////////////////////////////////////////////////
|
50 |
cTle::cTle(const cTle &tle)
|
51 |
{
|
52 |
m_strName = tle.m_strName;
|
53 |
m_strLine1 = tle.m_strLine1;
|
54 |
m_strLine2 = tle.m_strLine2;
|
55 |
|
56 |
for (int fld = FLD_FIRST; fld < FLD_LAST; fld++)
|
57 |
{
|
58 |
m_Field[fld] = tle.m_Field[fld];
|
59 |
}
|
60 |
|
61 |
m_mapCache = tle.m_mapCache;
|
62 |
}
|
63 |
|
64 |
/////////////////////////////////////////////////////////////////////////////
|
65 |
cTle::~cTle()
|
66 |
{
|
67 |
}
|
68 |
|
69 |
/////////////////////////////////////////////////////////////////////////////
|
70 |
// getField()
|
71 |
// Return requested field as a double (function return value) or as a text
|
72 |
// string (*pstr) in the units requested (eUnit). Set 'bStrUnits' to true
|
73 |
// to have units appended to text string.
|
74 |
//
|
75 |
// Note: numeric return values are cached; asking for the same field more
|
76 |
// than once incurs minimal overhead.
|
77 |
double cTle::getField(eField fld,
|
78 |
eUnits units, /* = U_NATIVE */
|
79 |
string *pstr /* = NULL */,
|
80 |
bool bStrUnits /* = false */) const
|
81 |
{
|
82 |
assert((FLD_FIRST <= fld) && (fld < FLD_LAST));
|
83 |
assert((U_FIRST <= units) && (units < U_LAST));
|
84 |
|
85 |
if (pstr)
|
86 |
{
|
87 |
// Return requested field in string form.
|
88 |
*pstr = m_Field[fld];
|
89 |
|
90 |
if (bStrUnits)
|
91 |
*pstr += getUnits(fld);
|
92 |
|
93 |
return 0.0;
|
94 |
}
|
95 |
else
|
96 |
{
|
97 |
// Return requested field in floating-point form.
|
98 |
// Return cache contents if it exists, else populate cache
|
99 |
FldKey key = Key(units, fld);
|
100 |
|
101 |
if (m_mapCache.find(key) == m_mapCache.end())
|
102 |
{
|
103 |
// Value not in cache; add it
|
104 |
double valNative = atof(m_Field[fld].c_str());
|
105 |
double valConv = ConvertUnits(valNative, fld, units);
|
106 |
m_mapCache[key] = valConv;
|
107 |
|
108 |
return valConv;
|
109 |
}
|
110 |
else
|
111 |
{
|
112 |
// return cached value
|
113 |
return m_mapCache[key];
|
114 |
}
|
115 |
}
|
116 |
}
|
117 |
|
118 |
//////////////////////////////////////////////////////////////////////////////
|
119 |
// Convert the given field into the requested units. It is assumed that
|
120 |
// the value being converted is in the TLE format's "native" form.
|
121 |
double cTle::ConvertUnits(double valNative, // value to convert
|
122 |
eField fld, // what field the value is
|
123 |
eUnits units) // what units to convert to
|
124 |
{
|
125 |
switch (fld)
|
126 |
{
|
127 |
case FLD_I:
|
128 |
case FLD_RAAN:
|
129 |
case FLD_ARGPER:
|
130 |
case FLD_M:
|
131 |
{
|
132 |
// The native TLE format is DEGREES
|
133 |
if (units == U_RAD)
|
134 |
return valNative * RADS_PER_DEG;
|
135 |
}
|
136 |
}
|
137 |
|
138 |
return valNative; // return value in unconverted native format
|
139 |
}
|
140 |
|
141 |
//////////////////////////////////////////////////////////////////////////////
|
142 |
string cTle::getUnits(eField fld) const
|
143 |
{
|
144 |
static const string strDegrees = " degrees";
|
145 |
static const string strRevsPerDay = " revs / day";
|
146 |
static const string strNull;
|
147 |
|
148 |
switch (fld)
|
149 |
{
|
150 |
case FLD_I:
|
151 |
case FLD_RAAN:
|
152 |
case FLD_ARGPER:
|
153 |
case FLD_M:
|
154 |
return strDegrees;
|
155 |
|
156 |
case FLD_MMOTION:
|
157 |
return strRevsPerDay;
|
158 |
|
159 |
default:
|
160 |
return strNull;
|
161 |
}
|
162 |
}
|
163 |
|
164 |
/////////////////////////////////////////////////////////////////////////////
|
165 |
// ExpToDecimal()
|
166 |
// Converts TLE-style exponential notation of the form [ |-]00000[+|-]0 to
|
167 |
// decimal notation. Assumes implied decimal point to the left of the first
|
168 |
// number in the string, i.e.,
|
169 |
// " 12345-3" = 0.00012345
|
170 |
// "-23429-5" = -0.0000023429
|
171 |
// " 40436+1" = 4.0436
|
172 |
string cTle::ExpToDecimal(const string &str)
|
173 |
{
|
174 |
const int COL_EXP_SIGN = 6;
|
175 |
const int LEN_EXP = 2;
|
176 |
|
177 |
const int LEN_BUFREAL = 32; // max length of buffer to hold floating point
|
178 |
// representation of input string.
|
179 |
int nMan;
|
180 |
int nExp;
|
181 |
|
182 |
// sscanf(%d) will read up to the exponent sign
|
183 |
sscanf(str.c_str(), "%d", &nMan);
|
184 |
|
185 |
double dblMan = nMan;
|
186 |
bool bNeg = (nMan < 0);
|
187 |
|
188 |
if (bNeg)
|
189 |
dblMan *= -1;
|
190 |
|
191 |
// Move decimal place to left of first digit
|
192 |
while (dblMan >= 1.0)
|
193 |
dblMan /= 10.0;
|
194 |
|
195 |
if (bNeg)
|
196 |
dblMan *= -1;
|
197 |
|
198 |
// now read exponent
|
199 |
sscanf(str.substr(COL_EXP_SIGN, LEN_EXP).c_str(), "%d", &nExp);
|
200 |
|
201 |
double dblVal = dblMan * pow(10.0, nExp);
|
202 |
char szVal[LEN_BUFREAL];
|
203 |
|
204 |
snprintf(szVal, sizeof(szVal), "%.9f", dblVal);
|
205 |
|
206 |
string strVal = szVal;
|
207 |
|
208 |
return strVal;
|
209 |
|
210 |
} // ExpToDecimal()
|
211 |
|
212 |
/////////////////////////////////////////////////////////////////////////////
|
213 |
// Initialize()
|
214 |
// Initialize the string array.
|
215 |
void cTle::Initialize()
|
216 |
{
|
217 |
// Have we already been initialized?
|
218 |
if (m_Field[FLD_NORADNUM].size())
|
219 |
return;
|
220 |
|
221 |
assert(!m_strName.empty());
|
222 |
assert(!m_strLine1.empty());
|
223 |
assert(!m_strLine2.empty());
|
224 |
|
225 |
m_Field[FLD_NORADNUM] = m_strLine1.substr(TLE1_COL_SATNUM, TLE1_LEN_SATNUM);
|
226 |
m_Field[FLD_INTLDESC] = m_strLine1.substr(TLE1_COL_INTLDESC_A,
|
227 |
TLE1_LEN_INTLDESC_A +
|
228 |
TLE1_LEN_INTLDESC_B +
|
229 |
TLE1_LEN_INTLDESC_C);
|
230 |
m_Field[FLD_EPOCHYEAR] =
|
231 |
m_strLine1.substr(TLE1_COL_EPOCH_A, TLE1_LEN_EPOCH_A);
|
232 |
|
233 |
m_Field[FLD_EPOCHDAY] =
|
234 |
m_strLine1.substr(TLE1_COL_EPOCH_B, TLE1_LEN_EPOCH_B);
|
235 |
|
236 |
if (m_strLine1[TLE1_COL_MEANMOTIONDT] == '-')
|
237 |
{
|
238 |
// value is negative
|
239 |
m_Field[FLD_MMOTIONDT] = "-0";
|
240 |
}
|
241 |
else
|
242 |
m_Field[FLD_MMOTIONDT] = "0";
|
243 |
|
244 |
m_Field[FLD_MMOTIONDT] += m_strLine1.substr(TLE1_COL_MEANMOTIONDT + 1,
|
245 |
TLE1_LEN_MEANMOTIONDT);
|
246 |
|
247 |
// decimal point assumed; exponential notation
|
248 |
m_Field[FLD_MMOTIONDT2] = ExpToDecimal(
|
249 |
m_strLine1.substr(TLE1_COL_MEANMOTIONDT2,
|
250 |
TLE1_LEN_MEANMOTIONDT2));
|
251 |
// decimal point assumed; exponential notation
|
252 |
m_Field[FLD_BSTAR] = ExpToDecimal(m_strLine1.substr(TLE1_COL_BSTAR,
|
253 |
TLE1_LEN_BSTAR));
|
254 |
//TLE1_COL_EPHEMTYPE
|
255 |
//TLE1_LEN_EPHEMTYPE
|
256 |
m_Field[FLD_SET] = m_strLine1.substr(TLE1_COL_ELNUM, TLE1_LEN_ELNUM);
|
257 |
|
258 |
TrimLeft(m_Field[FLD_SET]);
|
259 |
|
260 |
//TLE2_COL_SATNUM
|
261 |
//TLE2_LEN_SATNUM
|
262 |
|
263 |
m_Field[FLD_I] = m_strLine2.substr(TLE2_COL_INCLINATION,
|
264 |
TLE2_LEN_INCLINATION);
|
265 |
TrimLeft(m_Field[FLD_I]);
|
266 |
|
267 |
m_Field[FLD_RAAN] = m_strLine2.substr(TLE2_COL_RAASCENDNODE,
|
268 |
TLE2_LEN_RAASCENDNODE);
|
269 |
TrimLeft(m_Field[FLD_RAAN]);
|
270 |
|
271 |
// decimal point is assumed
|
272 |
m_Field[FLD_E] = "0.";
|
273 |
m_Field[FLD_E] += m_strLine2.substr(TLE2_COL_ECCENTRICITY,
|
274 |
TLE2_LEN_ECCENTRICITY);
|
275 |
|
276 |
m_Field[FLD_ARGPER] = m_strLine2.substr(TLE2_COL_ARGPERIGEE,
|
277 |
TLE2_LEN_ARGPERIGEE);
|
278 |
TrimLeft(m_Field[FLD_ARGPER]);
|
279 |
|
280 |
m_Field[FLD_M] = m_strLine2.substr(TLE2_COL_MEANANOMALY,
|
281 |
TLE2_LEN_MEANANOMALY);
|
282 |
TrimLeft(m_Field[FLD_M]);
|
283 |
|
284 |
m_Field[FLD_MMOTION] = m_strLine2.substr(TLE2_COL_MEANMOTION,
|
285 |
TLE2_LEN_MEANMOTION);
|
286 |
TrimLeft(m_Field[FLD_MMOTION]);
|
287 |
|
288 |
m_Field[FLD_ORBITNUM] = m_strLine2.substr(TLE2_COL_REVATEPOCH,
|
289 |
TLE2_LEN_REVATEPOCH);
|
290 |
TrimLeft(m_Field[FLD_ORBITNUM]);
|
291 |
|
292 |
} // InitStrVars()
|
293 |
|
294 |
/////////////////////////////////////////////////////////////////////////////
|
295 |
// IsTleFormat()
|
296 |
// Returns true if "str" is a valid data line of a two-line element set,
|
297 |
// else false.
|
298 |
//
|
299 |
// To be valid a line must:
|
300 |
// Have as the first character the line number
|
301 |
// Have as the second character a blank
|
302 |
// Be TLE_LEN_LINE_DATA characters long
|
303 |
// Have a valid checksum (note: no longer required as of 12/96)
|
304 |
//
|
305 |
bool cTle::IsValidLine(string& str, eTleLine line)
|
306 |
{
|
307 |
TrimLeft(str);
|
308 |
TrimRight(str);
|
309 |
|
310 |
size_t nLen = str.size();
|
311 |
|
312 |
if (nLen != TLE_LEN_LINE_DATA)
|
313 |
return false;
|
314 |
|
315 |
// First char in string must be line number
|
316 |
if ((str[0] - '0') != line)
|
317 |
return false;
|
318 |
|
319 |
// Second char in string must be blank
|
320 |
if (str[1] != ' ')
|
321 |
return false;
|
322 |
|
323 |
/*
|
324 |
NOTE: 12/96
|
325 |
The requirement that the last char in the line data must be a valid
|
326 |
checksum is too restrictive.
|
327 |
|
328 |
// Last char in string must be checksum
|
329 |
int nSum = CheckSum(str);
|
330 |
|
331 |
if (nSum != (str[TLE_LEN_LINE_DATA - 1] - '0'))
|
332 |
return false;
|
333 |
*/
|
334 |
|
335 |
return true;
|
336 |
|
337 |
} // IsTleFormat()
|
338 |
|
339 |
/////////////////////////////////////////////////////////////////////////////
|
340 |
// CheckSum()
|
341 |
// Calculate the check sum for a given line of TLE data, the last character
|
342 |
// of which is the current checksum. (Although there is no check here,
|
343 |
// the current checksum should match the one we calculate.)
|
344 |
// The checksum algorithm:
|
345 |
// Each number in the data line is summed, modulo 10.
|
346 |
// Non-numeric characters are zero, except minus signs, which are 1.
|
347 |
//
|
348 |
int cTle::CheckSum(const string& str)
|
349 |
{
|
350 |
// The length is "- 1" because we don't include the current (existing)
|
351 |
// checksum character in the checksum calculation.
|
352 |
size_t len = str.size() - 1;
|
353 |
int xsum = 0;
|
354 |
|
355 |
for (size_t i = 0; i < len; i++)
|
356 |
{
|
357 |
char ch = str[i];
|
358 |
if (isdigit(ch))
|
359 |
xsum += (ch - '0');
|
360 |
else if (ch == '-')
|
361 |
xsum++;
|
362 |
}
|
363 |
|
364 |
return (xsum % 10);
|
365 |
|
366 |
} // CheckSum()
|
367 |
|
368 |
/////////////////////////////////////////////////////////////////////////////
|
369 |
void cTle::TrimLeft(string& s)
|
370 |
{
|
371 |
while (s[0] == ' ')
|
372 |
s.erase(0, 1);
|
373 |
}
|
374 |
|
375 |
/////////////////////////////////////////////////////////////////////////////
|
376 |
void cTle::TrimRight(string& s)
|
377 |
{
|
378 |
while (s[s.size() - 1] == ' ')
|
379 |
s.erase(s.size() - 1);
|
380 |
}
|
381 |
|