/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2017 * * * * 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 #include #include #include #include #include #include #include #include "SpiceUsr.h" #include "SpiceZpr.h" namespace { const char* _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 const unsigned SpiceErrorBufferSize = 1841; // 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 bool throwOnSpiceError(std::string errorMessage) { SpiceBoolean failed = failed_c(); if (openspace::SpiceManager::ref().exceptionHandling()) { if (failed) { char buffer[SpiceErrorBufferSize]; getmsg_c("LONG", SpiceErrorBufferSize, buffer); reset_c(); throw openspace::SpiceManager::SpiceException( errorMessage + ": " + buffer ); } else { return false; } } else { return failed; } } const char* toString(openspace::SpiceManager::FieldOfViewMethod m) { switch (m) { case openspace::SpiceManager::FieldOfViewMethod::Ellipsoid: return "ELLIPSOID"; case openspace::SpiceManager::FieldOfViewMethod::Point: return "POINT"; default: throw ghoul::MissingCaseException(); } } const char* toString(openspace::SpiceManager::TerminatorType t) { switch (t) { case openspace::SpiceManager::TerminatorType::Umbral: return "UMBRAL"; case openspace::SpiceManager::TerminatorType::Penumbral: return "PENUMBRAL"; default: throw ghoul::MissingCaseException(); } } } using fmt::format; using std::string; #include "spicemanager_lua.inl" namespace openspace { SpiceManager::SpiceException::SpiceException(const string& msg) : ghoul::RuntimeError(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> 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(), 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 string& method) { const static std::map 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 string& type) { const static std::map Mapping = { { "UMBRAL", TerminatorType::Umbral }, { "PENUMBRAL", TerminatorType::Penumbral } }; ghoul_assert(!type.empty(), "Type must not be empty"); return Mapping.at(type); } SpiceManager::SpiceManager() : _useExceptions(UseException::Yes) { // Set the SPICE library to not exit the program if an error occurs erract_c("SET", 0, const_cast("REPORT")); // But we do not want SPICE to print the errors, we will fetch them ourselves errprt_c("SET", 0, const_cast("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("DEFAULT")); errprt_c("SET", 0, const_cast("DEFAULT")); } SpiceManager::KernelHandle SpiceManager::loadKernel(string filePath) { ghoul_assert(!filePath.empty(), "Empty file path"); ghoul_assert( FileSys.fileExists(filePath), format( "File '{}' ('{}') does not exist", filePath, absPath(filePath) ) ); ghoul_assert( FileSys.directoryExists(ghoul::filesystem::File(filePath).directoryName()), format( "File '{}' exists, but directory '{}' doesn't", absPath(filePath), ghoul::filesystem::File(filePath).directoryName() ) ); string path = absPath(filePath); auto it = std::find_if( _loadedKernels.begin(), _loadedKernels.end(), [path](const KernelInformation& info) { return info.path == path; }); if (it != _loadedKernels.end()) { it->refCount++; LDEBUG( format( "Kernel '{}' was already loaded. New reference count: {}", path, 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 ghoul::filesystem::Directory currentDirectory = FileSys.currentDirectory(); using RawPath = ghoul::filesystem::File::RawPath; string fileDirectory = ghoul::filesystem::File(path, RawPath::Yes).directoryName(); FileSys.setCurrentDirectory(fileDirectory); LINFO("Loading SPICE kernel '" << path << "'"); // Load the kernel furnsh_c(path.c_str()); // Reset the current directory to the previous one FileSys.setCurrentDirectory(currentDirectory); throwOnSpiceError("Kernel loading"); string fileExtension = ghoul::filesystem::File(path, RawPath::Yes).fileExtension(); if (fileExtension == "bc" || fileExtension == "BC") { findCkCoverage(path); // binary ck kernel } else if (fileExtension == "bsp" || fileExtension == "BSP") { findSpkCoverage(path); // binary spk kernel } KernelHandle kernelId = ++_lastAssignedKernel; ghoul_assert(kernelId != 0, fmt::format("Kernel Handle wrapped around to 0")); _loadedKernels.push_back({std::move(path), kernelId, 1}); return kernelId; } void SpiceManager::unloadKernel(KernelHandle kernelId) { ghoul_assert(kernelId <= _lastAssignedKernel, "Invalid unassigned kernel"); ghoul_assert(kernelId != KernelHandle(0), "Invalid zero handle"); 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(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(format("Reducing reference counter to: {}", it->refCount)); } } } void SpiceManager::unloadKernel(std::string filePath) { ghoul_assert(!filePath.empty(), "Empty filename"); string path = absPath(filePath); 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( 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(format("Unloading SPICE kernel '{}'", path)); unload_c(path.c_str()); _loadedKernels.erase(it); } else { // Otherwise, we hold on to it, but reduce the reference counter by 1 it->refCount--; LDEBUG(format("Reducing reference counter to: {}", it->refCount)); } } } bool SpiceManager::hasSpkCoverage(const string& target, double et) const { ghoul_assert(!target.empty(), "Empty target"); int id = naifId(target); auto it = _spkIntervals.find(id); if (it != _spkIntervals.end()) { std::vector> intervalVector = it->second; for (const auto& vecElement : intervalVector) { if (vecElement.first < et && vecElement.second > et) { return true; } } } return false; } bool SpiceManager::hasCkCoverage(const string& frame, double et) const { ghoul_assert(!frame.empty(), "Empty target"); int id = frameId(frame); auto it = _ckIntervals.find(id); if (it != _ckIntervals.end()) { std::vector> intervalVector = it->second; for (const auto& i : intervalVector) { if (i.first < et && i.second > et) { return true; } } } return false; } 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(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(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); throwOnSpiceError( 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& v) const { ghoul_assert(!v.empty(), "Array for values has to be preallocaed"); getValueInternal(body, value, static_cast(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); throwOnSpiceError(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"); double et; str2et_c(timeString.c_str(), &et); throwOnSpiceError(format("Error converting date '{}'", timeString)); return et; } string SpiceManager::dateFromEphemerisTime(double ephemerisTime, const string& formatString) const { ghoul_assert(!formatString.empty(), "Format is empty"); static const int BufferSize = 256; SpiceChar buffer[BufferSize]; timout_c(ephemerisTime, formatString.c_str(), BufferSize - 1, buffer); throwOnSpiceError( format("Error converting ephemeris time '{}' to date with format '{}'", ephemerisTime, formatString ) ); 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( format("Neither target '{}' nor observer '{}' has SPK coverage at time {}", target, observer, ephemerisTime ) ); } else { return glm::dvec3(); } } else if (targetHasCoverage && observerHasCoverage) { glm::dvec3 position; spkpos_c( target.c_str(), ephemerisTime, referenceFrame.c_str(), aberrationCorrection, observer.c_str(), glm::value_ptr(position), &lightTime ); throwOnSpiceError(format( "Error getting position from '{}' to '{}' in reference 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; pxform_c( from.c_str(), to.c_str(), ephemerisTime, reinterpret_cast(glm::value_ptr(transform)) ); throwOnSpiceError( 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); throwOnSpiceError(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 ); throwOnSpiceError(format( "Checking if target '{}' is in view of instrument '{}' failed", target, instrument )); return visible == SPICETRUE; } bool SpiceManager::isTargetInFieldOfView(const std::string& target, const std::string& observer, const std::string& instrument, FieldOfViewMethod method, AberrationCorrection aberrationCorrection, double& ephemerisTime) const { return isTargetInFieldOfView( target, observer, frameFromBody(target), instrument, method, aberrationCorrection, ephemerisTime ); } 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 ); throwOnSpiceError(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 string& fromFrame, const string& toFrame, double ephemerisTime) const { ghoul_assert(!fromFrame.empty(), "fromFrame must not be empty"); ghoul_assert(!toFrame.empty(), "toFrame must not be empty"); TransformMatrix m; sxform_c( fromFrame.c_str(), toFrame.c_str(), ephemerisTime, reinterpret_cast(m.data()) ); throwOnSpiceError(format( "Error retrieved state transform matrix from frame '{}' to frame '{}' at time " "'{}'", fromFrame, toFrame, ephemerisTime )); return m; } glm::dmat3 SpiceManager::positionTransformMatrix(const std::string& fromFrame, const std::string& toFrame, double ephemerisTime) const { ghoul_assert(!fromFrame.empty(), "fromFrame must not be empty"); ghoul_assert(!toFrame.empty(), "toFrame must not be empty"); glm::dmat3 result; pxform_c( fromFrame.c_str(), toFrame.c_str(), ephemerisTime, reinterpret_cast(glm::value_ptr(result)) ); throwOnSpiceError(""); SpiceBoolean success = !(failed_c()); reset_c(); if (!success) result = getEstimatedTransformMatrix(fromFrame, toFrame, ephemerisTime); return glm::transpose(result); } glm::dmat3 SpiceManager::positionTransformMatrix(const std::string& fromFrame, const std::string& toFrame, double ephemerisTimeFrom, double ephemerisTimeTo) const { ghoul_assert(!fromFrame.empty(), "fromFrame must not be empty"); ghoul_assert(!toFrame.empty(), "toFrame must not be empty"); glm::dmat3 result; pxfrm2_c( fromFrame.c_str(), toFrame.c_str(), ephemerisTimeFrom, ephemerisTimeTo, reinterpret_cast(glm::value_ptr(result)) ); throwOnSpiceError(format( "Error retrieving position transform matrix from '{}' at time '{}' to frame '{}' " "at time '{}'", fromFrame, ephemerisTimeFrom, toFrame, 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 { static const int MaxBoundsSize = 64; static const 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 ); bool failed = throwOnSpiceError(format( "Error getting field-of-view parameters for instrument '{}'", instrument )); if (failed) { 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]); } string shape = string(fovShapeBuffer); static const std::map Map = { { "POLYGON", FieldOfViewResult::Shape::Polygon }, { "RECTANGLE" , FieldOfViewResult::Shape::Rectangle }, { "CIRCLE", FieldOfViewResult::Shape::Circle }, { "ELLIPSE", FieldOfViewResult::Shape::Ellipse } }; res.shape = Map.at(shape); res.frameName = 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 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(res.terminatorPoints.data()) ); throwOnSpiceError(format( "Error getting terminator ellipse for target '{}' from observer '{}' in frame " "'{}' with light source '{}' at time '{}'", target, observer, frame, lightSource, ephemerisTime )); return res; } bool SpiceManager::addFrame(std::string body, std::string frame) { if (body == "" || frame == "") return false; else { _frameByBody.push_back(std::make_pair(body, frame)); return true; } } std::string SpiceManager::frameFromBody(const std::string& body) const { for (auto pair : _frameByBody) { if (pair.first == body) { return pair.second; } } std::string unionPrefix = "IAU_"; std::string frame = ""; if (body.find(unionPrefix) == std::string::npos) frame = unionPrefix + body; else frame = body; return frame; } void SpiceManager::findCkCoverage(const std::string& path) { ghoul_assert(!path.empty(), "Empty file path"); ghoul_assert(FileSys.fileExists(path), format("File '{}' does not exist", path)); const unsigned int MaxObj = 64; const unsigned int WinSiz = 10000; SPICEINT_CELL(ids, MaxObj); SPICEDOUBLE_CELL(cover, WinSiz); ckobj_c(path.c_str(), &ids); throwOnSpiceError("Error finding Ck Coverage"); for (SpiceInt i = 0; i < card_c(&ids); ++i) { SpiceInt frame = SPICE_CELL_ELEM_I(&ids, i); scard_c(0, &cover); ckcov_c(path.c_str(), frame, SPICEFALSE, "SEGMENT", 0.0, "TDB", &cover); throwOnSpiceError("Error finding Ck Coverage"); //Get the number of intervals in the coverage window. 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); throwOnSpiceError("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(FileSys.fileExists(path), format("File '{}' does not exist", path)); const unsigned int MaxObj = 64; const unsigned int WinSiz = 10000; SPICEINT_CELL(ids, MaxObj); SPICEDOUBLE_CELL(cover, WinSiz); spkobj_c(path.c_str(), &ids); throwOnSpiceError("Error finding Spk Converage"); for (SpiceInt i = 0; i < card_c(&ids); ++i) { SpiceInt obj = SPICE_CELL_ELEM_I(&ids, i); scard_c(0, &cover); spkcov_c(path.c_str(), obj, &cover); throwOnSpiceError("Error finding Spk Converage"); //Get the number of intervals in the coverage window. 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); throwOnSpiceError("Error finding Spk Converage"); //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 { 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(format("No position for '{}' at any time", target)); } else { return glm::dvec3(); } } const std::set& coveredTimes = _spkCoverageTimes.find(targetId)->second; glm::dvec3 pos; 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 ); throwOnSpiceError(format( "Error estimating position for target '{}' 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 ); throwOnSpiceError(format( "Error estimating position for target '{}' with observer '{}' in frame '{}'", target, observer, referenceFrame )); } else { // coverage both earlier and later, interpolate these positions glm::dvec3 posEarlier; 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; 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 ); throwOnSpiceError(format( "Error estimating position for target '{}' with observer '{}' in frame '{}'", target, observer, referenceFrame )); // linear interpolation 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; int idFrame = frameId(fromFrame); if (_ckCoverageTimes.find(idFrame) == _ckCoverageTimes.end()) { if (_useExceptions) { // no coverage throw SpiceException(format( "No data available for the transform matrix from '{}' to '{}' at any time", fromFrame, toFrame )); } else { return glm::dmat3(); } } std::set 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(glm::value_ptr(result)) ); throwOnSpiceError(format( "Error estimating transform matrix from frame '{}' 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(glm::value_ptr(result)) ); throwOnSpiceError(format( "Error estimating transform matrix from frame '{}' to from '{}' 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; pxform_c( fromFrame.c_str(), toFrame.c_str(), earlier, reinterpret_cast(glm::value_ptr(earlierTransform)) ); throwOnSpiceError(format( "Error estimating transform matrix from frame '{}' to from '{}' at time '{}'", fromFrame, toFrame, time )); glm::dmat3 laterTransform; pxform_c( fromFrame.c_str(), toFrame.c_str(), later, reinterpret_cast(glm::value_ptr(laterTransform)) ); throwOnSpiceError(format( "Error estimating transform matrix from frame '{}' to from '{}' at time '{}'", fromFrame, toFrame, time )); 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", { { "loadKernel", &luascriptfunctions::loadKernel, "string", "Loads the provided SPICE kernel by name. The name can contain path " "tokens, which are automatically resolved" }, { "unloadKernel", &luascriptfunctions::unloadKernel, "{string, number}", "Unloads the provided SPICE kernel. The name can contain path tokens, " "which are automatically resolved" } } }; } } // namespace openspace