diff --git a/CHANGELOG.md b/CHANGELOG.md index 64dfbfb..646f7c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ release. ### Added - Added getTargetStatesRanged so a request can be made with start, end, and range of ETs instead of a list [#115](https://github.com/DOI-USGS/SpiceQL/pull/115) +- Added additional support for ISD to kernel generation [#116](https://github.com/DOI-USGS/SpiceQL/pull/116) ### 1.2.7 diff --git a/SpiceQL/include/io.h b/SpiceQL/include/io.h index 10d556d..d07f3b6 100644 --- a/SpiceQL/include/io.h +++ b/SpiceQL/include/io.h @@ -6,6 +6,7 @@ * **/ +#include #include #include #include diff --git a/SpiceQL/include/spice_types.h b/SpiceQL/include/spice_types.h index 3b6d03a..9844174 100644 --- a/SpiceQL/include/spice_types.h +++ b/SpiceQL/include/spice_types.h @@ -71,6 +71,45 @@ namespace SpiceQL { **/ static Type translateType(std::string type); + /** + * @brief Get Kernel extension + * + * @param type Kernel type string + * @return Kernel extension as string + */ + static std::string getExt(std::string type); + + /** + * @brief Check if kernel type is binary + * + * @param type Kernel type string + * @return Whether kernel type is binary + */ + static bool isBinary(std::string type); + + /** + * @brief Check if kernel type is text-based + * + * @param type Kernel type string + * @return Whether kernel type is text-based + */ + static bool isText(std::string type); + + /** + * @brief Check if kernel type is CK + * + * @param type Kernel type string + * @return Whether kernel type is CK + */ + static bool isCk(std::string type); + + /** + * @brief Check if kernel type is SPK + * + * @param type Kernel type string + * @return Whether kernel type is SPK + */ + static bool isSpk(std::string type); /** * @brief Switch between Quality enum to string diff --git a/SpiceQL/include/utils.h b/SpiceQL/include/utils.h index 976e356..f5572ea 100644 --- a/SpiceQL/include/utils.h +++ b/SpiceQL/include/utils.h @@ -61,6 +61,16 @@ namespace SpiceQL { std::string replaceAll(std::string str, const std::string &from, const std::string &to); + /** + * @brief turn a string into a vector with a deliminator + * + * @param s input string + * @param delim char deliminator + * @return std::vector + */ + std::vector split(const std::string& s, char delim); + + /** * @brief glob, but with json * diff --git a/SpiceQL/src/api.cpp b/SpiceQL/src/api.cpp index 7403b3f..8fb4bdc 100644 --- a/SpiceQL/src/api.cpp +++ b/SpiceQL/src/api.cpp @@ -33,11 +33,14 @@ namespace SpiceQL { vector default_KernelQualities = {"smithed", "reconstructed"}; json aliasMap = { + {"A15_METRIC", "apollo"}, {"AMICA", "amica"}, {"CHANDRAYAAN-1_M3", "m3"}, {"CHANDRAYAAN-1_MRFFR", "mrffr"}, {"CASSINI_ISS_NAC", "cassini"}, {"CASSINI_ISS_WAC", "cassini"}, + {"CASSINI_VIMS_V", "cassini"}, + {"Cassini-Huygens", "cassini"}, {"DAWN_FC2_FILTER_1", "fc2"}, {"DAWN_FC2_FILTER_2", "fc2"}, {"DAWN_FC2_FILTER_3", "fc2"}, @@ -46,6 +49,7 @@ namespace SpiceQL { {"DAWN_FC2_FILTER_6", "fc2"}, {"DAWN_FC2_FILTER_7", "fc2"}, {"DAWN_FC2_FILTER_8", "fc2"}, + {"DAWN_VIR_IR", "vir"}, {"GLL_SSI_PLATFORM", "galileo"}, {"HAYABUSA_AMICA", "amica"}, {"HAYABUSA_NIRS", "nirs"}, @@ -59,9 +63,12 @@ namespace SpiceQL { {"LRO_MINIRF", "minirf"}, {"M10_VIDICON_A", "m10_vidicon_a"}, {"M10_VIDICON_B", "m10_vidicon_b"}, + {"VIDICON_A", "m10_vidicon_a"}, {"MARS", "mro"}, {"MSGR_MDIS_WAC", "mdis"}, {"MSGR_MDIS_NAC", "mdis"}, + {"MERCURY DUAL IMAGING SYSTEM NARROW ANGLE CAMERA", "mdis"}, + {"MEX_HRSC_HEAD", "hrsc"}, {"MEX_HRSC_SRC", "src"}, {"MEX_HRSC_IR", "hrsc"}, {"MGS_MOC_NA", "mgs"}, @@ -69,14 +76,22 @@ namespace SpiceQL { {"MGS_MOC_WA_BLUE", "mgs"}, {"MRO_MARCI_VIS", "marci"}, {"MRO_MARCI_UV", "marci"}, + {"MRO_MARCI_BASE", "marci"}, {"MRO_CTX", "ctx"}, {"MRO_HIRISE", "hirise"}, + {"MRO_HIRISE_LOOK_DIRECTION", "hirise"}, {"MRO_CRISM_VNIR", "crism"}, - {"NEAR EARTH ASTEROID RENDEZVOUS", ""}, + {"NEAR EARTH ASTEROID RENDEZVOUS", "near"}, + {"NEAR_MSI", "msi"}, {"NH_LORRI", "lorri"}, + {"NH_LORRI_1X1", "lorri"}, {"NH_RALPH_LEISA", "leisa"}, {"NH_MVIC", "mvic_tdi"}, + {"ISIS_NH_RALPH_LEISA", "leisa"}, {"ISIS_NH_RALPH_MVIC_METHANE", "mvic_framing"}, + {"ISIS_NH_RALPH_MVIC_FT", "mvic_framing"}, + {"M01_THEMIS_IR", "odyssey"}, + {"M01_THEMIS_VIS", "odyssey"}, {"THEMIS_IR", "odyssey"}, {"THEMIS_VIS", "odyssey"}, {"LISM_MI-VIS1", "kaguya"}, @@ -101,16 +116,27 @@ namespace SpiceQL { {"LISM_TC1_SDH", "kaguya"}, {"LISM_TC1_STH", "kaguya"}, {"LISM_TC1_SSH", "kaguya"}, + {"SELENE", "kaguya"}, + {"KAGUYA", "kaguya"}, + {"SELENE MAIN ORBITER", "kaguya"}, {"LO1_HIGH_RESOLUTION_CAMERA", "lo"}, {"LO2_HIGH_RESOLUTION_CAMERA", "lo"}, {"LO3_HIGH_RESOLUTION_CAMERA", "lo"}, {"LO4_HIGH_RESOLUTION_CAMERA", "lo"}, {"LO5_HIGH_RESOLUTION_CAMERA", "lo"}, + {"LO3_MED_RESOLUTION_CAMERA", "lo"}, {"NEPTUNE", "voyager1"}, {"SATURN", "voyager1"}, {"TGO_CASSIS", "cassis"}, + {"TGO_CASSIS_CRU", "cassis"}, {"VIKING ORBITER 1", "viking1"}, + {"VIKING_ORBITER_1", "viking1"}, {"VIKING ORBITER 2", "viking2"}, + {"VIKING_ORBITER_2", "viking2"}, + {"VO2_VISB", "viking2"}, + {"VO2_VISA", "viking2"}, + {"VO1_VISA", "viking1"}, + {"VO1_VISB", "viking1"}, {"VG1_ISSNA", "voyager1"}, {"VG1_ISSWA", "voyager1"}, {"VG2_ISSNA", "voyager2"}, @@ -120,8 +146,14 @@ namespace SpiceQL { {"High Resolution Camera", "clementine1"}, {"Long Wave Infrared Camera", "clementine1"}, {"Visual and Infrared Spectrometer", "vir"}, + {"CLEM_HIRES", "clementine1"}, + {"CLEM_UVVIS_A", "uvvis"}, + {"CLEM_LWIR", "clementine1"}, + {"CLEM_NIR", "nir"}, {"CH2", "chandrayaan2"}, - {"CH-2", "chandrayaan2"} + {"CH-2", "chandrayaan2"}, + {"ROS_VIRTIS-M_IR", "virtis"}, + {"MSL_MASTCAM_LEFT", "msl"} }; /** diff --git a/SpiceQL/src/io.cpp b/SpiceQL/src/io.cpp index cd51367..921812a 100644 --- a/SpiceQL/src/io.cpp +++ b/SpiceQL/src/io.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include @@ -10,6 +12,8 @@ #include "io.h" #include "utils.h" +#include + using namespace std; @@ -74,7 +78,7 @@ namespace SpiceQL { this->angularVelocities = angularVelocities; this->comment = comment; } - + void writeCk(string path, vector> quats, @@ -90,9 +94,15 @@ namespace SpiceQL { SpiceInt handle; // convert times, but first, we need SCLK+LSK kernels - Kernel sclkKernel(sclk); - Kernel lskKernel(lsk); + // allow and furnish multiple sclks + std::vector> sclkKernels; + if (!sclk.empty()) { + for (const std::string& sclkPath : split(sclk, ',')) + sclkKernels.push_back(std::make_unique(sclkPath)); + } + Kernel lskKernel(lsk); + for(auto &et : times) { double sclkdp; checkNaifErrors(); @@ -109,6 +119,33 @@ namespace SpiceQL { ckopn_c(path.c_str(), "CK", comment.size(), &handle); checkNaifErrors(); + // Flatten the nested quaternions into a single contiguous array. + // CSPICE functions (like ckw03_c) are C-based and expect a pointer to a + // contiguous block of memory (SpiceDouble quats[][4]). + // A std::vector> stores inner vectors in fragmented + // locations on the heap; we must "flatten" them into a single ribbon + // of doubles so that pointer arithmetic works correctly inside SPICE. + vector flatQuats; + flatQuats.reserve(quats.size() * 4); + for (const auto& q : quats) { + for (double d : q) { + flatQuats.push_back(d); + } + } + + // Flatten angular velocities if they exist. + // Similar to quaternions, AV data must be contiguous (SpiceDouble av[][3]). + // We check if the input nested vector is non-empty before proceeding. + vector flatAv; + if (!angularVelocities.empty()) { + flatAv.reserve(angularVelocities.size() * 3); + for (const auto& a : angularVelocities) { + for (double d : a) { + flatAv.push_back(d); + } + } + } + ckw03_c (handle, times.at(0), times.at(times.size()-1), @@ -118,8 +155,8 @@ namespace SpiceQL { segmentId.c_str(), times.size(), times.data(), - quats.data(), - (!angularVelocities.empty()) ? angularVelocities.data() : nullptr, + flatQuats.data(), + (!angularVelocities.empty()) ? flatAv.data() : nullptr, times.size(), times.data()); checkNaifErrors(); @@ -141,6 +178,22 @@ namespace SpiceQL { vector> stateVelocities, string segmentComment) { + if (stateTimes.empty() || statePositions.empty()) { + throw runtime_error("writeSpk: stateTimes and statePositions must be non-empty."); + } + + // NAIF spkw13_c requires segment start time < end time. Single-epoch (e.g. from ISD) has start == end. + if (stateTimes.size() == 1) { + stateTimes.push_back(stateTimes.front() + 1E-6); + statePositions.push_back(statePositions.front()); + if (!stateVelocities.empty()) { + stateVelocities.push_back(stateVelocities.front()); + } + } else if (stateTimes.front() >= stateTimes.back()) { + throw runtime_error( + "writeSpk: segment start time must be less than end time (got start == end or reversed order)."); + } + vector> states; if (stateVelocities.empty()) { diff --git a/SpiceQL/src/spice_types.cpp b/SpiceQL/src/spice_types.cpp index 71580d4..8bf3a1c 100644 --- a/SpiceQL/src/spice_types.cpp +++ b/SpiceQL/src/spice_types.cpp @@ -54,6 +54,14 @@ namespace SpiceQL { "reconstructed", "smithed"}; + const std::unordered_map KERNEL_EXTS = { {Kernel::Type::CK, ".bc"}, + {Kernel::Type::SPK, ".bsp"}, + {Kernel::Type::FK, ".tf"}, + {Kernel::Type::IK, ".ti"}, + {Kernel::Type::LSK, ".tls"}, + {Kernel::Type::MK, ".tm"}, + {Kernel::Type::PCK, ".tpc"}, + {Kernel::Type::SCLK, ".tsc"}}; string Kernel::translateType(Kernel::Type type) { return KERNEL_TYPES[static_cast(type)]; @@ -61,14 +69,38 @@ namespace SpiceQL { Kernel::Type Kernel::translateType(string type) { - auto res = findInVector(KERNEL_TYPES, type); + auto res = findInVector(KERNEL_TYPES, toLower(type)); if (res.first) { return static_cast(res.second); } throw invalid_argument(fmt::format("{} is not a valid kernel type", type)); - }; + } + + std::string Kernel::getExt(std::string type) { + Kernel::Type ktype = translateType(type); + auto it = KERNEL_EXTS.find(ktype); + if (it != KERNEL_EXTS.end()) { + return it->second; + } + throw invalid_argument(fmt::format("{} is not a valid kernel type", type)); + } + + bool Kernel::isBinary(std::string type) { + return (isCk(type) || isSpk(type)); + } + + bool Kernel::isText(std::string type) { + return !isBinary(type); + } + bool Kernel::isCk(std::string type) { + return translateType(type) == Kernel::Type::CK; + } + + bool Kernel::isSpk(std::string type) { + return translateType(type) == Kernel::Type::SPK; + } string Kernel::translateQuality(Kernel::Quality qa) { return KERNEL_QUALITIES[static_cast(qa)]; diff --git a/SpiceQL/src/utils.cpp b/SpiceQL/src/utils.cpp index e113880..60a9602 100644 --- a/SpiceQL/src/utils.cpp +++ b/SpiceQL/src/utils.cpp @@ -111,6 +111,20 @@ namespace SpiceQL { } + vector split(const string& s, char delim) { + vector out; + istringstream ss(s); + string part; + while (std::getline(ss, part, delim)) { + auto start = part.find_first_not_of(" \t"); + if (start == string::npos) continue; + auto end = part.find_last_not_of(" \t"); + out.push_back(part.substr(start, end == string::npos ? part.size() : end - start + 1)); + } + return out; + } + + vector> getPathsFromRegex(string root, vector regexes) { vector files_to_search = Memo::ls(root, true);