AmdaResultParser.cpp
167 lines
| 5.4 KiB
| text/x-c
|
CppLexer
|
r380 | #include "AmdaResultParser.h" | ||
|
r489 | #include <Common/DateUtils.h> | ||
|
r380 | #include <Data/ScalarSeries.h> | ||
#include <QDateTime> | ||||
#include <QFile> | ||||
|
r393 | #include <QRegularExpression> | ||
|
r380 | |||
r400 | #include <cmath> | |||
|
r380 | Q_LOGGING_CATEGORY(LOG_AmdaResultParser, "AmdaResultParser") | ||
namespace { | ||||
|
r446 | /// Message in result file when the file was not found on server | ||
const auto FILE_NOT_FOUND_MESSAGE = QStringLiteral("Not Found"); | ||||
|
r380 | /// Format for dates in result files | ||
const auto DATE_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz"); | ||||
|
r394 | /// Separator between values in a result line | ||
const auto RESULT_LINE_SEPARATOR = QRegularExpression{QStringLiteral("\\s+")}; | ||||
|
r393 | /// Regex to find unit in a line. Examples of valid lines: | ||
/// ... - Units : nT - ... | ||||
/// ... -Units:nT- ... | ||||
/// ... -Units: m²- ... | ||||
/// ... - Units : m/s - ... | ||||
const auto UNIT_REGEX = QRegularExpression{QStringLiteral("-\\s*Units\\s*:\\s*(.+?)\\s*-")}; | ||||
|
r394 | /// Converts a string date to a double date | ||
/// @return a double that represents the date in seconds, NaN if the string date can't be converted | ||||
|
r380 | double doubleDate(const QString &stringDate) noexcept | ||
{ | ||||
auto dateTime = QDateTime::fromString(stringDate, DATE_FORMAT); | ||||
|
r489 | dateTime.setTimeSpec(Qt::UTC); | ||
return dateTime.isValid() ? DateUtils::secondsSinceEpoch(dateTime) | ||||
|
r394 | : std::numeric_limits<double>::quiet_NaN(); | ||
|
r380 | } | ||
|
r492 | /// Checks if a line is a comment line | ||
bool isCommentLine(const QString &line) | ||||
{ | ||||
return line.startsWith("#"); | ||||
} | ||||
|
r393 | /** | ||
* Reads stream to retrieve x-axis unit | ||||
* @param stream the stream to read | ||||
* @return the unit that has been read in the stream, a default unit (time unit with no label) if an | ||||
* error occured during reading | ||||
*/ | ||||
Unit readXAxisUnit(QTextStream &stream) | ||||
{ | ||||
QString line{}; | ||||
|
r492 | // Searches unit in the comment lines | ||
while (stream.readLineInto(&line) && isCommentLine(line)) { | ||||
|
r393 | auto match = UNIT_REGEX.match(line); | ||
if (match.hasMatch()) { | ||||
return Unit{match.captured(1), true}; | ||||
} | ||||
} | ||||
|
r492 | qCWarning(LOG_AmdaResultParser()) << QObject::tr("The unit could not be found in the file"); | ||
|
r393 | // Error cases | ||
return Unit{{}, true}; | ||||
} | ||||
|
r394 | /** | ||
* Reads stream to retrieve results | ||||
* @param stream the stream to read | ||||
* @return the pair of vectors x-axis data/values data that has been read in the stream | ||||
*/ | ||||
QPair<QVector<double>, QVector<double> > readResults(QTextStream &stream) | ||||
{ | ||||
auto xData = QVector<double>{}; | ||||
auto valuesData = QVector<double>{}; | ||||
QString line{}; | ||||
|
r492 | |||
|
r394 | while (stream.readLineInto(&line)) { | ||
|
r492 | // Ignore comment lines | ||
if (!isCommentLine(line)) { | ||||
auto lineData = line.split(RESULT_LINE_SEPARATOR, QString::SkipEmptyParts); | ||||
if (lineData.size() == 2) { | ||||
// X : the data is converted from date to double (in secs) | ||||
auto x = doubleDate(lineData.at(0)); | ||||
// Value | ||||
bool valueOk; | ||||
auto value = lineData.at(1).toDouble(&valueOk); | ||||
|
r496 | // Adds result only if x is valid. Then, if value is invalid, it is set to NaN | ||
if (!std::isnan(x)) { | ||||
|
r492 | xData.push_back(x); | ||
|
r496 | |||
if (!valueOk) { | ||||
qCWarning(LOG_AmdaResultParser()) | ||||
<< QObject::tr( | ||||
"Value from line %1 is invalid and will be converted to NaN") | ||||
.arg(line); | ||||
value = std::numeric_limits<double>::quiet_NaN(); | ||||
} | ||||
|
r492 | valuesData.push_back(value); | ||
} | ||||
else { | ||||
qCWarning(LOG_AmdaResultParser()) | ||||
|
r496 | << QObject::tr("Can't retrieve results from line %1: x is invalid") | ||
|
r492 | .arg(line); | ||
} | ||||
|
r394 | } | ||
else { | ||||
qCWarning(LOG_AmdaResultParser()) | ||||
|
r492 | << QObject::tr("Can't retrieve results from line %1: invalid line").arg(line); | ||
|
r394 | } | ||
} | ||||
} | ||||
return qMakePair(std::move(xData), std::move(valuesData)); | ||||
} | ||||
|
r380 | } // namespace | ||
|
r563 | std::shared_ptr<IDataSeries> AmdaResultParser::readTxt(const QString &filePath, | ||
ValueType valueType) noexcept | ||||
|
r380 | { | ||
|
r563 | if (valueType == ValueType::UNKNOWN) { | ||
qCCritical(LOG_AmdaResultParser()) | ||||
<< QObject::tr("Can't retrieve AMDA data: the type of values to be read is unknown"); | ||||
return nullptr; | ||||
} | ||||
|
r380 | QFile file{filePath}; | ||
if (!file.open(QFile::ReadOnly | QIODevice::Text)) { | ||||
qCCritical(LOG_AmdaResultParser()) | ||||
<< QObject::tr("Can't retrieve AMDA data from file %1: %2") | ||||
.arg(filePath, file.errorString()); | ||||
return nullptr; | ||||
} | ||||
QTextStream stream{&file}; | ||||
|
r446 | // Checks if the file was found on the server | ||
auto firstLine = stream.readLine(); | ||||
if (firstLine.compare(FILE_NOT_FOUND_MESSAGE) == 0) { | ||||
qCCritical(LOG_AmdaResultParser()) | ||||
<< QObject::tr("Can't retrieve AMDA data from file %1: file was not found on server") | ||||
.arg(filePath); | ||||
return nullptr; | ||||
} | ||||
|
r393 | // Reads x-axis unit | ||
|
r492 | stream.seek(0); // returns to the beginning of the file | ||
|
r393 | auto xAxisUnit = readXAxisUnit(stream); | ||
// Reads results | ||||
|
r492 | stream.seek(0); // returns to the beginning of the file | ||
|
r394 | auto results = readResults(stream); | ||
|
r380 | |||
|
r394 | return std::make_shared<ScalarSeries>(std::move(results.first), std::move(results.second), | ||
xAxisUnit, Unit{}); | ||||
|
r380 | } | ||