mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-07 04:00:37 -06:00
1374 lines
46 KiB
C++
1374 lines
46 KiB
C++
/*****************************************************************************************
|
|
* *
|
|
* OpenSpace *
|
|
* *
|
|
* Copyright (c) 2014-2023 *
|
|
* *
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
|
* software and associated documentation files (the "Software"), to deal in the Software *
|
|
* without restriction, including without limitation the rights to use, copy, modify, *
|
|
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
|
* permit persons to whom the Software is furnished to do so, subject to the following *
|
|
* conditions: *
|
|
* *
|
|
* The above copyright notice and this permission notice shall be included in all copies *
|
|
* or substantial portions of the Software. *
|
|
* *
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
|
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
|
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
|
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
|
****************************************************************************************/
|
|
|
|
#include <openspace/util/spicemanager.h>
|
|
|
|
#include <openspace/engine/globals.h>
|
|
#include <openspace/scripting/lualibrary.h>
|
|
#include <ghoul/fmt.h>
|
|
#include <ghoul/logging/logmanager.h>
|
|
#include <ghoul/filesystem/file.h>
|
|
#include <ghoul/filesystem/filesystem.h>
|
|
#include <ghoul/misc/assert.h>
|
|
#include <ghoul/misc/profiling.h>
|
|
#include <algorithm>
|
|
#include <filesystem>
|
|
#include "SpiceUsr.h"
|
|
#include "SpiceZpr.h"
|
|
|
|
namespace {
|
|
constexpr std::string_view _loggerCat = "SpiceManager";
|
|
|
|
// The value comes from
|
|
// http://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/getmsg_c.html
|
|
// as the maximum message length
|
|
constexpr unsigned SpiceErrorBufferSize = 1841;
|
|
|
|
const char* toString(openspace::SpiceManager::FieldOfViewMethod m) {
|
|
using SM = openspace::SpiceManager;
|
|
switch (m) {
|
|
case SM::FieldOfViewMethod::Ellipsoid: return "ELLIPSOID";
|
|
case SM::FieldOfViewMethod::Point: return "POINT";
|
|
default: throw ghoul::MissingCaseException();
|
|
}
|
|
}
|
|
|
|
const char* toString(openspace::SpiceManager::TerminatorType t) {
|
|
using SM = openspace::SpiceManager;
|
|
switch (t) {
|
|
case SM::TerminatorType::Umbral: return "UMBRAL";
|
|
case SM::TerminatorType::Penumbral: return "PENUMBRAL";
|
|
default: throw ghoul::MissingCaseException();
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
#include "spicemanager_lua.inl"
|
|
|
|
namespace openspace {
|
|
|
|
SpiceManager* SpiceManager::_instance = nullptr;
|
|
|
|
SpiceManager::SpiceException::SpiceException(std::string msg)
|
|
: ghoul::RuntimeError(std::move(msg), "Spice")
|
|
{
|
|
ghoul_assert(
|
|
SpiceManager::ref().exceptionHandling() == SpiceManager::UseException::Yes,
|
|
"No exceptions should be thrown when UseException is No"
|
|
);
|
|
}
|
|
|
|
SpiceManager::AberrationCorrection::AberrationCorrection(Type t, Direction d)
|
|
: type(t)
|
|
, direction(d)
|
|
{}
|
|
|
|
SpiceManager::AberrationCorrection::AberrationCorrection(const std::string& identifier) {
|
|
const static std::map<std::string, std::pair<Type, Direction>> Mapping = {
|
|
{ "NONE" , { Type::None, Direction::Reception } },
|
|
{ "LT" , { Type::LightTime, Direction::Reception } },
|
|
{ "LT+S" , { Type::LightTimeStellar, Direction::Reception } },
|
|
{ "CN" , { Type::ConvergedNewtonian, Direction::Reception } },
|
|
{ "CN+S" , { Type::ConvergedNewtonianStellar, Direction::Reception } },
|
|
{ "XLT" , { Type::LightTime, Direction::Transmission } },
|
|
{ "XLT+S" , { Type::LightTimeStellar, Direction::Transmission } },
|
|
{ "XCN" , { Type::ConvergedNewtonian, Direction::Transmission } },
|
|
{ "XCN+S" , { Type::ConvergedNewtonianStellar, Direction::Transmission } }
|
|
};
|
|
|
|
auto it = Mapping.find(identifier);
|
|
|
|
ghoul_assert(!identifier.empty(), "Identifier may not be empty");
|
|
ghoul_assert(it != Mapping.end(), fmt::format("Invalid identifer '{}'", identifier));
|
|
|
|
type = it->second.first;
|
|
direction = it->second.second;
|
|
}
|
|
|
|
SpiceManager::AberrationCorrection::operator const char*() const {
|
|
switch (type) {
|
|
case Type::None:
|
|
return "NONE";
|
|
case Type::LightTime:
|
|
return (direction == Direction::Reception) ? "LT" : "XLT";
|
|
case Type::LightTimeStellar:
|
|
return (direction == Direction::Reception) ? "LT+S" : "XLT+S";
|
|
case Type::ConvergedNewtonian:
|
|
return (direction == Direction::Reception) ? "CN" : "XCN";
|
|
case Type::ConvergedNewtonianStellar:
|
|
return (direction == Direction::Reception) ? "CN+S" : "XCN+S";
|
|
default:
|
|
throw ghoul::MissingCaseException();
|
|
}
|
|
}
|
|
|
|
SpiceManager::FieldOfViewMethod SpiceManager::fieldOfViewMethodFromString(
|
|
const std::string& method)
|
|
{
|
|
const static std::map<std::string, FieldOfViewMethod> Mapping = {
|
|
{ "ELLIPSOID", FieldOfViewMethod::Ellipsoid },
|
|
{ "POINT", FieldOfViewMethod::Point }
|
|
};
|
|
|
|
ghoul_assert(!method.empty(), "Method must not be empty");
|
|
|
|
return Mapping.at(method);
|
|
}
|
|
|
|
SpiceManager::TerminatorType SpiceManager::terminatorTypeFromString(
|
|
const std::string& type)
|
|
{
|
|
const static std::map<std::string, TerminatorType> Mapping = {
|
|
{ "UMBRAL", TerminatorType::Umbral },
|
|
{ "PENUMBRAL", TerminatorType::Penumbral }
|
|
};
|
|
|
|
ghoul_assert(!type.empty(), "Type must not be empty");
|
|
|
|
return Mapping.at(type);
|
|
}
|
|
|
|
SpiceManager::SpiceManager() {
|
|
// Set the SPICE library to not exit the program if an error occurs
|
|
erract_c("SET", 0, const_cast<char*>("REPORT"));
|
|
// But we do not want SPICE to print the errors, we will fetch them ourselves
|
|
errprt_c("SET", 0, const_cast<char*>("NONE"));
|
|
}
|
|
|
|
SpiceManager::~SpiceManager() {
|
|
for (const KernelInformation& i : _loadedKernels) {
|
|
unload_c(i.path.c_str());
|
|
}
|
|
|
|
// Set values back to default
|
|
erract_c("SET", 0, const_cast<char*>("DEFAULT"));
|
|
errprt_c("SET", 0, const_cast<char*>("DEFAULT"));
|
|
}
|
|
|
|
void SpiceManager::initialize() {
|
|
ghoul_assert(!isInitialized(), "SpiceManager is already initialized");
|
|
_instance = new SpiceManager;
|
|
}
|
|
|
|
void SpiceManager::deinitialize() {
|
|
ghoul_assert(isInitialized(), "SpiceManager is not initialized");
|
|
delete _instance;
|
|
_instance = nullptr;
|
|
}
|
|
|
|
bool SpiceManager::isInitialized() {
|
|
return _instance != nullptr;
|
|
}
|
|
|
|
SpiceManager& SpiceManager::ref() {
|
|
ghoul_assert(isInitialized(), "SpiceManager is not initialized");
|
|
return *_instance;
|
|
}
|
|
|
|
// This method checks if one of the previous SPICE methods has failed. If it has, an
|
|
// exception with the SPICE error message is thrown
|
|
// If an error occurred, true is returned, otherwise, false
|
|
void throwSpiceError(const std::string& errorMessage) {
|
|
if (openspace::SpiceManager::ref().exceptionHandling()) {
|
|
char buffer[SpiceErrorBufferSize];
|
|
getmsg_c("LONG", SpiceErrorBufferSize, buffer);
|
|
reset_c();
|
|
throw openspace::SpiceManager::SpiceException(errorMessage + ": " + buffer);
|
|
}
|
|
else {
|
|
reset_c();
|
|
}
|
|
}
|
|
|
|
SpiceManager::KernelHandle SpiceManager::loadKernel(std::string filePath) {
|
|
ghoul_assert(!filePath.empty(), "Empty file path");
|
|
ghoul_assert(
|
|
std::filesystem::is_regular_file(filePath),
|
|
fmt::format("File '{}' ({}) does not exist", filePath, absPath(filePath))
|
|
);
|
|
ghoul_assert(
|
|
std::filesystem::is_directory(std::filesystem::path(filePath).parent_path()),
|
|
fmt::format(
|
|
"File {} exists, but directory {} does not",
|
|
absPath(filePath), std::filesystem::path(filePath).parent_path()
|
|
)
|
|
);
|
|
|
|
std::filesystem::path path = absPath(std::move(filePath));
|
|
const auto it = std::find_if(
|
|
_loadedKernels.begin(),
|
|
_loadedKernels.end(),
|
|
[path](const KernelInformation& info) { return info.path == path; }
|
|
);
|
|
|
|
if (it != _loadedKernels.end()) {
|
|
it->refCount++;
|
|
return it->id;
|
|
}
|
|
|
|
// We need to set the current directory as meta-kernels are usually defined relative
|
|
// to the directory they reside in. The directory change is not necessary for regular
|
|
// kernels
|
|
std::filesystem::path currentDirectory = std::filesystem::current_path();
|
|
|
|
std::filesystem::path p = path.parent_path();
|
|
std::filesystem::current_path(p);
|
|
|
|
LINFO(fmt::format("Loading SPICE kernel {}", path));
|
|
// Load the kernel
|
|
furnsh_c(path.string().c_str());
|
|
|
|
// Reset the current directory to the previous one
|
|
std::filesystem::current_path(currentDirectory);
|
|
|
|
if (failed_c()) {
|
|
throwSpiceError("Kernel loading");
|
|
}
|
|
|
|
std::filesystem::path fileExtension = path.extension();
|
|
if (fileExtension == ".bc" || fileExtension == ".BC") {
|
|
findCkCoverage(path.string()); // binary ck kernel
|
|
}
|
|
else if (fileExtension == ".bsp" || fileExtension == ".BSP") {
|
|
findSpkCoverage(path.string()); // binary spk kernel
|
|
}
|
|
|
|
KernelHandle kernelId = ++_lastAssignedKernel;
|
|
ghoul_assert(kernelId != 0, fmt::format("Kernel Handle wrapped around to 0"));
|
|
_loadedKernels.push_back({ path.string(), kernelId, 1 });
|
|
return kernelId;
|
|
}
|
|
|
|
void SpiceManager::unloadKernel(KernelHandle kernelId) {
|
|
ghoul_assert(kernelId <= _lastAssignedKernel, "Invalid unassigned kernel");
|
|
ghoul_assert(kernelId != KernelHandle(0), "Invalid zero handle");
|
|
|
|
const auto it = std::find_if(
|
|
_loadedKernels.begin(),
|
|
_loadedKernels.end(),
|
|
[&kernelId](const KernelInformation& info) { return info.id == kernelId; }
|
|
);
|
|
|
|
if (it != _loadedKernels.end()) {
|
|
// If there was only one part interested in the kernel, we can unload it
|
|
if (it->refCount == 1) {
|
|
// No need to check for errors as we do not allow empty path names
|
|
LINFO(fmt::format("Unloading SPICE kernel {}", it->path));
|
|
unload_c(it->path.c_str());
|
|
_loadedKernels.erase(it);
|
|
}
|
|
// Otherwise, we hold on to it, but reduce the reference counter by 1
|
|
else {
|
|
it->refCount--;
|
|
LDEBUG(fmt::format("Reducing reference counter to: {}", it->refCount));
|
|
}
|
|
}
|
|
}
|
|
|
|
void SpiceManager::unloadKernel(std::string filePath) {
|
|
ghoul_assert(!filePath.empty(), "Empty filename");
|
|
|
|
std::filesystem::path path = absPath(std::move(filePath));
|
|
|
|
const auto it = std::find_if(
|
|
_loadedKernels.begin(),
|
|
_loadedKernels.end(),
|
|
[&path](const KernelInformation& info) { return info.path == path; }
|
|
);
|
|
|
|
if (it == _loadedKernels.end()) {
|
|
if (_useExceptions) {
|
|
throw SpiceException(
|
|
fmt::format("{} did not correspond to a loaded kernel", path)
|
|
);
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
// If there was only one part interested in the kernel, we can unload it
|
|
if (it->refCount == 1) {
|
|
LINFO(fmt::format("Unloading SPICE kernel {}", path));
|
|
unload_c(path.string().c_str());
|
|
_loadedKernels.erase(it);
|
|
}
|
|
else {
|
|
// Otherwise, we hold on to it, but reduce the reference counter by 1
|
|
it->refCount--;
|
|
LDEBUG(fmt::format("Reducing reference counter to: {}", it->refCount));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SpiceManager::hasSpkCoverage(const std::string& target, double et) const {
|
|
ghoul_assert(!target.empty(), "Empty target");
|
|
|
|
const int id = naifId(target);
|
|
// SOLAR SYSTEM BARYCENTER special case, implicitly included by Spice
|
|
if (id == 0) {
|
|
return true;
|
|
}
|
|
|
|
const auto it = _spkIntervals.find(id);
|
|
if (it != _spkIntervals.end()) {
|
|
const std::vector<std::pair<double, double>>& intervalVector = it->second;
|
|
for (const std::pair<double, double>& vecElement : intervalVector) {
|
|
if ((vecElement.first < et) && (vecElement.second > et)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::vector<std::pair<double, double>> SpiceManager::spkCoverage(
|
|
const std::string& target) const
|
|
{
|
|
ghoul_assert(!target.empty(), "Empty target");
|
|
|
|
const int id = naifId(target);
|
|
const auto it = _spkIntervals.find(id);
|
|
if (it != _spkIntervals.end()) {
|
|
return it->second;
|
|
}
|
|
else {
|
|
std::vector<std::pair<double, double>> emptyList;
|
|
return emptyList;
|
|
}
|
|
}
|
|
|
|
|
|
bool SpiceManager::hasCkCoverage(const std::string& frame, double et) const {
|
|
ghoul_assert(!frame.empty(), "Empty target");
|
|
|
|
const int id = frameId(frame);
|
|
const auto it = _ckIntervals.find(id);
|
|
if (it != _ckIntervals.end()) {
|
|
const std::vector<std::pair<double, double>>& intervalVector = it->second;
|
|
for (const std::pair<double, double>& i : intervalVector) {
|
|
if ((i.first < et) && (i.second > et)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::vector<std::pair<double, double>> SpiceManager::ckCoverage(
|
|
const std::string& target) const
|
|
{
|
|
ghoul_assert(!target.empty(), "Empty target");
|
|
|
|
int id = naifId(target);
|
|
const auto it = _ckIntervals.find(id);
|
|
if (it != _ckIntervals.end()) {
|
|
return it->second;
|
|
}
|
|
else {
|
|
id *= 1000;
|
|
const auto it2 = _ckIntervals.find(id);
|
|
if (it2 != _ckIntervals.end()) {
|
|
return it2->second;
|
|
}
|
|
else {
|
|
std::vector<std::pair<double, double>> emptyList;
|
|
return emptyList;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<std::pair<int, std::string>> SpiceManager::spiceBodies(
|
|
bool builtInFrames) const
|
|
{
|
|
std::vector<std::pair<int, std::string>> bodies;
|
|
|
|
constexpr int Frnmln = 33;
|
|
static SpiceInt idsetBuffer[SPICE_CELL_CTRLSZ + 8192];
|
|
static SpiceCell idset = {
|
|
SPICE_INT,
|
|
0,
|
|
8192,
|
|
0,
|
|
SPICETRUE,
|
|
SPICEFALSE,
|
|
SPICEFALSE,
|
|
&idsetBuffer,
|
|
&(idsetBuffer[SPICE_CELL_CTRLSZ])
|
|
};
|
|
|
|
SpiceChar frname[Frnmln];
|
|
|
|
for (SpiceInt i = 1; i <= 6; i++) {
|
|
if (i < 6) {
|
|
if (builtInFrames) {
|
|
bltfrm_c(i, &idset);
|
|
}
|
|
else {
|
|
kplfrm_c(i, &idset);
|
|
}
|
|
}
|
|
else {
|
|
if (builtInFrames) {
|
|
bltfrm_c(SPICE_FRMTYP_ALL, &idset);
|
|
}
|
|
else {
|
|
kplfrm_c(SPICE_FRMTYP_ALL, &idset);
|
|
}
|
|
}
|
|
|
|
for (SpiceInt j = 0; j < card_c(&idset); j++) {
|
|
frmnam_c(
|
|
(reinterpret_cast<SpiceInt*>(idset.data))[j],
|
|
Frnmln,
|
|
frname
|
|
);
|
|
bodies.push_back(
|
|
std::make_pair(
|
|
static_cast<long>(reinterpret_cast<SpiceInt*>(idset.data)[j]),
|
|
frname
|
|
)
|
|
);
|
|
}
|
|
}
|
|
return bodies;
|
|
}
|
|
|
|
bool SpiceManager::hasValue(int naifId, const std::string& item) const {
|
|
return bodfnd_c(naifId, item.c_str());
|
|
}
|
|
|
|
bool SpiceManager::hasValue(const std::string& body, const std::string& item) const {
|
|
ghoul_assert(!body.empty(), "Empty body");
|
|
ghoul_assert(!item.empty(), "Empty item");
|
|
|
|
int id = naifId(body);
|
|
return hasValue(id, item);
|
|
}
|
|
|
|
int SpiceManager::naifId(const std::string& body) const {
|
|
ghoul_assert(!body.empty(), "Empty body");
|
|
|
|
SpiceBoolean success;
|
|
SpiceInt id;
|
|
bods2c_c(body.c_str(), &id, &success);
|
|
if (!success && _useExceptions) {
|
|
throw SpiceException(fmt::format("Could not find NAIF ID of body '{}'", body));
|
|
}
|
|
return id;
|
|
}
|
|
|
|
bool SpiceManager::hasNaifId(const std::string& body) const {
|
|
ghoul_assert(!body.empty(), "Empty body");
|
|
|
|
SpiceBoolean success;
|
|
SpiceInt id;
|
|
bods2c_c(body.c_str(), &id, &success);
|
|
reset_c();
|
|
return success;
|
|
}
|
|
|
|
int SpiceManager::frameId(const std::string& frame) const {
|
|
ghoul_assert(!frame.empty(), "Empty frame");
|
|
|
|
SpiceInt id;
|
|
namfrm_c(frame.c_str(), &id);
|
|
if (id == 0 && _useExceptions) {
|
|
throw SpiceException(fmt::format("Could not find NAIF ID of frame '{}'", frame));
|
|
}
|
|
return id;
|
|
}
|
|
|
|
bool SpiceManager::hasFrameId(const std::string& frame) const {
|
|
ghoul_assert(!frame.empty(), "Empty frame");
|
|
|
|
SpiceInt id;
|
|
namfrm_c(frame.c_str(), &id);
|
|
return id != 0;
|
|
}
|
|
|
|
void getValueInternal(const std::string& body, const std::string& value, int size,
|
|
double* v)
|
|
{
|
|
ghoul_assert(!body.empty(), "Empty body");
|
|
ghoul_assert(!value.empty(), "Empty value");
|
|
ghoul_assert(v != nullptr, "Empty value pointer");
|
|
|
|
SpiceInt n;
|
|
bodvrd_c(body.c_str(), value.c_str(), size, &n, v);
|
|
|
|
if (failed_c()) {
|
|
throwSpiceError(
|
|
fmt::format("Error getting value '{}' for body '{}'", value, body)
|
|
);
|
|
}
|
|
}
|
|
|
|
void SpiceManager::getValue(const std::string& body, const std::string& value,
|
|
double& v) const
|
|
{
|
|
getValueInternal(body, value, 1, &v);
|
|
}
|
|
|
|
void SpiceManager::getValue(const std::string& body, const std::string& value,
|
|
glm::dvec2& v) const
|
|
{
|
|
getValueInternal(body, value, 2, glm::value_ptr(v));
|
|
}
|
|
|
|
void SpiceManager::getValue(const std::string& body, const std::string& value,
|
|
glm::dvec3& v) const
|
|
{
|
|
getValueInternal(body, value, 3, glm::value_ptr(v));
|
|
}
|
|
|
|
void SpiceManager::getValue(const std::string& body, const std::string& value,
|
|
glm::dvec4& v) const
|
|
{
|
|
getValueInternal(body, value, 4, glm::value_ptr(v));
|
|
}
|
|
|
|
void SpiceManager::getValue(const std::string& body, const std::string& value,
|
|
std::vector<double>& v) const
|
|
{
|
|
ghoul_assert(!v.empty(), "Array for values has to be preallocaed");
|
|
|
|
getValueInternal(body, value, static_cast<int>(v.size()), v.data());
|
|
}
|
|
|
|
double SpiceManager::spacecraftClockToET(const std::string& craft, double craftTicks) {
|
|
ghoul_assert(!craft.empty(), "Empty craft");
|
|
|
|
int craftId = naifId(craft);
|
|
double et;
|
|
sct2e_c(craftId, craftTicks, &et);
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error transforming spacecraft clock of '{}' at time {}", craft, craftTicks
|
|
));
|
|
}
|
|
return et;
|
|
}
|
|
|
|
double SpiceManager::ephemerisTimeFromDate(const std::string& timeString) const {
|
|
ghoul_assert(!timeString.empty(), "Empty timeString");
|
|
|
|
return ephemerisTimeFromDate(timeString.c_str());
|
|
}
|
|
|
|
double SpiceManager::ephemerisTimeFromDate(const char* timeString) const {
|
|
double et;
|
|
str2et_c(timeString, &et);
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format("Error converting date '{}'", timeString));
|
|
}
|
|
return et;
|
|
}
|
|
|
|
std::string SpiceManager::dateFromEphemerisTime(double ephemerisTime, const char* format)
|
|
{
|
|
constexpr int BufferSize = 128;
|
|
char Buffer[BufferSize];
|
|
std::memset(Buffer, char(0), BufferSize);
|
|
|
|
timout_c(ephemerisTime, format, BufferSize, Buffer);
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error converting ephemeris time '{}' to date with format '{}'",
|
|
ephemerisTime, format
|
|
));
|
|
}
|
|
if (Buffer[0] == '*') {
|
|
// The conversion failed and we need to use et2utc
|
|
constexpr int SecondsPrecision = 3;
|
|
et2utc_c(ephemerisTime, "C", SecondsPrecision, BufferSize, Buffer);
|
|
}
|
|
|
|
|
|
return std::string(Buffer);
|
|
}
|
|
|
|
glm::dvec3 SpiceManager::targetPosition(const std::string& target,
|
|
const std::string& observer,
|
|
const std::string& referenceFrame,
|
|
AberrationCorrection aberrationCorrection,
|
|
double ephemerisTime, double& lightTime) const
|
|
{
|
|
ghoul_assert(!target.empty(), "Target is not empty");
|
|
ghoul_assert(!observer.empty(), "Observer is not empty");
|
|
ghoul_assert(!referenceFrame.empty(), "Reference frame is not empty");
|
|
|
|
bool targetHasCoverage = hasSpkCoverage(target, ephemerisTime);
|
|
bool observerHasCoverage = hasSpkCoverage(observer, ephemerisTime);
|
|
if (!targetHasCoverage && !observerHasCoverage) {
|
|
if (_useExceptions) {
|
|
throw SpiceException(
|
|
fmt::format(
|
|
"Neither target '{}' nor observer '{}' has SPK coverage at time {}",
|
|
target, observer, ephemerisTime
|
|
)
|
|
);
|
|
}
|
|
else {
|
|
return glm::dvec3(0.0);
|
|
}
|
|
}
|
|
else if (targetHasCoverage && observerHasCoverage) {
|
|
glm::dvec3 position = glm::dvec3(0.0);
|
|
spkpos_c(
|
|
target.c_str(),
|
|
ephemerisTime,
|
|
referenceFrame.c_str(),
|
|
aberrationCorrection,
|
|
observer.c_str(),
|
|
glm::value_ptr(position),
|
|
&lightTime
|
|
);
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error getting position from '{}' to '{}' in frame '{}' at time {}",
|
|
target, observer, referenceFrame, ephemerisTime
|
|
));
|
|
}
|
|
return position;
|
|
}
|
|
else if (targetHasCoverage) {
|
|
// observer has no coverage
|
|
return getEstimatedPosition(
|
|
observer,
|
|
target,
|
|
referenceFrame,
|
|
aberrationCorrection,
|
|
ephemerisTime,
|
|
lightTime
|
|
) * -1.0;
|
|
}
|
|
else {
|
|
// target has no coverage
|
|
return getEstimatedPosition(
|
|
target,
|
|
observer,
|
|
referenceFrame,
|
|
aberrationCorrection,
|
|
ephemerisTime,
|
|
lightTime
|
|
);
|
|
}
|
|
}
|
|
|
|
glm::dvec3 SpiceManager::targetPosition(const std::string& target,
|
|
const std::string& observer,
|
|
const std::string& referenceFrame,
|
|
AberrationCorrection aberrationCorrection,
|
|
double ephemerisTime) const
|
|
{
|
|
double unused = 0.0;
|
|
return targetPosition(
|
|
target,
|
|
observer,
|
|
referenceFrame,
|
|
aberrationCorrection,
|
|
ephemerisTime,
|
|
unused
|
|
);
|
|
}
|
|
|
|
glm::dmat3 SpiceManager::frameTransformationMatrix(const std::string& from,
|
|
const std::string& to,
|
|
double ephemerisTime) const
|
|
{
|
|
ghoul_assert(!from.empty(), "From must not be empty");
|
|
ghoul_assert(!to.empty(), "To must not be empty");
|
|
|
|
// get rotation matrix from frame A - frame B
|
|
glm::dmat3 transform = glm::dmat3(1.0);
|
|
pxform_c(
|
|
from.c_str(),
|
|
to.c_str(),
|
|
ephemerisTime,
|
|
reinterpret_cast<double(*)[3]>(glm::value_ptr(transform))
|
|
);
|
|
|
|
if (failed_c()) {
|
|
throwSpiceError(
|
|
fmt::format("Error converting from frame '{}' to frame '{}' at time '{}'",
|
|
from, to, ephemerisTime
|
|
)
|
|
);
|
|
}
|
|
|
|
// The rox-major, column-major order are switched in GLM and SPICE, so we have to
|
|
// transpose the matrix before we can return it
|
|
return glm::transpose(transform);
|
|
}
|
|
|
|
SpiceManager::SurfaceInterceptResult SpiceManager::surfaceIntercept(
|
|
const std::string& target,
|
|
const std::string& observer,
|
|
const std::string& fovFrame,
|
|
const std::string& referenceFrame,
|
|
AberrationCorrection aberrationCorrection,
|
|
double ephemerisTime,
|
|
const glm::dvec3& directionVector) const
|
|
{
|
|
ghoul_assert(!target.empty(), "Target must not be empty");
|
|
ghoul_assert(!observer.empty(), "Observer must not be empty");
|
|
ghoul_assert(target != observer, "Target and observer must be different");
|
|
ghoul_assert(!fovFrame.empty(), "FOV frame must not be empty");
|
|
ghoul_assert(!referenceFrame.empty(), "Reference frame must not be empty");
|
|
ghoul_assert(directionVector != glm::dvec3(0.0), "Direction vector must not be zero");
|
|
|
|
const std::string ComputationMethod = "ELLIPSOID";
|
|
|
|
SurfaceInterceptResult result;
|
|
|
|
SpiceBoolean found;
|
|
sincpt_c(ComputationMethod.c_str(),
|
|
target.c_str(),
|
|
ephemerisTime,
|
|
referenceFrame.c_str(),
|
|
aberrationCorrection,
|
|
observer.c_str(),
|
|
fovFrame.c_str(),
|
|
glm::value_ptr(directionVector),
|
|
glm::value_ptr(result.surfaceIntercept),
|
|
&result.interceptEpoch,
|
|
glm::value_ptr(result.surfaceVector),
|
|
&found
|
|
);
|
|
result.interceptFound = (found == SPICETRUE);
|
|
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error retrieving surface intercept on target '{}' viewed from observer '{}' "
|
|
"in reference frame '{}' at time '{}'",
|
|
target, observer, referenceFrame, ephemerisTime
|
|
));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool SpiceManager::isTargetInFieldOfView(const std::string& target,
|
|
const std::string& observer,
|
|
const std::string& referenceFrame,
|
|
const std::string& instrument,
|
|
FieldOfViewMethod method,
|
|
AberrationCorrection aberrationCorrection,
|
|
double& ephemerisTime) const
|
|
{
|
|
ghoul_assert(!target.empty(), "Target must not be empty");
|
|
ghoul_assert(!observer.empty(), "Observer must not be empty");
|
|
ghoul_assert(target != observer, "Target and observer must be different");
|
|
ghoul_assert(!referenceFrame.empty(), "Reference frame must not be empty");
|
|
ghoul_assert(!instrument.empty(), "Instrument must not be empty");
|
|
|
|
int visible;
|
|
fovtrg_c(instrument.c_str(),
|
|
target.c_str(),
|
|
toString(method),
|
|
referenceFrame.c_str(),
|
|
aberrationCorrection,
|
|
observer.c_str(),
|
|
&ephemerisTime,
|
|
&visible
|
|
);
|
|
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Checking if target '{}' is in view of instrument '{}' failed",
|
|
target, instrument
|
|
));
|
|
}
|
|
|
|
return visible == SPICETRUE;
|
|
}
|
|
|
|
SpiceManager::TargetStateResult SpiceManager::targetState(const std::string& target,
|
|
const std::string& observer,
|
|
const std::string& referenceFrame,
|
|
AberrationCorrection aberrationCorrection,
|
|
double ephemerisTime) const
|
|
{
|
|
ghoul_assert(!target.empty(), "Target must not be empty");
|
|
ghoul_assert(!observer.empty(), "Observer must not be empty");
|
|
ghoul_assert(!referenceFrame.empty(), "Reference frame must not be empty");
|
|
|
|
TargetStateResult result;
|
|
result.lightTime = 0.0;
|
|
|
|
double buffer[6];
|
|
|
|
spkezr_c(
|
|
target.c_str(),
|
|
ephemerisTime,
|
|
referenceFrame.c_str(),
|
|
aberrationCorrection,
|
|
observer.c_str(),
|
|
buffer,
|
|
&result.lightTime
|
|
);
|
|
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error retrieving state of target '{}' viewed from observer '{}' in "
|
|
"reference frame '{}' at time '{}'",
|
|
target, observer, referenceFrame, ephemerisTime
|
|
));
|
|
}
|
|
|
|
memmove(glm::value_ptr(result.position), buffer, sizeof(double) * 3);
|
|
memmove(glm::value_ptr(result.velocity), buffer + 3, sizeof(double) * 3);
|
|
return result;
|
|
}
|
|
|
|
SpiceManager::TransformMatrix SpiceManager::stateTransformMatrix(
|
|
const std::string& sourceFrame,
|
|
const std::string& destinationFrame,
|
|
double ephemerisTime) const
|
|
{
|
|
ghoul_assert(!sourceFrame.empty(), "sourceFrame must not be empty");
|
|
ghoul_assert(!destinationFrame.empty(), "toFrame must not be empty");
|
|
|
|
TransformMatrix m;
|
|
sxform_c(
|
|
sourceFrame.c_str(),
|
|
destinationFrame.c_str(),
|
|
ephemerisTime,
|
|
reinterpret_cast<double(*)[6]>(m.data())
|
|
);
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error retrieved state transform matrix from frame '{}' to frame '{}' at "
|
|
"time '{}'",
|
|
sourceFrame, destinationFrame, ephemerisTime
|
|
));
|
|
}
|
|
return m;
|
|
}
|
|
|
|
glm::dmat3 SpiceManager::positionTransformMatrix(const std::string& sourceFrame,
|
|
const std::string& destinationFrame,
|
|
double ephemerisTime) const
|
|
{
|
|
ghoul_assert(!sourceFrame.empty(), "sourceFrame must not be empty");
|
|
ghoul_assert(!destinationFrame.empty(), "destinationFrame must not be empty");
|
|
|
|
glm::dmat3 result = glm::dmat3(1.0);
|
|
pxform_c(
|
|
sourceFrame.c_str(),
|
|
destinationFrame.c_str(),
|
|
ephemerisTime,
|
|
reinterpret_cast<double(*)[3]>(glm::value_ptr(result))
|
|
);
|
|
|
|
if (failed_c()) {
|
|
throwSpiceError("");
|
|
}
|
|
SpiceBoolean success = !(failed_c());
|
|
reset_c();
|
|
if (!success) {
|
|
result = getEstimatedTransformMatrix(
|
|
sourceFrame,
|
|
destinationFrame,
|
|
ephemerisTime
|
|
);
|
|
}
|
|
|
|
return glm::transpose(result);
|
|
}
|
|
|
|
glm::dmat3 SpiceManager::positionTransformMatrix(const std::string& sourceFrame,
|
|
const std::string& destinationFrame,
|
|
double ephemerisTimeFrom,
|
|
double ephemerisTimeTo) const
|
|
{
|
|
ghoul_assert(!sourceFrame.empty(), "sourceFrame must not be empty");
|
|
ghoul_assert(!destinationFrame.empty(), "destinationFrame must not be empty");
|
|
|
|
glm::dmat3 result = glm::dmat3(1.0);
|
|
|
|
pxfrm2_c(
|
|
sourceFrame.c_str(),
|
|
destinationFrame.c_str(),
|
|
ephemerisTimeFrom,
|
|
ephemerisTimeTo,
|
|
reinterpret_cast<double(*)[3]>(glm::value_ptr(result))
|
|
);
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error retrieving position transform matrix from '{}' at time '{}' to frame "
|
|
"'{}' at time '{}'",
|
|
sourceFrame, ephemerisTimeFrom, destinationFrame, ephemerisTimeTo
|
|
));
|
|
}
|
|
return glm::transpose(result);
|
|
}
|
|
|
|
SpiceManager::FieldOfViewResult
|
|
SpiceManager::fieldOfView(const std::string& instrument) const
|
|
{
|
|
ghoul_assert(!instrument.empty(), "Instrument must not be empty");
|
|
return fieldOfView(naifId(instrument));
|
|
}
|
|
|
|
SpiceManager::FieldOfViewResult SpiceManager::fieldOfView(int instrument) const {
|
|
constexpr int MaxBoundsSize = 64;
|
|
constexpr int BufferSize = 128;
|
|
|
|
FieldOfViewResult res;
|
|
|
|
SpiceInt nrReturned;
|
|
double boundsArr[MaxBoundsSize][3];
|
|
char fovShapeBuffer[BufferSize];
|
|
char frameNameBuffer[BufferSize];
|
|
getfov_c(instrument, // instrument id
|
|
MaxBoundsSize, // maximum size for the bounds vector
|
|
BufferSize, // maximum size for the fov shape buffer
|
|
BufferSize, // maximum size for the frame name buffer
|
|
fovShapeBuffer, // the fov shape buffer
|
|
frameNameBuffer, // the frame name buffer
|
|
glm::value_ptr(res.boresightVector), // the boresight vector
|
|
&nrReturned, // the number of returned array values
|
|
boundsArr // the bounds
|
|
);
|
|
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error getting field-of-view parameters for instrument '{}'", instrument
|
|
));
|
|
return res;
|
|
}
|
|
|
|
res.bounds.reserve(nrReturned);
|
|
for (int i = 0; i < nrReturned; ++i) {
|
|
res.bounds.emplace_back(boundsArr[i][0], boundsArr[i][1], boundsArr[i][2]);
|
|
}
|
|
|
|
std::string shape = std::string(fovShapeBuffer);
|
|
static const std::map<std::string, FieldOfViewResult::Shape> Map = {
|
|
{ "POLYGON", FieldOfViewResult::Shape::Polygon },
|
|
{ "RECTANGLE" , FieldOfViewResult::Shape::Rectangle },
|
|
{ "CIRCLE", FieldOfViewResult::Shape::Circle },
|
|
{ "ELLIPSE", FieldOfViewResult::Shape::Ellipse }
|
|
};
|
|
res.shape = Map.at(shape);
|
|
res.frameName = std::string(frameNameBuffer);
|
|
|
|
return res;
|
|
}
|
|
|
|
SpiceManager::TerminatorEllipseResult SpiceManager::terminatorEllipse(
|
|
const std::string& target,
|
|
const std::string& observer,
|
|
const std::string& frame,
|
|
const std::string& lightSource,
|
|
TerminatorType terminatorType,
|
|
AberrationCorrection aberrationCorrection,
|
|
double ephemerisTime,
|
|
int numberOfTerminatorPoints)
|
|
{
|
|
ghoul_assert(!target.empty(), "Target must not be empty");
|
|
ghoul_assert(!observer.empty(), "Observer must not be empty");
|
|
ghoul_assert(!frame.empty(), "Frame must not be empty");
|
|
ghoul_assert(!lightSource.empty(), "Light source must not be empty");
|
|
ghoul_assert(numberOfTerminatorPoints >= 1, "Terminator points must be >= 1");
|
|
|
|
TerminatorEllipseResult res;
|
|
|
|
// Warning: This assumes std::vector<glm::dvec3> to have all values memory contiguous
|
|
res.terminatorPoints.resize(numberOfTerminatorPoints);
|
|
|
|
edterm_c(
|
|
toString(terminatorType),
|
|
lightSource.c_str(),
|
|
target.c_str(),
|
|
ephemerisTime,
|
|
frame.c_str(),
|
|
aberrationCorrection,
|
|
observer.c_str(),
|
|
numberOfTerminatorPoints,
|
|
&res.targetEphemerisTime,
|
|
glm::value_ptr(res.observerPosition),
|
|
reinterpret_cast<double(*)[3]>(res.terminatorPoints.data())
|
|
);
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error getting terminator ellipse for target '{}' from observer '{}' in "
|
|
"frame '{}' with light source '{}' at time '{}'",
|
|
target, observer, frame, lightSource, ephemerisTime
|
|
));
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void SpiceManager::findCkCoverage(const std::string& path) {
|
|
ghoul_assert(!path.empty(), "Empty file path");
|
|
ghoul_assert(
|
|
std::filesystem::is_regular_file(path),
|
|
fmt::format("File '{}' does not exist", path)
|
|
);
|
|
|
|
constexpr unsigned int MaxObj = 1024;
|
|
constexpr unsigned int WinSiz = 16384;
|
|
|
|
#if defined __clang__
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wold-style-cast"
|
|
#elif defined __GNUC__
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
|
#endif
|
|
|
|
SPICEINT_CELL(ids, MaxObj);
|
|
SPICEDOUBLE_CELL(cover, WinSiz);
|
|
|
|
ckobj_c(path.c_str(), &ids);
|
|
if (failed_c()) {
|
|
throwSpiceError("Error finding Ck Coverage");
|
|
}
|
|
|
|
for (SpiceInt i = 0; i < card_c(&ids); ++i) {
|
|
const SpiceInt frame = SPICE_CELL_ELEM_I(&ids, i);
|
|
|
|
#if defined __clang__
|
|
#pragma clang diagnostic pop
|
|
#elif defined __GNUC__
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
scard_c(0, &cover);
|
|
ckcov_c(path.c_str(), frame, SPICEFALSE, "SEGMENT", 0.0, "TDB", &cover);
|
|
if (failed_c()) {
|
|
throwSpiceError("Error finding Ck Coverage");
|
|
}
|
|
|
|
// Get the number of intervals in the coverage window.
|
|
const SpiceInt numberOfIntervals = wncard_c(&cover);
|
|
|
|
for (SpiceInt j = 0; j < numberOfIntervals; ++j) {
|
|
// Get the endpoints of the jth interval.
|
|
SpiceDouble b, e;
|
|
wnfetd_c(&cover, j, &b, &e);
|
|
if (failed_c()) {
|
|
throwSpiceError("Error finding Ck Coverage");
|
|
}
|
|
|
|
_ckCoverageTimes[frame].insert(e);
|
|
_ckCoverageTimes[frame].insert(b);
|
|
_ckIntervals[frame].emplace_back(b, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SpiceManager::findSpkCoverage(const std::string& path) {
|
|
ghoul_assert(!path.empty(), "Empty file path");
|
|
ghoul_assert(
|
|
std::filesystem::is_regular_file(path),
|
|
fmt::format("File '{}' does not exist", path)
|
|
);
|
|
|
|
constexpr unsigned int MaxObj = 1024;
|
|
constexpr unsigned int WinSiz = 16384;
|
|
|
|
#if defined __clang__
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wold-style-cast"
|
|
#elif defined __GNUC__
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
|
#endif
|
|
|
|
SPICEINT_CELL(ids, MaxObj);
|
|
SPICEDOUBLE_CELL(cover, WinSiz);
|
|
|
|
spkobj_c(path.c_str(), &ids);
|
|
if (failed_c()) {
|
|
throwSpiceError("Error finding Spk ID for coverage");
|
|
}
|
|
|
|
for (SpiceInt i = 0; i < card_c(&ids); ++i) {
|
|
const SpiceInt obj = SPICE_CELL_ELEM_I(&ids, i);
|
|
|
|
#if defined __clang__
|
|
#pragma clang diagnostic pop
|
|
#elif defined __GNUC__
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|
|
scard_c(0, &cover);
|
|
spkcov_c(path.c_str(), obj, &cover);
|
|
if (failed_c()) {
|
|
throwSpiceError("Error finding Spk coverage");
|
|
}
|
|
|
|
// Get the number of intervals in the coverage window.
|
|
const SpiceInt numberOfIntervals = wncard_c(&cover);
|
|
|
|
for (SpiceInt j = 0; j < numberOfIntervals; ++j) {
|
|
//Get the endpoints of the jth interval.
|
|
SpiceDouble b, e;
|
|
wnfetd_c(&cover, j, &b, &e);
|
|
if (failed_c()) {
|
|
throwSpiceError("Error finding Spk coverage");
|
|
}
|
|
|
|
// insert all into coverage time set, the windows could be merged @AA
|
|
_spkCoverageTimes[obj].insert(e);
|
|
_spkCoverageTimes[obj].insert(b);
|
|
_spkIntervals[obj].emplace_back(b, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
glm::dvec3 SpiceManager::getEstimatedPosition(const std::string& target,
|
|
const std::string& observer,
|
|
const std::string& referenceFrame,
|
|
AberrationCorrection aberrationCorrection,
|
|
double ephemerisTime,
|
|
double& lightTime) const
|
|
{
|
|
ZoneScoped
|
|
|
|
ghoul_assert(!target.empty(), "Target must not be empty");
|
|
ghoul_assert(!observer.empty(), "Observer must not be empty");
|
|
ghoul_assert(!referenceFrame.empty(), "Reference frame must not be empty");
|
|
ghoul_assert(target != observer, "Target and observer must be different");
|
|
|
|
int targetId = naifId(target);
|
|
|
|
if (targetId == 0) {
|
|
// SOLAR SYSTEM BARYCENTER special case, no definition in kernels
|
|
return glm::dvec3(0.0);
|
|
}
|
|
|
|
if (_spkCoverageTimes.find(targetId) == _spkCoverageTimes.end()) {
|
|
if (_useExceptions) {
|
|
// no coverage
|
|
throw SpiceException(fmt::format("No position for '{}' at any time", target));
|
|
}
|
|
else {
|
|
return glm::dvec3(0.0);
|
|
}
|
|
}
|
|
|
|
const std::set<double>& coveredTimes = _spkCoverageTimes.find(targetId)->second;
|
|
|
|
glm::dvec3 pos = glm::dvec3(0.0);
|
|
if (coveredTimes.lower_bound(ephemerisTime) == coveredTimes.begin()) {
|
|
// coverage later, fetch first position
|
|
spkpos_c(
|
|
target.c_str(),
|
|
*(coveredTimes.begin()),
|
|
referenceFrame.c_str(),
|
|
aberrationCorrection,
|
|
observer.c_str(),
|
|
glm::value_ptr(pos),
|
|
&lightTime
|
|
);
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error estimating position for '{}' with observer '{}' in frame '{}'",
|
|
target, observer, referenceFrame
|
|
));
|
|
}
|
|
|
|
}
|
|
else if (coveredTimes.upper_bound(ephemerisTime) == coveredTimes.end()) {
|
|
// coverage earlier, fetch last position
|
|
spkpos_c(
|
|
target.c_str(),
|
|
*(coveredTimes.rbegin()),
|
|
referenceFrame.c_str(),
|
|
aberrationCorrection,
|
|
observer.c_str(),
|
|
glm::value_ptr(pos),
|
|
&lightTime
|
|
);
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error estimating position for '{}' with observer '{}' in frame '{}'",
|
|
target, observer, referenceFrame
|
|
));
|
|
}
|
|
}
|
|
else {
|
|
// coverage both earlier and later, interpolate these positions
|
|
glm::dvec3 posEarlier = glm::dvec3(0.0);
|
|
double ltEarlier;
|
|
double timeEarlier = *std::prev((coveredTimes.lower_bound(ephemerisTime)));
|
|
spkpos_c(
|
|
target.c_str(),
|
|
timeEarlier,
|
|
referenceFrame.c_str(),
|
|
aberrationCorrection,
|
|
observer.c_str(),
|
|
glm::value_ptr(posEarlier),
|
|
<Earlier
|
|
);
|
|
|
|
glm::dvec3 posLater = glm::dvec3(0.0);
|
|
double ltLater;
|
|
double timeLater = *(coveredTimes.upper_bound(ephemerisTime));
|
|
spkpos_c(
|
|
target.c_str(),
|
|
timeLater,
|
|
referenceFrame.c_str(),
|
|
aberrationCorrection,
|
|
observer.c_str(),
|
|
glm::value_ptr(posLater),
|
|
<Later
|
|
);
|
|
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error estimating position for '{}' with observer '{}' in frame '{}'",
|
|
target, observer, referenceFrame
|
|
));
|
|
}
|
|
|
|
// linear interpolation
|
|
const double t = (ephemerisTime - timeEarlier) / (timeLater - timeEarlier);
|
|
pos = posEarlier * (1.0 - t) + posLater * t;
|
|
lightTime = ltEarlier * (1.0 - t) + ltLater * t;
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
glm::dmat3 SpiceManager::getEstimatedTransformMatrix(const std::string& fromFrame,
|
|
const std::string& toFrame,
|
|
double time) const
|
|
{
|
|
glm::dmat3 result = glm::dmat3(1.0);
|
|
const int idFrame = frameId(fromFrame);
|
|
|
|
if (_ckCoverageTimes.find(idFrame) == _ckCoverageTimes.end()) {
|
|
if (_useExceptions) {
|
|
// no coverage
|
|
throw SpiceException(fmt::format(
|
|
"No data available for transform matrix from '{}' to '{}' at any time",
|
|
fromFrame, toFrame
|
|
));
|
|
}
|
|
else {
|
|
return glm::dmat3(1.0);
|
|
}
|
|
}
|
|
|
|
std::set<double> coveredTimes = _ckCoverageTimes.find(idFrame)->second;
|
|
|
|
if (coveredTimes.lower_bound(time) == coveredTimes.begin()) {
|
|
// coverage later, fetch first transform
|
|
pxform_c(
|
|
fromFrame.c_str(),
|
|
toFrame.c_str(),
|
|
*(coveredTimes.begin()),
|
|
reinterpret_cast<double(*)[3]>(glm::value_ptr(result))
|
|
);
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error estimating transform matrix from '{}' to from '{}' at time '{}'",
|
|
fromFrame, toFrame, time
|
|
));
|
|
}
|
|
}
|
|
else if (coveredTimes.upper_bound(time) == coveredTimes.end()) {
|
|
// coverage earlier, fetch last transform
|
|
pxform_c(
|
|
fromFrame.c_str(),
|
|
toFrame.c_str(),
|
|
*(coveredTimes.rbegin()),
|
|
reinterpret_cast<double(*)[3]>(glm::value_ptr(result))
|
|
);
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error estimating transform matrix from frame '{}' to '{}' at time '{}'",
|
|
fromFrame, toFrame, time
|
|
));
|
|
}
|
|
}
|
|
else {
|
|
// coverage both earlier and later, interpolate these transformations
|
|
double earlier = *std::prev((coveredTimes.lower_bound(time)));
|
|
double later = *(coveredTimes.upper_bound(time));
|
|
|
|
glm::dmat3 earlierTransform = glm::dmat3(1.0);
|
|
pxform_c(
|
|
fromFrame.c_str(),
|
|
toFrame.c_str(),
|
|
earlier,
|
|
reinterpret_cast<double(*)[3]>(glm::value_ptr(earlierTransform))
|
|
);
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error estimating transform matrix from frame '{}' to '{}' at time '{}'",
|
|
fromFrame, toFrame, time
|
|
));
|
|
}
|
|
|
|
glm::dmat3 laterTransform = glm::dmat3(1.0);
|
|
pxform_c(
|
|
fromFrame.c_str(),
|
|
toFrame.c_str(),
|
|
later,
|
|
reinterpret_cast<double(*)[3]>(glm::value_ptr(laterTransform))
|
|
);
|
|
if (failed_c()) {
|
|
throwSpiceError(fmt::format(
|
|
"Error estimating transform matrix from frame '{}' to '{}' at time '{}'",
|
|
fromFrame, toFrame, time
|
|
));
|
|
}
|
|
|
|
const double t = (time - earlier) / (later - earlier);
|
|
result = earlierTransform * (1.0 - t) + laterTransform * t;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void SpiceManager::setExceptionHandling(UseException useException) {
|
|
_useExceptions = useException;
|
|
}
|
|
|
|
SpiceManager::UseException SpiceManager::exceptionHandling() const {
|
|
return _useExceptions;
|
|
}
|
|
|
|
scripting::LuaLibrary SpiceManager::luaLibrary() {
|
|
return {
|
|
"spice",
|
|
{
|
|
codegen::lua::LoadKernel,
|
|
codegen::lua::UnloadKernel,
|
|
codegen::lua::SpiceBodies,
|
|
codegen::lua::RotationMatrix,
|
|
codegen::lua::Position
|
|
}
|
|
};
|
|
}
|
|
|
|
} // namespace openspace
|