diff --git a/apps/OpenSpace/ext/launcher/include/profile/horizonsdialog.h b/apps/OpenSpace/ext/launcher/include/profile/horizonsdialog.h index 3ae2bb593f..0cbd7cece8 100644 --- a/apps/OpenSpace/ext/launcher/include/profile/horizonsdialog.h +++ b/apps/OpenSpace/ext/launcher/include/profile/horizonsdialog.h @@ -77,7 +77,7 @@ private: json sendRequest(const std::string url); json handleReply(QNetworkReply* reply); bool checkHttpStatus(const QVariant& statusCode); - std::filesystem::path handleAnswer(json& answer); + openspace::HorizonsFile handleAnswer(json& answer); bool handleResult(openspace::HorizonsFile::ResultCode& result); void appendLog(const std::string& message, const LogLevel level); diff --git a/apps/OpenSpace/ext/launcher/src/profile/horizonsdialog.cpp b/apps/OpenSpace/ext/launcher/src/profile/horizonsdialog.cpp index 58ceeed090..8fa72a861a 100644 --- a/apps/OpenSpace/ext/launcher/src/profile/horizonsdialog.cpp +++ b/apps/OpenSpace/ext/launcher/src/profile/horizonsdialog.cpp @@ -22,8 +22,6 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ - - #include "profile/horizonsdialog.h" #include "profile/line.h" @@ -57,19 +55,6 @@ namespace { constexpr const char* Years = "calendar years"; constexpr const char* Unitless = "equal intervals (unitless)"; - std::string replaceAll(const std::string& string, const std::string& from, const std::string& to) { - if (from.empty()) - return ""; - - std::string result = string; - size_t startPos = 0; - while ((startPos = result.find(from, startPos)) != std::string::npos) { - result.replace(startPos, from.length(), to); - startPos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' - } - return result; - } - std::string trim(const std::string& text) { std::string result = text; if (text.empty()) @@ -146,8 +131,8 @@ void HorizonsDialog::createWidgets() { generateLabel->setObjectName("heading"); layout->addWidget(generateLabel); - QLabel* infoLabel = new QLabel("

For more information about the Horizons system " - "please visit: " + QLabel* infoLabel = new QLabel("

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

", this ); @@ -158,7 +143,7 @@ void HorizonsDialog::createWidgets() { } { QBoxLayout* container = new QHBoxLayout(this); - QLabel* typeLabel = new QLabel("Horizons data type", this); + QLabel* typeLabel = new QLabel("Horizons data type:", this); container->addWidget(typeLabel); _typeCombo = new QComboBox(this); @@ -179,7 +164,9 @@ void HorizonsDialog::createWidgets() { container->addWidget(nameLabel); _nameEdit = new QLineEdit(QString::fromStdString("horizons.dat"), this); - _nameEdit->setToolTip("Name of the generated Horizons file. Must end with '.dat'"); + _nameEdit->setToolTip( + "Name of the generated Horizons file. Must end with '.dat'" + ); container->addWidget(_nameEdit); layout->addLayout(container); @@ -190,7 +177,9 @@ void HorizonsDialog::createWidgets() { container->addWidget(directoryLabel); _directoryEdit = new QLineEdit(this); - _directoryEdit->setToolTip("Directory where the generated Horizons file is saved"); + _directoryEdit->setToolTip( + "Directory where the generated Horizons file is saved" + ); container->addWidget(_directoryEdit); QPushButton* directoryButton = new QPushButton("Browse", this); @@ -210,15 +199,20 @@ void HorizonsDialog::createWidgets() { QLabel* targetLabel = new QLabel("Target Body:", this); container->addWidget(targetLabel); - _targetEdit = new QLineEdit(QString::fromStdString("Mars Reconnaissance Orbiter"), this); - _targetEdit->setToolTip("Which target or body would you like Horizons trajectery data for?"); + _targetEdit = + new QLineEdit(QString::fromStdString("Mars Reconnaissance Orbiter"), this); + _targetEdit->setToolTip( + "Which target or body would you like Horizons trajectery data for?" + ); container->addWidget(_targetEdit); layout->addLayout(container); _chooseTargetCombo = new QComboBox(this); _chooseTargetCombo->hide(); - _chooseTargetCombo->setToolTip("Choose a target from the search, or search again"); + _chooseTargetCombo->setToolTip( + "Choose a target from the search, or search again" + ); layout->addWidget(_chooseTargetCombo); } { @@ -234,7 +228,9 @@ void HorizonsDialog::createWidgets() { _chooseObserverCombo = new QComboBox(this); _chooseObserverCombo->hide(); - _chooseObserverCombo->setToolTip("Choose an observer from the search, or search again"); + _chooseObserverCombo->setToolTip( + "Choose an observer from the search, or search again" + ); layout->addWidget(_chooseObserverCombo); } { @@ -348,12 +344,12 @@ bool HorizonsDialog::handleRequest() { return false; } - std::filesystem::path file = handleAnswer(answer); - if (!std::filesystem::is_regular_file(file)) { + openspace::HorizonsFile file = handleAnswer(answer); + if (file.isEmpty()) { return false; } - _horizonsFile = openspace::HorizonsFile(file); + _horizonsFile = std::move(file); openspace::HorizonsFile::ResultCode result = _horizonsFile.isValidHorizonsFile(); @@ -460,7 +456,8 @@ std::string HorizonsDialog::constructUrl() { std::string command; if (_chooseTargetCombo->count() > 0 && _chooseTargetCombo->currentIndex() != 0) { - command = _chooseTargetCombo->itemData(_chooseTargetCombo->currentIndex()).toString().toStdString(); + command = _chooseTargetCombo->itemData(_chooseTargetCombo->currentIndex()) + .toString().toStdString(); _targetName = _chooseTargetCombo->currentText().toStdString(); _targetEdit->setText(command.c_str()); } @@ -518,7 +515,15 @@ std::string HorizonsDialog::constructUrl() { return ""; } - return openspace::HorizonsFile::constructUrl(type, command, center, _startTime, _endTime, _stepEdit->text().toStdString(), unit); + return openspace::HorizonsFile::constructUrl( + type, + command, + center, + _startTime, + _endTime, + _stepEdit->text().toStdString(), + unit + ); } // Send request synchronously, EventLoop waits until request has finished @@ -591,7 +596,9 @@ json HorizonsDialog::handleReply(QNetworkReply* reply) { bool HorizonsDialog::checkHttpStatus(const QVariant& statusCode) { bool isKnown = true; - if (statusCode.isValid() && statusCode.toInt() != int(HorizonsDialog::HTTPCodes::Ok)) { + if (statusCode.isValid() && + statusCode.toInt() != int(HorizonsDialog::HTTPCodes::Ok)) + { std::string message; int code = statusCode.toInt(); @@ -628,8 +635,9 @@ bool HorizonsDialog::checkHttpStatus(const QVariant& statusCode) { return isKnown; } -std::filesystem::path HorizonsDialog::handleAnswer(json& answer) { - openspace::HorizonsFile::ResultCode isValid = openspace::HorizonsFile::isValidAnswer(answer); +openspace::HorizonsFile HorizonsDialog::handleAnswer(json& answer) { + openspace::HorizonsFile::ResultCode isValid = + openspace::HorizonsFile::isValidAnswer(answer); if (isValid != openspace::HorizonsFile::ResultCode::Valid && isValid != openspace::HorizonsFile::ResultCode::MultipleObserverStations && isValid != openspace::HorizonsFile::ResultCode::ErrorTimeRange) @@ -637,9 +645,9 @@ std::filesystem::path HorizonsDialog::handleAnswer(json& answer) { // Special case with MultipleObserverStations since it is detected as an error // but could be fixed by parsing the matches and let user choose // Special case with ErrorTimeRange since it is detected as an error - // but could be nice to display the avalable itme range of target to the user + // but could be nice to display the available time range of target to the user handleResult(isValid); - return std::filesystem::path(); + return openspace::HorizonsFile(); } // Create a text file and write reply to it @@ -655,21 +663,17 @@ std::filesystem::path HorizonsDialog::handleAnswer(json& answer) { "Malformed answer recieved: " + answer.dump(), HorizonsDialog::LogLevel::Error ); - return std::filesystem::path(); + return openspace::HorizonsFile(); } // 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(); + return openspace::HorizonsFile(); } - // Write response into a new file - std::ofstream file(filePath); - file << replaceAll(*result, "\\n", "\n") << std::endl; - file.close(); - - return fullFilePath; + // Return a new file with the result + return openspace::HorizonsFile(fullFilePath, *result); } bool HorizonsDialog::handleResult(openspace::HorizonsFile::ResultCode& result) { @@ -735,7 +739,8 @@ bool HorizonsDialog::handleResult(openspace::HorizonsFile::ResultCode& result) { 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 + - "'. Try to use another observer for the current target or another time range.", + "'. Try to use another observer for the current target or another " + "time range.", HorizonsDialog::LogLevel::Error ); break; @@ -750,7 +755,10 @@ bool HorizonsDialog::handleResult(openspace::HorizonsFile::ResultCode& result) { ); std::vector matchingstations = - _horizonsFile.parseMatches("Observatory Name", "Multiple matching stations found"); + _horizonsFile.parseMatches( + "Observatory Name", + "Multiple matching stations found" + ); if (matchingstations.empty()) { appendLog("Could not parse the matching stations", HorizonsDialog::LogLevel::Error @@ -805,7 +813,8 @@ bool HorizonsDialog::handleResult(openspace::HorizonsFile::ResultCode& result) { // Case Small Bodies: // Line before data: Matching small-bodies // Format: Record #, Epoch-yr, >MATCH DESIG<, Primary Desig, Name - // Line after data: (X matches. To SELECT, enter record # (integer), followed by semi-colon.) + // Line after data: + // (X matches. To SELECT, enter record # (integer), followed by semi-colon.) // Case Major Bodies: // Line before data: Multiple major-bodies match string "X*" diff --git a/modules/space/horizonsfile.cpp b/modules/space/horizonsfile.cpp index eb3fc6556c..fda4b97fce 100644 --- a/modules/space/horizonsfile.cpp +++ b/modules/space/horizonsfile.cpp @@ -42,9 +42,9 @@ namespace { constexpr const char* VectorUrl = "https://ssd.jpl.nasa.gov/api/horizons.api?format=" "json&MAKE_EPHEM='YES'&EPHEM_TYPE='VECTORS'&VEC_TABLE='1'&VEC_LABELS='NO'&" "CSV_FORMAT='NO'"; - constexpr const char* ObserverUrl = "https://ssd.jpl.nasa.gov/api/horizons.api?format=" - "json&MAKE_EPHEM='YES'&EPHEM_TYPE='OBSERVER'&QUANTITIES='20,33'&RANGE_UNITS='KM'&" - "SUPPRESS_RANGE_RATE='YES'&CSV_FORMAT='NO'"; + constexpr const char* ObserverUrl = "https://ssd.jpl.nasa.gov/api/horizons.api?" + "format=json&MAKE_EPHEM='YES'&EPHEM_TYPE='OBSERVER'&QUANTITIES='20,33'&" + "RANGE_UNITS='KM'&SUPPRESS_RANGE_RATE='YES'&CSV_FORMAT='NO'"; constexpr const char* Command = "&COMMAND="; constexpr const char* Center = "&CENTER="; constexpr const char* StartTime = "&START_TIME="; @@ -52,7 +52,9 @@ namespace { constexpr const char* StepSize = "&STEP_SIZE="; constexpr const char* WhiteSpace = "%20"; - std::string replaceAll(const std::string& string, const std::string& from, const std::string& to) { + std::string replaceAll(const std::string& string, const std::string& from, + const std::string& to) + { if (from.empty()) return ""; @@ -60,7 +62,9 @@ namespace { size_t startPos = 0; while ((startPos = result.find(from, startPos)) != std::string::npos) { result.replace(startPos, from.length(), to); - startPos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx' + + // In case 'to' contains 'from', ex replacing 'x' with 'yx' + startPos += to.length(); } return result; } @@ -76,6 +80,14 @@ HorizonsFile::HorizonsFile(std::filesystem::path file) : _file(std::move(file)) {} +HorizonsFile::HorizonsFile(std::filesystem::path filePath, const std::string& result) { + // Write the response into a new file and save it + std::ofstream file(filePath); + file << replaceAll(result, "\\n", "\n") << std::endl; + file.close(); + _file = std::move(filePath); +} + void HorizonsFile::setFile(std::filesystem::path file) { _file = std::move(file); } @@ -142,7 +154,9 @@ HorizonsFile::ResultCode HorizonsFile::isValidAnswer(const json& answer) { auto sourceIt = signature.find("source"); if (sourceIt != signature.end()) { if (*sourceIt != ApiSource) { - LWARNING(fmt::format("Horizons answer from unkown source '{}'", *sourceIt)); + LWARNING(fmt::format("Horizons answer from unkown source '{}'", + *sourceIt) + ); } } else { @@ -157,7 +171,9 @@ HorizonsFile::ResultCode HorizonsFile::isValidAnswer(const json& answer) { } } else { - LWARNING("Could not find version information, version might not be supported"); + LWARNING( + "Could not find version information, version might not be supported" + ); } } else { @@ -201,7 +217,9 @@ HorizonsFile::ResultCode HorizonsFile::isValidAnswer(const json& answer) { // -- - -------- ------ - ------ - ----------------; // * Observer station * // Multiple matching stations found. - else if (errorMessage.find("Multiple matching stations found") != std::string::npos) { + else if (errorMessage.find("Multiple matching stations found") != + std::string::npos) + { return ResultCode::MultipleObserverStations; } // Unknown error @@ -213,6 +231,10 @@ HorizonsFile::ResultCode HorizonsFile::isValidAnswer(const json& answer) { return ResultCode::Valid; } +bool HorizonsFile::isEmpty() const { + return !std::filesystem::is_regular_file(_file); +} + // Check whether the given Horizons file is valid or not // Return an error code with what is the problem if there was one HorizonsFile::ResultCode HorizonsFile::isValidHorizonsFile() const { @@ -230,7 +252,10 @@ HorizonsFile::ResultCode HorizonsFile::isValidHorizonsFile() const { std::getline(fileStream, line); // First line is just stars (*) no information, skip // Valid Target? - if (fileStream.good() && (line.find("Revised") != std::string::npos || line.find("JPL") != std::string::npos)) { + if (fileStream.good() && + (line.find("Revised") != std::string::npos || + line.find("JPL") != std::string::npos)) + { // If the target is valid, the first line is the date it was last revised // In case of comets it says the Source in the top foundTarget = true; @@ -421,7 +446,8 @@ void HorizonsFile::displayErrorMessage(const ResultCode code) const { // Case Small Bodies: // Line before data: Matching small-bodies // Format: Record #, Epoch-yr, >MATCH DESIG<, Primary Desig, Name - // Line after data: (X matches. To SELECT, enter record # (integer), followed by semi-colon.) + // Line after data: + // (X matches. To SELECT, enter record # (integer), followed by semi-colon.) // Case Major Bodies: // Line before data: Multiple major-bodies match string "X*" @@ -635,7 +661,7 @@ HorizonsFile::HorizonsResult HorizonsFile::readObserverFile() const { } std::vector HorizonsFile::parseMatches(const std::string& startPhrase, - const std::string& endPhrase) const + const std::string& endPhrase) const { std::ifstream fileStream(_file); std::vector matches; diff --git a/modules/space/horizonsfile.h b/modules/space/horizonsfile.h index f6d1fb0331..829fd53a70 100644 --- a/modules/space/horizonsfile.h +++ b/modules/space/horizonsfile.h @@ -37,18 +37,30 @@ 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: + * (https://ssd.jpl.nasa.gov/horizons.cgi). The implementation supports both Vector + * and Observer as Horizons data table + * + * In case of Vector table data the implementation expects a file with format: + * TIME(JulianDayNumber = A.D. YYYY-MM-DD HH:MM:SS TDB) + * X(km) Y(km) Z(km) + * TIME - Only the "YYYY-MM-DD HH:MM:SS" part is of interest, the rest is ignored + * X - X position in kilometers in Ecliptic J2000 reference frame + * Y - Y position in kilometers in Ecliptic J2000 reference frame + * Z - Z position in kilometers in Ecliptic J2000 reference frame + * Changes required in the "Table Settings" for compatible data: + * 1. Under "Select Output Quantities" choose option "Position components {x, y, z} only" + * 2. Uncheck the "Vector labels" options + * + * In case of Observer table data the implementation 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 + * Range - The distance from target to observer in kilometers + * GalLon - Galactic Longitude in degrees + * GalLat - Galactic Latitude in degrees + * Changes required in the "Table Settings" for compatible data: + * 1. Under "Observer Table Settings" uncheck all options except + * "Observer range & range-rate" and "Galactic longitude & latitude" + * 2. Change "Range units" to "kilometers (km)" instead of "astronomical units (au)" + * 3. Check the "Suppress range-rate" option */ class HorizonsFile { public: @@ -97,15 +109,18 @@ public: HorizonsFile(); HorizonsFile(std::filesystem::path file); + HorizonsFile(std::filesystem::path filePath, const std::string& result); void setFile(std::filesystem::path file); const std::filesystem::path& file() const; static std::string constructUrl(Type type, const std::string& target, const std::string& observer, const std::string& startTime, - const std::string& stopTime, const std::string& stepSize, const std::string& unit); + const std::string& stopTime, const std::string& stepSize, + const std::string& unit); static ResultCode isValidAnswer(const json& answer); + bool isEmpty() const; ResultCode isValidHorizonsFile() const; void displayErrorMessage(const ResultCode code) const; HorizonsResult readFile() const; diff --git a/modules/space/translation/horizonstranslation.h b/modules/space/translation/horizonstranslation.h index 5a23dd9d61..fa73028e61 100644 --- a/modules/space/translation/horizonstranslation.h +++ b/modules/space/translation/horizonstranslation.h @@ -41,7 +41,7 @@ namespace documentation { struct Documentation; } /** * The HorizonsTranslation is based on text files generated from NASA JPL HORIZONS Website - * (https://ssd.jpl.nasa.gov/horizons.cgi). The implementation supports both Vector table + * (https://ssd.jpl.nasa.gov/horizons.cgi). The implementation supports both Vector * and Observer as Horizons data table * * In case of Vector table data the implementation expects a file with format: @@ -52,7 +52,7 @@ namespace documentation { struct Documentation; } * Y - Y position in kilometers in Ecliptic J2000 reference frame * Z - Z position in kilometers in Ecliptic J2000 reference frame * Changes required in the "Table Settings" for compatible data: - * 1. Under "Select Output Quantities" choose option "Position components {x, y, z} only + * 1. Under "Select Output Quantities" choose option "Position components {x, y, z} only" * 2. Uncheck the "Vector labels" options * * In case of Observer table data the implementation expects a file with format: