1 |
kusanagi |
1.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 |
|
|
|