celestia/src/tools/spice2xyzv/spice2xyzv.cpp

602 lines
14 KiB
C++

// spice2xyzv.cpp
//
// Copyright (C) 2008-2009, Chris Laurel <claurel@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// Create a Celestia xyzv file from a pool of SPICE SPK files
#include "SpiceUsr.h"
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <cmath>
#include <ctime>
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
using namespace std;
const double J2000 = 2451545.0;
// Default values
// Units are seconds
const double MIN_STEP_SIZE = 60.0;
const double MAX_STEP_SIZE = 5 * 86400.0;
// Units are kilometers
const double TOLERANCE = 20.0;
class Configuration
{
public:
Configuration() :
kernelDirectory("."),
frameName("eclipJ2000"),
minStepSize(MIN_STEP_SIZE),
maxStepSize(MAX_STEP_SIZE),
tolerance(TOLERANCE)
{
}
string kernelDirectory;
vector<string> kernelList;
string startDate;
string endDate;
string observerName;
string targetName;
string frameName;
double minStepSize;
double maxStepSize;
double tolerance;
};
// Very basic 3-vector class
class Vec3d
{
public:
Vec3d() : x(0.0), y(0.0), z(0.0) {}
Vec3d(double _x, double _y, double _z) : x(_x), y(_y), z(_z) {}
Vec3d(const double v[]) : x(v[0]), y(v[1]), z(v[2]) {}
double length() const { return std::sqrt(x * x + y * y + z * z); }
double x, y, z;
};
// Vector add
Vec3d operator+(const Vec3d& v0, const Vec3d& v1)
{
return Vec3d(v0.x + v1.x, v0.y + v1.y, v0.z + v1.z);
}
// Vector subtract
Vec3d operator-(const Vec3d& v0, const Vec3d& v1)
{
return Vec3d(v0.x - v1.x, v0.y - v1.y, v0.z - v1.z);
}
// Additive inverse
Vec3d operator-(const Vec3d& v)
{
return Vec3d(-v.x, -v.y, -v.z);
}
// Scalar multiply
Vec3d operator*(const Vec3d& v, double d)
{
return Vec3d(v.x * d, v.y * d, v.z * d);
}
// Scalar multiply
Vec3d operator*(double d, const Vec3d& v)
{
return Vec3d(v.x * d, v.y * d, v.z * d);
}
ostream& operator<<(ostream& o, const Vec3d& v)
{
return o << v.x << ' ' << v.y << ' ' << v.z;
}
// The StateVector object just contains the position and velocity
// in 3D.
class StateVector
{
public:
// Construct a new StateVector from an array of 6 doubles
// (as used by SPICE.)
StateVector(const double v[]) :
position(v), velocity(v + 3) {};
Vec3d position;
Vec3d velocity;
};
// QuotedString is used read a double quoted string from a C++ input
// stream.
class QuotedString
{
public:
string value;
};
istream& operator>>(istream& in, QuotedString& qs)
{
char c = '\0';
in >> c;
while (in && isspace(c))
{
in >> c;
}
if (c != '"')
{
in.setstate(ios::failbit);
return in;
}
string s;
in >> c;
while (in && c != '"')
{
s += c;
in.get(c);
}
if (in)
qs.value = s;
return in;
}
// QuoteStringList is used to read a list of double quoted strings from
// a C++ input stream. The string list must be enclosed by square brackets.
class QuotedStringList
{
public:
vector<string> value;
};
istream& operator>>(istream& in, QuotedStringList& qsl)
{
qsl.value.clear();
char c = '\0';
in >> c;
if (c != '[')
{
in.setstate(ios::failbit);
return in;
}
in >> c;
while (in && c == '"')
{
in.unget();
QuotedString qs;
if (in >> qs)
{
qsl.value.push_back(qs.value);
in >> c;
}
}
if (c != ']')
in.setstate(ios::failbit);
return in;
}
static Vec3d cubicInterpolate(const Vec3d& p0, const Vec3d& v0,
const Vec3d& p1, const Vec3d& v1,
double t)
{
return p0 + (((2.0 * (p0 - p1) + v1 + v0) * (t * t * t)) +
((3.0 * (p1 - p0) - 2.0 * v0 - v1) * (t * t)) +
(v0 * t));
}
double et2jd(double et)
{
return J2000 + et / 86400.0;
}
string etToString(double et)
{
char buf[200];
et2utc_c(et, "C", 3, sizeof(buf), buf);
return string(buf);
}
void printRecord(ostream& out, double et, const StateVector& state)
{
// < 1 second error around J2000
out << setprecision(12) << et2jd(et) << " ";
// < 1 meter error at 1 billion km
out << setprecision(12) << state.position << " ";
// < 0.1 mm/s error at 10 km/s
out << setprecision(8) << state.velocity << endl;
}
StateVector getStateVector(SpiceInt targetID,
double et,
const string& frameName,
SpiceInt observerID)
{
double stateVector[6];
double lightTime = 0.0;
spkgeo_c(targetID, et, frameName.c_str(), observerID,
stateVector, &lightTime);
return StateVector(stateVector);
}
// Convert a body name to a NAIF ID. Return true if the ID was
// found, and false otherwise.
bool bodyNameToId(const std::string& name, SpiceInt* naifId)
{
SpiceBoolean found = SPICEFALSE;
bodn2c_c(name.c_str(), naifId, &found);
if (found)
{
return true;
}
// Check to see whether the string is actually a numeric ID
istringstream str(name, istringstream::in);
int id = 0;
str >> id;
if (str.eof())
{
*naifId = id;
return true;
}
else
{
return false;
}
}
// Dump information about the xyzv file in the comment header
void writeCommentHeader(const Configuration& config,
ostream& out)
{
SpiceInt observerID = 0;
SpiceInt targetID = 0;
if (!bodyNameToId(config.observerName, &observerID))
return;
if (!bodyNameToId(config.targetName, &targetID))
return;
out << "# Celestia xyzv file generated by spice2xyzv\n";
out << "#\n";
time_t now = 0;
time(&now);
out << "# Creation date: " << ctime(&now);
out << "#\n";
out << "# SPICE kernel files used:\n";
for (vector<string>::const_iterator iter = config.kernelList.begin();
iter != config.kernelList.end(); iter++)
{
out << "# " << *iter << endl;
}
out << "#\n";
out << "# Start date: " << config.startDate << endl;
out << "# End date: " << config.endDate << endl;
out << "# Observer: " << config.observerName << " (" << observerID << ")" << endl;
out << "# Target: " << config.targetName << " (" << targetID << ")" << endl;
out << "# Frame: " << config.frameName << endl;
out << "#\n";
out << "# Min step size: " << config.minStepSize << " s" << endl;
out << "# Max step size: " << config.maxStepSize << " s" << endl;
out << "# Tolerance: " << config.tolerance << " km" << endl;
out << "#\n";
out << "# Records are <jd> <x> <y> <z> <vel x> <vel y> <vel z>\n";
out << "# Time is a TDB Julian date\n";
out << "# Position in km\n";
out << "# Velocity in km/sec\n";
out << endl;
}
bool convertSpkToXyzv(const Configuration& config,
ostream& out)
{
// Load the required SPICE kernels
for (vector<string>::const_iterator iter = config.kernelList.begin();
iter != config.kernelList.end(); iter++)
{
string pathname = config.kernelDirectory + "/" + *iter;
furnsh_c(pathname.c_str());
}
double startET = 0.0;
double endET = 0.0;
str2et_c(config.startDate.c_str(), &startET);
str2et_c(config.endDate.c_str(), &endET);
SpiceInt observerID = 0;
SpiceInt targetID = 0;
if (!bodyNameToId(config.observerName, &observerID))
{
cerr << "Observer object " << config.observerName << " not found. Aborting.\n";
return false;
}
if (!bodyNameToId(config.targetName, &targetID))
{
cerr << "Target object " << config.targetName << " not found. Aborting.\n";
return false;
}
double startStepSize = config.minStepSize; // samplingParams.startStep;
double maxStepSize = config.maxStepSize; // samplingParams.maxStep;
double minStepSize = config.minStepSize; // samplingParams.minStep;
double tolerance = config.tolerance; // samplingParams.tolerance;
double t = startET;
const double stepFactor = 1.25;
StateVector lastState = getStateVector(targetID, startET, config.frameName, observerID);
double et = startET;
printRecord(out, et, lastState);
int sampCount = 0;
int nTests = 0;
while (t < endET)
{
// Make sure that we don't go past the end of the sample interval
maxStepSize = min(maxStepSize, endET - t);
double dt = min(maxStepSize, startStepSize * 2.0);
StateVector s1 = getStateVector(targetID, t + dt, config.frameName, observerID);
double tmid = t + dt / 2.0;
Vec3d pTest = getStateVector(targetID, tmid, config.frameName, observerID).position;
Vec3d pInterp = cubicInterpolate(lastState.position,
lastState.velocity * dt,
s1.position,
s1.velocity * dt,
0.5);
nTests++;
double positionError = (pInterp - pTest).length();
// Error is greater than tolerance; decrease the step until the
// error is within the tolerance.
if (positionError > tolerance)
{
while (positionError > tolerance && dt > minStepSize)
{
dt /= stepFactor;
s1 = getStateVector(targetID, t + dt, config.frameName, observerID);
tmid = t + dt / 2.0;
Vec3d pTest = getStateVector(targetID, tmid, config.frameName, observerID).position;
pInterp = cubicInterpolate(lastState.position,
lastState.velocity * dt,
s1.position,
s1.velocity * dt,
0.5);
nTests++;
positionError = (pInterp - pTest).length();
}
}
else
{
// Error is less than the tolerance; increase the step size until
// the tolerance is just exceeded.
while (positionError < tolerance && dt < maxStepSize)
{
dt *= stepFactor;
dt = min(maxStepSize, dt);
s1 = getStateVector(targetID, t + dt, config.frameName, observerID);
tmid = t + dt / 2.0;
Vec3d pTest = getStateVector(targetID, tmid, config.frameName, observerID).position;
pInterp = cubicInterpolate(lastState.position,
lastState.velocity * dt,
s1.position,
s1.velocity * dt,
0.5);
nTests++;
positionError = (pInterp - pTest).length();
}
}
t = t + dt;
lastState = s1;
printRecord(out, t, lastState);
sampCount++;
}
return true;
}
bool readConfig(istream& in, Configuration& config)
{
QuotedString qs;
while (in && !in.eof())
{
string key;
in >> key;
if (in.eof())
return true;
if (!in.eof())
{
if (key == "StartDate")
{
if (in >> qs)
config.startDate = qs.value;
}
else if (key == "EndDate")
{
if (in >> qs)
config.endDate = qs.value;
}
else if (key == "Observer")
{
if (in >> qs)
config.observerName = qs.value;
}
else if (key == "Target")
{
if (in >> qs)
config.targetName = qs.value;
}
else if (key == "Frame")
{
if (in >> qs)
config.frameName = qs.value;
}
else if (key == "MinStep")
{
in >> config.minStepSize;
}
else if (key == "MaxStep")
{
in >> config.maxStepSize;
}
else if (key == "Tolerance")
{
in >> config.tolerance;
}
else if (key == "KernelDirectory")
{
if (in >> qs)
config.kernelDirectory = qs.value;
}
else if (key == "Kernels")
{
QuotedStringList qsl;
if (in >> qsl)
{
config.kernelList = qsl.value;
}
}
}
}
return in.good();
}
int main(int argc, char* argv[])
{
if (argc < 2)
{
cerr << "Usage: spice2xyzv <config filename> [output filename]\n";
return 1;
}
ifstream configFile(argv[1]);
if (!configFile)
{
cerr << "Error opening configuration file.\n";
return 1;
}
Configuration config;
if (!readConfig(configFile, config))
{
cerr << "Error in configuration file.\n";
return 1;
}
// Check that all required parameters are present.
if (config.startDate.empty())
{
cerr << "StartDate missing from configuration file.\n";
return 1;
}
if (config.endDate.empty())
{
cerr << "EndDate missing from configuration file.\n";
return 1;
}
if (config.targetName.empty())
{
cerr << "Target missing from configuration file.\n";
return 1;
}
if (config.observerName.empty())
{
cerr << "Observer missing from configuration file.\n";
return 1;
}
if (config.kernelList.empty())
{
cerr << "Kernels missing from configuration file.\n";
return 1;
}
// Load the leap second kernel
#if defined(_WIN32) || defined(NATIVE_OSX_APP)
furnsh_c("naif0012.tls");
#else
furnsh_c(CONFIG_DATA_DIR "/" "naif0012.tls");
#endif
writeCommentHeader(config, cout);
convertSpkToXyzv(config, cout);
return 0;
}