diff --git a/apps/OpenSpace/ext/launcher/include/profile/horizonsdialog.h b/apps/OpenSpace/ext/launcher/include/profile/horizonsdialog.h index 7147525d60..414d9e8c9b 100644 --- a/apps/OpenSpace/ext/launcher/include/profile/horizonsdialog.h +++ b/apps/OpenSpace/ext/launcher/include/profile/horizonsdialog.h @@ -26,6 +26,7 @@ #define __OPENSPACE_UI_LAUNCHER___HORIZONS___H__ #include +#include <../modules/space/horizonsfile.h> #include #include #include @@ -55,28 +56,6 @@ private slots: void approved(); private: - enum class HorizonsResult { - Valid, - FileEmpty, - FileAlreadyExist, - ConnectionError, - - // Erros caught by the error field in the json output - ErrorSize, - ErrorTimeRange, - ErrorNoObserver, - ErrorObserverTargetSame, - ErrorNoData, - MultipleObserverStations, - - // Erros/problems NOT caught by the error field in the json output - MultipleObserver, - ErrorNoTarget, - MultipleTarget, - - UnknownError - }; - enum class LogLevel { Error, Warning, @@ -96,12 +75,11 @@ private: bool handleRequest(); bool isValidInput(); std::string constructUrl(); - HorizonsResult sendRequest(const std::string url); - HorizonsResult handleReply(QNetworkReply* reply); + json sendRequest(const std::string url); + json handleReply(QNetworkReply* reply); bool checkHttpStatus(const QVariant& statusCode); - HorizonsResult isValidAnswer(const json& answer); - HorizonsResult isValidHorizonsFile(const std::string& file) const; - bool handleResult(HorizonsDialog::HorizonsResult& result); + std::filesystem::path handleAnswer(json& answer); + bool handleResult(openspace::HorizonsFile::HorizonsResult& result); void appendLog(const std::string& message, const LogLevel level); std::filesystem::path _horizonsFile; @@ -115,9 +93,9 @@ private: QLineEdit* _centerEdit = nullptr; QComboBox* _chooseObserverCombo = nullptr; std::string _observerName; - QDateTimeEdit* _startEdit; + QDateTimeEdit* _startEdit = nullptr; std::string _startTime; - QDateTimeEdit* _endEdit; + QDateTimeEdit* _endEdit = nullptr; std::string _endTime; QLineEdit* _stepEdit = nullptr; QComboBox* _timeTypeCombo = nullptr; diff --git a/apps/OpenSpace/ext/launcher/src/profile/horizonsdialog.cpp b/apps/OpenSpace/ext/launcher/src/profile/horizonsdialog.cpp index 01b67dfa52..3db1269e68 100644 --- a/apps/OpenSpace/ext/launcher/src/profile/horizonsdialog.cpp +++ b/apps/OpenSpace/ext/launcher/src/profile/horizonsdialog.cpp @@ -283,7 +283,7 @@ void HorizonsDialog::createWidgets() { layout->addWidget(generateLabel); QUrl website("https://ssd.jpl.nasa.gov/horizons/"); - QLabel* infoLabel = new QLabel("

For more information about the Horizons system" + QLabel* infoLabel = new QLabel("

For more information about the Horizons system " "please visit: " "https://ssd.jpl.nasa.gov/horizons/

", this @@ -462,7 +462,22 @@ bool HorizonsDialog::handleRequest() { _chooseTargetCombo->clear(); _chooseTargetCombo->hide(); - HorizonsDialog::HorizonsResult result = sendRequest(url); + json answer = sendRequest(url); + if (answer.empty()) { + _errorMsg->setText("Connection error"); + return false; + } + + std::filesystem::path file = handleAnswer(answer); + if (!std::filesystem::is_regular_file(file)) { + return false; + } + + _horizonsFile = file; + openspace::HorizonsFile horizonsFile(_horizonsFile); + openspace::HorizonsFile::HorizonsResult result = + horizonsFile.isValidHorizonsFile(); + return handleResult(result); } @@ -612,7 +627,7 @@ std::string HorizonsDialog::constructUrl() { } // Send request synchronously, EventLoop waits until request has finished -HorizonsDialog::HorizonsResult HorizonsDialog::sendRequest(const std::string url) { +json HorizonsDialog::sendRequest(const std::string url) { QNetworkRequest request; request.setHeader(QNetworkRequest::UserAgentHeader, "OpenSpace"); request.setUrl(QUrl(url.c_str())); @@ -625,7 +640,7 @@ HorizonsDialog::HorizonsResult HorizonsDialog::sendRequest(const std::string url "Could not connect to Horizons API", HorizonsDialog::LogLevel::Error ); - return HorizonsDialog::HorizonsResult::ConnectionError; + return json(); } loop.exec(QEventLoop::ExcludeUserInputEvents); @@ -633,7 +648,7 @@ HorizonsDialog::HorizonsResult HorizonsDialog::sendRequest(const std::string url return handleReply(reply); } -HorizonsDialog::HorizonsResult HorizonsDialog::handleReply(QNetworkReply* reply) { +json HorizonsDialog::handleReply(QNetworkReply* reply) { if (reply->error()) { QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); if (!checkHttpStatus(statusCode)) { @@ -644,7 +659,7 @@ HorizonsDialog::HorizonsResult HorizonsDialog::handleReply(QNetworkReply* reply) } reply->deleteLater(); - return HorizonsDialog::HorizonsResult::ConnectionError; + return false; } QUrl redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); @@ -663,38 +678,8 @@ HorizonsDialog::HorizonsResult HorizonsDialog::handleReply(QNetworkReply* reply) std::cout << answer.toStdString(); std::cout << "'" << std::endl; - // Convert the answer to a json object and validate it - json jsonAnswer = json::parse(answer.toStdString()); - HorizonsDialog::HorizonsResult isValid = isValidAnswer(jsonAnswer); - if (isValid != HorizonsDialog::HorizonsResult::Valid) { - return isValid; - } - - // Create a text file and write reply to it - QString filePathQ = _directoryEdit->text(); - filePathQ.append(QDir::separator()); - filePathQ.append(_nameEdit->text()); - std::string filePath = filePathQ.toStdString(); - std::filesystem::path fullFilePath = std::filesystem::absolute(filePath); - - auto result = jsonAnswer.find("result"); - if (result == jsonAnswer.end()) { - return HorizonsDialog::HorizonsResult::UnknownError; - } - - // Check if the file already exists - if (std::filesystem::is_regular_file(fullFilePath)) { - return HorizonsDialog::HorizonsResult::FileAlreadyExist; - } - - // Write response into a new file - std::ofstream file(filePath); - file << replaceAll(*result, "\\n", "\n") << std::endl; - //file << *result << std::endl; - file.close(); - - _horizonsFile = fullFilePath; - return isValidHorizonsFile(filePath); + // Convert the answer to a json object and return it + return json::parse(answer.toStdString()); } bool HorizonsDialog::checkHttpStatus(const QVariant& statusCode) { @@ -732,172 +717,50 @@ bool HorizonsDialog::checkHttpStatus(const QVariant& statusCode) { return isKnown; } -HorizonsDialog::HorizonsResult HorizonsDialog::isValidAnswer(const json& answer) { - auto it = answer.find("error"); - if (it != answer.end()) { - // There was an error - std::string errorMessage = *it; - - // Projected output length (~X) exceeds 90024 line max -- change step-size - if (errorMessage.find("Projected output length") != std::string::npos) { - return HorizonsDialog::HorizonsResult::ErrorSize; - } - // No ephemeris for target "X" after A.D. Y UT - else if (errorMessage.find("No ephemeris for target") != std::string::npos) { - return HorizonsDialog::HorizonsResult::ErrorTimeRange; - } - // No site matches. Use "*@body" to list, "c@body" to enter coords, ?! for help. - else if (errorMessage.find("No site matches") != std::string::npos) { - return HorizonsDialog::HorizonsResult::ErrorNoObserver; - } - // Observer table for X / Y->Y disallowed. - else if (errorMessage.find("disallowed") != std::string::npos) { - return HorizonsDialog::HorizonsResult::ErrorObserverTargetSame; - } - // Insufficient ephemeris data has been loaded to compute the state of X - // relative to Y at the ephemeris epoch Z; - else if (errorMessage.find("Insufficient ephemeris data") != std::string::npos) { - return HorizonsDialog::HorizonsResult::ErrorNoData; - } - // # E. Lon DXY DZ Observatory Name; - // -- - -------- ------ - ------ - ----------------; - // * Observer station * - // Multiple matching stations found. - else if (errorMessage.find("Multiple matching stations found") != std::string::npos) { - return HorizonsDialog::HorizonsResult::MultipleObserverStations; - } - // Unknown error - else { - appendLog(errorMessage, HorizonsDialog::LogLevel::Error); - return HorizonsDialog::HorizonsResult::UnknownError; - } +std::filesystem::path HorizonsDialog::handleAnswer(json& answer) { + openspace::HorizonsFile::HorizonsResult isValid = openspace::HorizonsFile::isValidAnswer(answer); + if (isValid != openspace::HorizonsFile::HorizonsResult::Valid) { + handleResult(isValid); + return std::filesystem::path(); } - return HorizonsDialog::HorizonsResult::Valid; + + // Create a text file and write reply to it + QString filePathQ = _directoryEdit->text(); + filePathQ.append(QDir::separator()); + filePathQ.append(_nameEdit->text()); + std::string filePath = filePathQ.toStdString(); + std::filesystem::path fullFilePath = std::filesystem::absolute(filePath); + + auto result = answer.find("result"); + if (result == answer.end()) { + _errorMsg->setText("Malformed answer recieved"); + return std::filesystem::path(); + } + + // Check if the file already exists + if (std::filesystem::is_regular_file(fullFilePath)) { + _errorMsg->setText("File already exist, try another filename"); + return std::filesystem::path(); + } + + // Write response into a new file + std::ofstream file(filePath); + file << replaceAll(*result, "\\n", "\n") << std::endl; + file.close(); + + return fullFilePath; } -// Check whether the given Horizons file is valid or not -// Return an error code with what is the problem if there was one -HorizonsDialog::HorizonsResult HorizonsDialog::isValidHorizonsFile(const std::string& file) const { - std::ifstream fileStream(file); - if (!fileStream.good()) { - return HorizonsDialog::HorizonsResult::FileEmpty; - } - - // The header of a Horizons file has a lot of information about the - // query that can tell us if the file is valid or not. - // The line $$SOE indicates start of data. - std::string line; - bool foundTarget = false; - while (fileStream.good() && line.find("$$SOE") == std::string::npos) { - // Valid Target? - if (line.find("Revised") != std::string::npos) { - // If the target is valid, the first line is the date it was last revised - foundTarget = true; - } - - // Selected time range too big and step size too small? - if (line.find("change step-size") != std::string::npos) { - fileStream.close(); - return HorizonsDialog::HorizonsResult::ErrorSize; - } - - // Outside valid time range? - if (line.find("No ephemeris for target") != std::string::npos) { - // Available time range is located several lines before this in the file - // The avalable time range is persed later - fileStream.close(); - return HorizonsDialog::HorizonsResult::ErrorTimeRange; - } - - // Valid Observer? - if (line.find("No site matches") != std::string::npos || - line.find("Cannot find central body") != std::string::npos) - { - fileStream.close(); - return HorizonsDialog::HorizonsResult::ErrorNoObserver; - } - - // Are observer and target the same? - if (line.find("disallowed") != std::string::npos) - { - fileStream.close(); - return HorizonsDialog::HorizonsResult::ErrorObserverTargetSame; - } - - // Enough data? - if (line.find("Insufficient ephemeris data") != std::string::npos) - { - fileStream.close(); - return HorizonsDialog::HorizonsResult::ErrorNoData; - } - - // Multiple Observer stations? - if (line.find("Multiple matching stations found") != std::string::npos) { - fileStream.close(); - return HorizonsDialog::HorizonsResult::MultipleObserverStations; - } - - // Multiple matching major bodies? - if (line.find("Multiple major-bodies match string") != std::string::npos) { - // Target - if (!foundTarget) { - // If target was not found then it is the target that has multiple matches - fileStream.close(); - return HorizonsDialog::HorizonsResult::MultipleTarget; - } - // Observer - else { - fileStream.close(); - return HorizonsDialog::HorizonsResult::MultipleObserver; - } - } - - // Multiple matching small bodies? - if (line.find("Small-body Index Search Results") != std::string::npos) { - // Small bodies can only be targets not observers - fileStream.close(); - return HorizonsDialog::HorizonsResult::MultipleTarget; - } - - // No Target? - if (line.find("No matches found") != std::string::npos) { - fileStream.close(); - return HorizonsDialog::HorizonsResult::ErrorNoTarget; - } - - std::getline(fileStream, line); - } - - // If we reached end of file before we found the start of data then it is - // not a valid file - if (fileStream.good()) { - fileStream.close(); - return HorizonsDialog::HorizonsResult::Valid; - } - else { - fileStream.close(); - return HorizonsDialog::HorizonsResult::UnknownError; - } -} - -bool HorizonsDialog::handleResult(HorizonsDialog::HorizonsResult& result) { +bool HorizonsDialog::handleResult(openspace::HorizonsFile::HorizonsResult& result) { switch (result) { - case HorizonsDialog::HorizonsResult::Valid: + case openspace::HorizonsFile::HorizonsResult::Valid: return true; - case HorizonsDialog::HorizonsResult::FileEmpty: + case openspace::HorizonsFile::HorizonsResult::Empty: _errorMsg->setText("The horizons file is empty"); break; - case HorizonsDialog::HorizonsResult::FileAlreadyExist: - _errorMsg->setText("File already exist, try another filename"); - break; - - case HorizonsDialog::HorizonsResult::ConnectionError: - _errorMsg->setText("Connection error"); - break; - - case HorizonsDialog::HorizonsResult::ErrorSize: { + case openspace::HorizonsFile::HorizonsResult::ErrorSize: { std::string message = "Time range '" + _startTime + "' to '" + _endTime + "' with step size '" + _stepEdit->text().toStdString() + "' " + _timeTypeCombo->currentText().toStdString() + @@ -907,7 +770,7 @@ bool HorizonsDialog::handleResult(HorizonsDialog::HorizonsResult& result) { break; } - case HorizonsDialog::HorizonsResult::ErrorTimeRange: { + case openspace::HorizonsFile::HorizonsResult::ErrorTimeRange: { appendLog("Time range is outside the valid range for target '" + _targetName + "'.", HorizonsDialog::LogLevel::Error); @@ -926,21 +789,21 @@ bool HorizonsDialog::handleResult(HorizonsDialog::HorizonsResult& result) { break; } - case HorizonsDialog::HorizonsResult::ErrorNoObserver: + case openspace::HorizonsFile::HorizonsResult::ErrorNoObserver: appendLog("No match was found for observer '" + _observerName + "'. " "Use '@" + _observerName + "' as observer to list possible matches.", HorizonsDialog::LogLevel::Error ); break; - case HorizonsDialog::HorizonsResult::ErrorObserverTargetSame: + case openspace::HorizonsFile::HorizonsResult::ErrorObserverTargetSame: appendLog("The observer '" + _observerName + "' and target '" + _targetName + "' are the same. Please use another observer for the current target.", HorizonsDialog::LogLevel::Error ); break; - case HorizonsDialog::HorizonsResult::ErrorNoData: + case openspace::HorizonsFile::HorizonsResult::ErrorNoData: appendLog("There is not enough data to compute the state of target '" + _targetName + "' in relation to the observer '" + _observerName + "' for the time range '" + _startTime + "' to '" + _endTime + @@ -949,7 +812,7 @@ bool HorizonsDialog::handleResult(HorizonsDialog::HorizonsResult& result) { ); break; - case HorizonsDialog::HorizonsResult::MultipleObserverStations: + case openspace::HorizonsFile::HorizonsResult::MultipleObserverStations: appendLog("Multiple observer stations was found for observer '" + _observerName + "'. ", HorizonsDialog::LogLevel::Warning ); @@ -959,7 +822,7 @@ bool HorizonsDialog::handleResult(HorizonsDialog::HorizonsResult& result) { ); break; - case HorizonsDialog::HorizonsResult::MultipleObserver: { + case openspace::HorizonsFile::HorizonsResult::MultipleObserver: { appendLog("Multiple matches were found for observer '" + _observerName + "'", HorizonsDialog::LogLevel::Warning @@ -986,13 +849,13 @@ bool HorizonsDialog::handleResult(HorizonsDialog::HorizonsResult& result) { break; } - case HorizonsDialog::HorizonsResult::ErrorNoTarget: + case openspace::HorizonsFile::HorizonsResult::ErrorNoTarget: appendLog("No match was found for target '" + _targetName + "'", HorizonsDialog::LogLevel::Error ); break; - case HorizonsDialog::HorizonsResult::MultipleTarget: { + case openspace::HorizonsFile::HorizonsResult::MultipleTarget: { // Case Small Bodies: // Line before data: Matching small-bodies // Format: Record #, Epoch-yr, >MATCH DESIG<, Primary Desig, Name @@ -1028,7 +891,7 @@ bool HorizonsDialog::handleResult(HorizonsDialog::HorizonsResult& result) { break; } - case HorizonsDialog::HorizonsResult::UnknownError: + case openspace::HorizonsFile::HorizonsResult::UnknownError: _errorMsg->setText("An unknown error occured"); break; diff --git a/modules/space/CMakeLists.txt b/modules/space/CMakeLists.txt index f638f16bc4..35e09179ce 100644 --- a/modules/space/CMakeLists.txt +++ b/modules/space/CMakeLists.txt @@ -25,6 +25,7 @@ include(${OPENSPACE_CMAKE_EXT_DIR}/module_definition.cmake) set(HEADER_FILES + horizonsfile.h speckloader.h rendering/planetgeometry.h rendering/renderableconstellationbounds.h @@ -46,6 +47,7 @@ set(HEADER_FILES source_group("Header Files" FILES ${HEADER_FILES}) set(SOURCE_FILES + horizonsfile.cpp spacemodule_lua.inl speckloader.cpp rendering/planetgeometry.cpp diff --git a/modules/space/horizonsfile.cpp b/modules/space/horizonsfile.cpp new file mode 100644 index 0000000000..70ea3b144c --- /dev/null +++ b/modules/space/horizonsfile.cpp @@ -0,0 +1,207 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2022 * + * * + * 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 + +namespace { + constexpr const char* _loggerCat = "HorizonsFile"; +} // namespace + +namespace openspace { + +HorizonsFile::HorizonsFile() + : _file() +{} + +HorizonsFile::HorizonsFile(std::filesystem::path file) + : _file(std::move(file)) +{} + +void HorizonsFile::setFile(std::filesystem::path file) { + _file = file; +} + +const std::filesystem::path& HorizonsFile::file() const { + return _file; +} + +std::filesystem::path& HorizonsFile::file() { + return _file; +} + +HorizonsFile::HorizonsResult HorizonsFile::isValidAnswer(const json& answer) { + auto it = answer.find("error"); + if (it != answer.end()) { + // There was an error + std::string errorMessage = *it; + + // Projected output length (~X) exceeds 90024 line max -- change step-size + if (errorMessage.find("Projected output length") != std::string::npos) { + return HorizonsFile::HorizonsResult::ErrorSize; + } + // No ephemeris for target "X" after A.D. Y UT + else if (errorMessage.find("No ephemeris for target") != std::string::npos) { + return HorizonsFile::HorizonsResult::ErrorTimeRange; + } + // No site matches. Use "*@body" to list, "c@body" to enter coords, ?! for help. + else if (errorMessage.find("No site matches") != std::string::npos) { + return HorizonsFile::HorizonsResult::ErrorNoObserver; + } + // Observer table for X / Y->Y disallowed. + else if (errorMessage.find("disallowed") != std::string::npos) { + return HorizonsFile::HorizonsResult::ErrorObserverTargetSame; + } + // Insufficient ephemeris data has been loaded to compute the state of X + // relative to Y at the ephemeris epoch Z; + else if (errorMessage.find("Insufficient ephemeris data") != std::string::npos) { + return HorizonsFile::HorizonsResult::ErrorNoData; + } + // # E. Lon DXY DZ Observatory Name; + // -- - -------- ------ - ------ - ----------------; + // * Observer station * + // Multiple matching stations found. + else if (errorMessage.find("Multiple matching stations found") != std::string::npos) { + return HorizonsFile::HorizonsResult::MultipleObserverStations; + } + // Unknown error + else { + LERROR(errorMessage); + return HorizonsFile::HorizonsResult::UnknownError; + } + } + return HorizonsFile::HorizonsResult::Valid; +} + +// Check whether the given Horizons file is valid or not +// Return an error code with what is the problem if there was one +HorizonsFile::HorizonsResult HorizonsFile::isValidHorizonsFile() const { + std::ifstream fileStream(_file); + if (!fileStream.good()) { + return HorizonsFile::HorizonsResult::Empty; + } + + // The header of a Horizons file has a lot of information about the + // query that can tell us if the file is valid or not. + // The line $$SOE indicates start of data. + std::string line; + bool foundTarget = false; + while (fileStream.good() && line.find("$$SOE") == std::string::npos) { + // Valid Target? + if (line.find("Revised") != std::string::npos) { + // If the target is valid, the first line is the date it was last revised + foundTarget = true; + } + + // Selected time range too big and step size too small? + if (line.find("change step-size") != std::string::npos) { + fileStream.close(); + return HorizonsFile::HorizonsResult::ErrorSize; + } + + // Outside valid time range? + if (line.find("No ephemeris for target") != std::string::npos) { + // Available time range is located several lines before this in the file + // The avalable time range is persed later + fileStream.close(); + return HorizonsFile::HorizonsResult::ErrorTimeRange; + } + + // Valid Observer? + if (line.find("No site matches") != std::string::npos || + line.find("Cannot find central body") != std::string::npos) + { + fileStream.close(); + return HorizonsFile::HorizonsResult::ErrorNoObserver; + } + + // Are observer and target the same? + if (line.find("disallowed") != std::string::npos) + { + fileStream.close(); + return HorizonsFile::HorizonsResult::ErrorObserverTargetSame; + } + + // Enough data? + if (line.find("Insufficient ephemeris data") != std::string::npos) + { + fileStream.close(); + return HorizonsFile::HorizonsResult::ErrorNoData; + } + + // Multiple Observer stations? + if (line.find("Multiple matching stations found") != std::string::npos) { + fileStream.close(); + return HorizonsFile::HorizonsResult::MultipleObserverStations; + } + + // Multiple matching major bodies? + if (line.find("Multiple major-bodies match string") != std::string::npos) { + // Target + if (!foundTarget) { + // If target was not found then it is the target that has multiple matches + fileStream.close(); + return HorizonsFile::HorizonsResult::MultipleTarget; + } + // Observer + else { + fileStream.close(); + return HorizonsFile::HorizonsResult::MultipleObserver; + } + } + + // Multiple matching small bodies? + if (line.find("Small-body Index Search Results") != std::string::npos) { + // Small bodies can only be targets not observers + fileStream.close(); + return HorizonsFile::HorizonsResult::MultipleTarget; + } + + // No Target? + if (line.find("No matches found") != std::string::npos) { + fileStream.close(); + return HorizonsFile::HorizonsResult::ErrorNoTarget; + } + + std::getline(fileStream, line); + } + + // If we reached end of file before we found the start of data then it is + // not a valid file + if (fileStream.good()) { + fileStream.close(); + return HorizonsFile::HorizonsResult::Valid; + } + else { + fileStream.close(); + return HorizonsFile::HorizonsResult::UnknownError; + } +} + +} // namespace openspace diff --git a/modules/space/horizonsfile.h b/modules/space/horizonsfile.h new file mode 100644 index 0000000000..dc70e2e718 --- /dev/null +++ b/modules/space/horizonsfile.h @@ -0,0 +1,88 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2022 * + * * + * 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. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_MODULE_SPACE___HORIZONSFILE___H__ +#define __OPENSPACE_MODULE_SPACE___HORIZONSFILE___H__ + +#include +#include + +using json = nlohmann::json; + +namespace openspace { + +/** + * A Horizons file is a text file generated from NASA JPL HORIZONS Website + * (https://ssd.jpl.nasa.gov/horizons.cgi). The implementation that reads these files + * expects a file with format: + * TIME(YYYY-MM-DD HH:MM:SS) Range(km) GalLon(degrees) GalLat(degrees) + * Range - The distance from target to observer. Enabled with "Observer range & + * range-rate" (nr 20) in "Table Setting". This also generates a delta that must be + * suppressed under "Additional Table Settings". User must set output settings to + * kilometers under "Range units". + * GalLon - Galactic Longitude. Enabled with "Galactic longitude & latitude" (nr 33) in + * "Table Setting". + * GalLat - Galactic Latitude. Output is by default set to degrees. + * Make sure that no other settings are enables in the "Table Setting" than the ones + * descripbed above + */ +class HorizonsFile { +public: + enum class HorizonsResult { + Valid, + Empty, + + // Erros caught by the error field in the json output + ErrorSize, + ErrorTimeRange, + ErrorNoObserver, + ErrorObserverTargetSame, + ErrorNoData, + MultipleObserverStations, + + // Erros/problems NOT caught by the error field in the json output + MultipleObserver, + ErrorNoTarget, + MultipleTarget, + + UnknownError + }; + + HorizonsFile(); + HorizonsFile(std::filesystem::path file); + + void setFile(std::filesystem::path file); + const std::filesystem::path& file() const; + std::filesystem::path& file(); + + static HorizonsResult isValidAnswer(const json& answer); + HorizonsResult isValidHorizonsFile() const; + +private: + std::filesystem::path _file; +}; + +} // namespace openspace + +#endif // __OPENSPACE_MODULE_SPACE___HORIZONSFILE___H__