AmdaResultParser.cpp
217 lines
| 7.4 KiB
| text/x-c
|
CppLexer
Alexandre Leroux
|
r380 | #include "AmdaResultParser.h" | ||
Alexandre Leroux
|
r489 | #include <Common/DateUtils.h> | ||
Alexandre Leroux
|
r380 | #include <Data/ScalarSeries.h> | ||
Alexandre Leroux
|
r565 | #include <Data/VectorSeries.h> | ||
Alexandre Leroux
|
r380 | |||
#include <QDateTime> | ||||
#include <QFile> | ||||
Alexandre Leroux
|
r393 | #include <QRegularExpression> | ||
Alexandre Leroux
|
r380 | |||
r400 | #include <cmath> | |||
Alexandre Leroux
|
r380 | Q_LOGGING_CATEGORY(LOG_AmdaResultParser, "AmdaResultParser") | ||
namespace { | ||||
Alexandre Leroux
|
r446 | /// Message in result file when the file was not found on server | ||
const auto FILE_NOT_FOUND_MESSAGE = QStringLiteral("Not Found"); | ||||
Alexandre Leroux
|
r380 | /// Format for dates in result files | ||
const auto DATE_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz"); | ||||
Alexandre Leroux
|
r394 | /// Separator between values in a result line | ||
const auto RESULT_LINE_SEPARATOR = QRegularExpression{QStringLiteral("\\s+")}; | ||||
Alexandre Leroux
|
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*-")}; | ||||
Alexandre Leroux
|
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 | ||||
Alexandre Leroux
|
r380 | double doubleDate(const QString &stringDate) noexcept | ||
{ | ||||
auto dateTime = QDateTime::fromString(stringDate, DATE_FORMAT); | ||||
Alexandre Leroux
|
r489 | dateTime.setTimeSpec(Qt::UTC); | ||
return dateTime.isValid() ? DateUtils::secondsSinceEpoch(dateTime) | ||||
Alexandre Leroux
|
r394 | : std::numeric_limits<double>::quiet_NaN(); | ||
Alexandre Leroux
|
r380 | } | ||
Alexandre Leroux
|
r492 | /// Checks if a line is a comment line | ||
bool isCommentLine(const QString &line) | ||||
{ | ||||
return line.startsWith("#"); | ||||
} | ||||
Alexandre Leroux
|
r564 | /// @return the number of lines to be read depending on the type of value passed in parameter | ||
int nbValues(AmdaResultParser::ValueType valueType) noexcept | ||||
{ | ||||
switch (valueType) { | ||||
case AmdaResultParser::ValueType::SCALAR: | ||||
return 1; | ||||
case AmdaResultParser::ValueType::VECTOR: | ||||
return 3; | ||||
case AmdaResultParser::ValueType::UNKNOWN: | ||||
// Invalid case | ||||
break; | ||||
} | ||||
// Invalid cases | ||||
qCCritical(LOG_AmdaResultParser()) | ||||
<< QObject::tr("Can't get the number of values to read: unsupported type"); | ||||
return 0; | ||||
} | ||||
Alexandre Leroux
|
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{}; | ||||
Alexandre Leroux
|
r492 | // Searches unit in the comment lines | ||
while (stream.readLineInto(&line) && isCommentLine(line)) { | ||||
Alexandre Leroux
|
r393 | auto match = UNIT_REGEX.match(line); | ||
if (match.hasMatch()) { | ||||
return Unit{match.captured(1), true}; | ||||
} | ||||
} | ||||
Alexandre Leroux
|
r492 | qCWarning(LOG_AmdaResultParser()) << QObject::tr("The unit could not be found in the file"); | ||
Alexandre Leroux
|
r393 | // Error cases | ||
return Unit{{}, true}; | ||||
} | ||||
Alexandre Leroux
|
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 | ||||
*/ | ||||
Alexandre Leroux
|
r564 | QPair<QVector<double>, QVector<QVector<double> > > | ||
readResults(QTextStream &stream, AmdaResultParser::ValueType valueType) | ||||
Alexandre Leroux
|
r394 | { | ||
Alexandre Leroux
|
r564 | auto expectedNbValues = nbValues(valueType); | ||
Alexandre Leroux
|
r394 | auto xData = QVector<double>{}; | ||
Alexandre Leroux
|
r564 | auto valuesData = QVector<QVector<double> >(expectedNbValues); | ||
Alexandre Leroux
|
r394 | |||
QString line{}; | ||||
Alexandre Leroux
|
r492 | |||
Alexandre Leroux
|
r394 | while (stream.readLineInto(&line)) { | ||
Alexandre Leroux
|
r492 | // Ignore comment lines | ||
if (!isCommentLine(line)) { | ||||
auto lineData = line.split(RESULT_LINE_SEPARATOR, QString::SkipEmptyParts); | ||||
Alexandre Leroux
|
r564 | if (lineData.size() == expectedNbValues + 1) { | ||
Alexandre Leroux
|
r492 | // X : the data is converted from date to double (in secs) | ||
auto x = doubleDate(lineData.at(0)); | ||||
Alexandre Leroux
|
r496 | // Adds result only if x is valid. Then, if value is invalid, it is set to NaN | ||
if (!std::isnan(x)) { | ||||
Alexandre Leroux
|
r492 | xData.push_back(x); | ||
Alexandre Leroux
|
r496 | |||
Alexandre Leroux
|
r564 | // Values | ||
for (auto valueIndex = 0; valueIndex < expectedNbValues; ++valueIndex) { | ||||
auto column = valueIndex + 1; | ||||
bool valueOk; | ||||
auto value = lineData.at(column).toDouble(&valueOk); | ||||
if (!valueOk) { | ||||
qCWarning(LOG_AmdaResultParser()) | ||||
<< QObject::tr( | ||||
"Value from (line %1, column %2) is invalid and will be " | ||||
"converted to NaN") | ||||
.arg(line, column); | ||||
value = std::numeric_limits<double>::quiet_NaN(); | ||||
} | ||||
valuesData[valueIndex].append(value); | ||||
Alexandre Leroux
|
r496 | } | ||
Alexandre Leroux
|
r492 | } | ||
else { | ||||
qCWarning(LOG_AmdaResultParser()) | ||||
Alexandre Leroux
|
r496 | << QObject::tr("Can't retrieve results from line %1: x is invalid") | ||
Alexandre Leroux
|
r492 | .arg(line); | ||
} | ||||
Alexandre Leroux
|
r394 | } | ||
else { | ||||
qCWarning(LOG_AmdaResultParser()) | ||||
Alexandre Leroux
|
r492 | << QObject::tr("Can't retrieve results from line %1: invalid line").arg(line); | ||
Alexandre Leroux
|
r394 | } | ||
} | ||||
} | ||||
return qMakePair(std::move(xData), std::move(valuesData)); | ||||
} | ||||
Alexandre Leroux
|
r380 | } // namespace | ||
Alexandre Leroux
|
r563 | std::shared_ptr<IDataSeries> AmdaResultParser::readTxt(const QString &filePath, | ||
ValueType valueType) noexcept | ||||
Alexandre Leroux
|
r380 | { | ||
Alexandre Leroux
|
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; | ||||
} | ||||
Alexandre Leroux
|
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}; | ||||
Alexandre Leroux
|
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; | ||||
} | ||||
Alexandre Leroux
|
r393 | // Reads x-axis unit | ||
Alexandre Leroux
|
r492 | stream.seek(0); // returns to the beginning of the file | ||
Alexandre Leroux
|
r393 | auto xAxisUnit = readXAxisUnit(stream); | ||
// Reads results | ||||
Alexandre Leroux
|
r492 | stream.seek(0); // returns to the beginning of the file | ||
Alexandre Leroux
|
r564 | auto results = readResults(stream, valueType); | ||
Alexandre Leroux
|
r565 | // Creates data series | ||
switch (valueType) { | ||||
case ValueType::SCALAR: | ||||
Q_ASSERT(results.second.size() == 1); | ||||
return std::make_shared<ScalarSeries>( | ||||
Alexandre Leroux
|
r694 | std::move(results.first.toStdVector()), | ||
std::move(results.second.takeFirst().toStdVector()), xAxisUnit, Unit{}); | ||||
Alexandre Leroux
|
r565 | case ValueType::VECTOR: { | ||
Q_ASSERT(results.second.size() == 3); | ||||
Alexandre Leroux
|
r694 | auto xValues = results.second.takeFirst().toStdVector(); | ||
auto yValues = results.second.takeFirst().toStdVector(); | ||||
auto zValues = results.second.takeFirst().toStdVector(); | ||||
return std::make_shared<VectorSeries>(std::move(results.first.toStdVector()), | ||||
std::move(xValues), std::move(yValues), | ||||
std::move(zValues), xAxisUnit, Unit{}); | ||||
Alexandre Leroux
|
r565 | } | ||
case ValueType::UNKNOWN: | ||||
// Invalid case | ||||
break; | ||||
} | ||||
Alexandre Leroux
|
r380 | |||
Alexandre Leroux
|
r565 | // Invalid cases | ||
qCCritical(LOG_AmdaResultParser()) | ||||
<< QObject::tr("Can't create data series: unsupported value type"); | ||||
return nullptr; | ||||
Alexandre Leroux
|
r380 | } | ||