1 // spice2xyzv.cpp
2 //
3 // Copyright (C) 2008, Chris Laurel <claurel@shatters.net>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // Create a Celestia xyzv file from a pool of SPICE SPK files
11
12 #include "SpiceUsr.h"
13 #include <string>
14 #include <vector>
15 #include <iostream>
16 #include <fstream>
17 #include <iomanip>
18 #include <cmath>
19 #include <ctime>
20
21 using namespace std;
22
23
24 const double J2000 = 2451545.0;
25
26
27 // Default values
28 // Units are seconds
29 const double MIN_STEP_SIZE = 60.0;
30 const double MAX_STEP_SIZE = 5 * 86400.0;
31
32 // Units are kilometers
33 const double TOLERANCE = 20.0;
34
35
36 class Configuration
37 {
38 public:
Configuration()39 Configuration() :
40 kernelDirectory("."),
41 frameName("eclipJ2000"),
42 minStepSize(MIN_STEP_SIZE),
43 maxStepSize(MAX_STEP_SIZE),
44 tolerance(TOLERANCE)
45 {
46 }
47
48 string kernelDirectory;
49 vector<string> kernelList;
50 string startDate;
51 string endDate;
52 string observerName;
53 string targetName;
54 string frameName;
55 double minStepSize;
56 double maxStepSize;
57 double tolerance;
58 };
59
60
61 // Very basic 3-vector class
62 class Vec3d
63 {
64 public:
Vec3d()65 Vec3d() : x(0.0), y(0.0), z(0.0) {}
Vec3d(double _x,double _y,double _z)66 Vec3d(double _x, double _y, double _z) : x(_x), y(_y), z(_z) {}
Vec3d(const double v[])67 Vec3d(const double v[]) : x(v[0]), y(v[1]), z(v[2]) {}
68
length() const69 double length() const { return std::sqrt(x * x + y * y + z * z); }
70
71 double x, y, z;
72 };
73
74 // Vector add
operator +(const Vec3d & v0,const Vec3d & v1)75 Vec3d operator+(const Vec3d& v0, const Vec3d& v1)
76 {
77 return Vec3d(v0.x + v1.x, v0.y + v1.y, v0.z + v1.z);
78 }
79
80 // Vector subtract
operator -(const Vec3d & v0,const Vec3d & v1)81 Vec3d operator-(const Vec3d& v0, const Vec3d& v1)
82 {
83 return Vec3d(v0.x - v1.x, v0.y - v1.y, v0.z - v1.z);
84 }
85
86 // Additive inverse
operator -(const Vec3d & v)87 Vec3d operator-(const Vec3d& v)
88 {
89 return Vec3d(-v.x, -v.y, -v.z);
90 }
91
92 // Scalar multiply
operator *(const Vec3d & v,double d)93 Vec3d operator*(const Vec3d& v, double d)
94 {
95 return Vec3d(v.x * d, v.y * d, v.z * d);
96 }
97
98 // Scalar multiply
operator *(double d,const Vec3d & v)99 Vec3d operator*(double d, const Vec3d& v)
100 {
101 return Vec3d(v.x * d, v.y * d, v.z * d);
102 }
103
operator <<(ostream & o,const Vec3d & v)104 ostream& operator<<(ostream& o, const Vec3d& v)
105 {
106 return o << v.x << ' ' << v.y << ' ' << v.z;
107 }
108
109
110 // The StateVector object just contains the position and velocity
111 // in 3D.
112 class StateVector
113 {
114 public:
115 // Construct a new StateVector from an array of 6 doubles
116 // (as used by SPICE.)
StateVector(const double v[])117 StateVector(const double v[]) :
118 position(v), velocity(v + 3) {};
119
120 Vec3d position;
121 Vec3d velocity;
122 };
123
124
125 // QuotedString is used read a double quoted string from a C++ input
126 // stream.
127 class QuotedString
128 {
129 public:
130 string value;
131 };
132
operator >>(istream & in,QuotedString & qs)133 istream& operator>>(istream& in, QuotedString& qs)
134 {
135 char c = '\0';
136
137 in >> c;
138 while (in && isspace(c))
139 {
140 in >> c;
141 }
142
143 if (c != '"')
144 {
145 in.setstate(ios::failbit);
146 return in;
147 }
148
149 string s;
150
151 in >> c;
152 while (in && c != '"')
153 {
154 s += c;
155 in.get(c);
156 }
157
158 if (in)
159 qs.value = s;
160
161 return in;
162 }
163
164
165
166 // QuoteStringList is used to read a list of double quoted strings from
167 // a C++ input stream. The string list must be enclosed by square brackets.
168 class QuotedStringList
169 {
170 public:
171 vector<string> value;
172 };
173
operator >>(istream & in,QuotedStringList & qsl)174 istream& operator>>(istream& in, QuotedStringList& qsl)
175 {
176 qsl.value.clear();
177 char c = '\0';
178
179 in >> c;
180 if (c != '[')
181 {
182 in.setstate(ios::failbit);
183 return in;
184 }
185
186 in >> c;
187 while (in && c == '"')
188 {
189 in.unget();
190
191 QuotedString qs;
192 if (in >> qs)
193 {
194 qsl.value.push_back(qs.value);
195 in >> c;
196 }
197 }
198
199 if (c != ']')
200 in.setstate(ios::failbit);
201
202 return in;
203 }
204
205
206
cubicInterpolate(const Vec3d & p0,const Vec3d & v0,const Vec3d & p1,const Vec3d & v1,double t)207 static Vec3d cubicInterpolate(const Vec3d& p0, const Vec3d& v0,
208 const Vec3d& p1, const Vec3d& v1,
209 double t)
210 {
211 return p0 + (((2.0 * (p0 - p1) + v1 + v0) * (t * t * t)) +
212 ((3.0 * (p1 - p0) - 2.0 * v0 - v1) * (t * t)) +
213 (v0 * t));
214 }
215
216
et2jd(double et)217 double et2jd(double et)
218 {
219 return J2000 + et / 86400.0;
220 }
221
222
etToString(double et)223 string etToString(double et)
224 {
225 char buf[200];
226 et2utc_c(et, "C", 3, sizeof(buf), buf);
227 return string(buf);
228 }
229
230
printRecord(ostream & out,double et,const StateVector & state)231 void printRecord(ostream& out, double et, const StateVector& state)
232 {
233 // < 1 second error around J2000
234 out << setprecision(12) << et2jd(et) << " ";
235
236 // < 1 meter error at 1 billion km
237 out << setprecision(12) << state.position << " ";
238
239 // < 0.1 mm/s error at 10 km/s
240 out << setprecision(8) << state.velocity << endl;
241 }
242
243
getStateVector(SpiceInt targetID,double et,const string & frameName,SpiceInt observerID)244 StateVector getStateVector(SpiceInt targetID,
245 double et,
246 const string& frameName,
247 SpiceInt observerID)
248 {
249 double stateVector[6];
250 double lightTime = 0.0;
251
252 spkgeo_c(targetID, et, frameName.c_str(), observerID,
253 stateVector, &lightTime);
254
255 return StateVector(stateVector);
256 }
257
258
259
convertSpkToXyzv(const Configuration & config,ostream & out)260 bool convertSpkToXyzv(const Configuration& config,
261 ostream& out)
262 {
263 // Load the required SPICE kernels
264 for (vector<string>::const_iterator iter = config.kernelList.begin();
265 iter != config.kernelList.end(); iter++)
266 {
267 string pathname = config.kernelDirectory + "/" + *iter;
268 furnsh_c(pathname.c_str());
269 }
270
271
272 double startET = 0.0;
273 double endET = 0.0;
274
275 str2et_c(config.startDate.c_str(), &startET);
276 str2et_c(config.endDate.c_str(), &endET);
277
278 SpiceBoolean found = SPICEFALSE;
279 SpiceInt observerID = 0;
280 SpiceInt targetID = 0;
281 bodn2c_c(config.observerName.c_str(), &observerID, &found);
282 if (!found)
283 {
284 cerr << "Observer object " << config.observerName << " not found. Aborting.\n";
285 return false;
286 }
287
288 bodn2c_c(config.targetName.c_str(), &targetID, &found);
289 if (!found)
290 {
291 cerr << "Target object " << config.targetName << " not found. Aborting.\n";
292 return false;
293 }
294
295 StateVector lastState = getStateVector(targetID, startET, config.frameName, observerID);
296 double et = startET;
297
298 printRecord(out, et, lastState);
299
300 while (et + config.minStepSize < endET)
301 {
302 double dt = config.minStepSize;
303
304 StateVector s0 = getStateVector(targetID, et + dt, config.frameName, observerID);
305 double et0 = et + dt;
306
307 while (dt < config.maxStepSize && et + dt * 2.0 < endET)
308 {
309 dt *= 2.0;
310 StateVector s1 = getStateVector(targetID, et + dt, config.frameName, observerID);
311 double et1 = et + dt;
312
313 Vec3d pInterp = cubicInterpolate(lastState.position,
314 lastState.velocity * dt,
315 s1.position,
316 s1.velocity * dt,
317 0.5);
318
319 double positionError = (pInterp - s0.position).length();
320 if (positionError > config.tolerance || dt > config.maxStepSize)
321 break;
322
323 s0 = s1;
324 et0 = et1;
325 }
326
327 lastState = s0;
328 et = et0;
329
330 printRecord(out, et0, lastState);
331 }
332
333 lastState = getStateVector(targetID, endET, config.frameName, observerID);
334 printRecord(out, endET, lastState);
335
336 return true;
337 }
338
339
340 // Dump information about the xyzv file in the comment header
writeCommentHeader(const Configuration & config,ostream & out)341 void writeCommentHeader(const Configuration& config,
342 ostream& out)
343 {
344 SpiceInt observerID = 0;
345 SpiceInt targetID = 0;
346 SpiceBoolean found = SPICEFALSE;
347
348 bodn2c_c(config.observerName.c_str(), &observerID, &found);
349 if (!found)
350 return;
351 bodn2c_c(config.targetName.c_str(), &targetID, &found);
352 if (!found)
353 return;
354
355 out << "# Celestia xyzv file generated by spice2xyzv\n";
356 out << "#\n";
357
358 time_t now = 0;
359 time(&now);
360 out << "# Creation date: " << ctime(&now);
361 out << "#\n";
362
363 out << "# SPICE kernel files used:\n";
364 for (vector<string>::const_iterator iter = config.kernelList.begin();
365 iter != config.kernelList.end(); iter++)
366 {
367 out << "# " << *iter << endl;
368 }
369 out << "#\n";
370
371 out << "# Start date: " << config.startDate << endl;
372 out << "# End date: " << config.endDate << endl;
373 out << "# Observer: " << config.observerName << " (" << observerID << ")" << endl;
374 out << "# Target: " << config.targetName << " (" << targetID << ")" << endl;
375 out << "# Frame: " << config.frameName << endl;
376 out << "#\n";
377
378 out << "# Min step size: " << config.minStepSize << " s" << endl;
379 out << "# Max step size: " << config.maxStepSize << " s" << endl;
380 out << "# Tolerance: " << config.tolerance << " km" << endl;
381 out << "#\n";
382
383 out << "# Records are <jd> <x> <y> <z> <vel x> <vel y> <vel z>\n";
384 out << "# Time is a TDB Julian date\n";
385 out << "# Position in km\n";
386 out << "# Velocity in km/sec\n";
387
388 out << endl;
389 }
390
391
readConfig(istream & in,Configuration & config)392 bool readConfig(istream& in, Configuration& config)
393 {
394 QuotedString qs;
395
396 while (in && !in.eof())
397 {
398 string key;
399
400 in >> key;
401 if (in.eof())
402 return true;
403
404 if (!in.eof())
405 {
406 if (key == "StartDate")
407 {
408 if (in >> qs)
409 config.startDate = qs.value;
410 }
411 else if (key == "EndDate")
412 {
413 if (in >> qs)
414 config.endDate = qs.value;
415 }
416 else if (key == "Observer")
417 {
418 if (in >> qs)
419 config.observerName = qs.value;
420 }
421 else if (key == "Target")
422 {
423 if (in >> qs)
424 config.targetName = qs.value;
425 }
426 else if (key == "Frame")
427 {
428 if (in >> qs)
429 config.frameName = qs.value;
430 }
431 else if (key == "MinStep")
432 {
433 in >> config.minStepSize;
434 }
435 else if (key == "MaxStep")
436 {
437 in >> config.maxStepSize;
438 }
439 else if (key == "Tolerance")
440 {
441 in >> config.tolerance;
442 }
443 else if (key == "KernelDirectory")
444 {
445 if (in >> qs)
446 config.kernelDirectory = qs.value;
447 }
448 else if (key == "Kernels")
449 {
450 QuotedStringList qsl;
451 if (in >> qsl)
452 {
453 config.kernelList = qsl.value;
454 }
455 }
456 }
457 }
458
459 return in.good();
460 }
461
462
main(int argc,char * argv[])463 int main(int argc, char* argv[])
464 {
465 // Load the leap second kernel
466 furnsh_c("naif0008.tls");
467
468 if (argc < 2)
469 {
470 cerr << "Usage: spice2xyzv <config filename> [output filename]\n";
471 return 1;
472 }
473
474
475 ifstream configFile(argv[1]);
476 if (!configFile)
477 {
478 cerr << "Error opening configuration file.\n";
479 return 1;
480 }
481
482
483 Configuration config;
484 if (!readConfig(configFile, config))
485 {
486 cerr << "Error in configuration file.\n";
487 return 1;
488 }
489
490 // Check that all required parameters are present.
491 if (config.startDate.empty())
492 {
493 cerr << "StartDate missing from configuration file.\n";
494 return 1;
495 }
496
497 if (config.endDate.empty())
498 {
499 cerr << "EndDate missing from configuration file.\n";
500 return 1;
501 }
502
503 if (config.targetName.empty())
504 {
505 cerr << "Target missing from configuration file.\n";
506 return 1;
507 }
508
509 if (config.observerName.empty())
510 {
511 cerr << "Observer missing from configuration file.\n";
512 return 1;
513 }
514
515 if (config.kernelList.empty())
516 {
517 cerr << "Kernels missing from configuration file.\n";
518 return 1;
519 }
520
521 writeCommentHeader(config, cout);
522 convertSpkToXyzv(config, cout);
523
524 return 0;
525 }
526