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