mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-05 11:09:37 -06:00
Move some functions to a new HorizonsFile class
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
#define __OPENSPACE_UI_LAUNCHER___HORIZONS___H__
|
||||
|
||||
#include <openspace/json.h>
|
||||
#include <../modules/space/horizonsfile.h>
|
||||
#include <QDialog>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
@@ -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;
|
||||
|
||||
@@ -283,7 +283,7 @@ void HorizonsDialog::createWidgets() {
|
||||
layout->addWidget(generateLabel);
|
||||
|
||||
QUrl website("https://ssd.jpl.nasa.gov/horizons/");
|
||||
QLabel* infoLabel = new QLabel("<p>For more information about the Horizons system"
|
||||
QLabel* infoLabel = new QLabel("<p>For more information about the Horizons system "
|
||||
"please visit: <a href=\"https://ssd.jpl.nasa.gov/horizons/\">"
|
||||
"https://ssd.jpl.nasa.gov/horizons/</a></p>",
|
||||
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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
207
modules/space/horizonsfile.cpp
Normal file
207
modules/space/horizonsfile.cpp
Normal file
@@ -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 <modules/space/horizonsfile.h>
|
||||
|
||||
#include <ghoul/fmt.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/logging/logmanager.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
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
|
||||
88
modules/space/horizonsfile.h
Normal file
88
modules/space/horizonsfile.h
Normal file
@@ -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 <openspace/json.h>
|
||||
#include <filesystem>
|
||||
|
||||
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__
|
||||
Reference in New Issue
Block a user