AmdaResultParser.cpp
225 lines
| 7.6 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
|
r394 | /// Separator between values in a result line | ||
const auto RESULT_LINE_SEPARATOR = QRegularExpression{QStringLiteral("\\s+")}; | ||||
Alexandre Leroux
|
r775 | /// Regex to find the header of the data in the file. This header indicates the end of comments in | ||
/// the file | ||||
const auto DATA_HEADER_REGEX = QRegularExpression{QStringLiteral("#\\s*DATA\\s*:")}; | ||||
/// Format for dates in result files | ||||
const auto DATE_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz"); | ||||
Alexandre Leroux
|
r393 | /// Regex to find unit in a line. Examples of valid lines: | ||
Alexandre Leroux
|
r775 | /// ... PARAMETER_UNITS : nT ... | ||
/// ... PARAMETER_UNITS:nT ... | ||||
/// ... PARAMETER_UNITS: m² ... | ||||
/// ... PARAMETER_UNITS : m/s ... | ||||
const auto UNIT_REGEX = QRegularExpression{QStringLiteral("\\s*PARAMETER_UNITS\\s*:\\s*(.+)")}; | ||||
QDateTime dateTimeFromString(const QString &stringDate) noexcept | ||||
{ | ||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) | ||||
return QDateTime::fromString(stringDate, Qt::ISODateWithMs); | ||||
#else | ||||
return QDateTime::fromString(stringDate, DATE_FORMAT); | ||||
#endif | ||||
} | ||||
Alexandre Leroux
|
r393 | |||
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 | ||
{ | ||||
Alexandre Leroux
|
r775 | // Format: yyyy-MM-ddThh:mm:ss.zzz | ||
auto dateTime = dateTimeFromString(stringDate); | ||||
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
|
r775 | // Searches unit in the comment lines (as long as the reading has not reached the data header) | ||
while (stream.readLineInto(&line) && !line.contains(DATA_HEADER_REGEX)) { | ||||
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
|
r733 | std::pair<std::vector<double>, std::vector<double> > | ||
Alexandre Leroux
|
r564 | readResults(QTextStream &stream, AmdaResultParser::ValueType valueType) | ||
Alexandre Leroux
|
r394 | { | ||
Alexandre Leroux
|
r775 | auto expectedNbValues = nbValues(valueType) + 1; | ||
Alexandre Leroux
|
r564 | |||
Alexandre Leroux
|
r733 | auto xData = std::vector<double>{}; | ||
auto valuesData = std::vector<double>{}; | ||||
Alexandre Leroux
|
r394 | |||
QString line{}; | ||||
Alexandre Leroux
|
r492 | |||
Alexandre Leroux
|
r775 | // Skip comment lines | ||
while (stream.readLineInto(&line) && isCommentLine(line)) { | ||||
} | ||||
if (!stream.atEnd()) { | ||||
do { | ||||
Alexandre Leroux
|
r492 | auto lineData = line.split(RESULT_LINE_SEPARATOR, QString::SkipEmptyParts); | ||
Alexandre Leroux
|
r775 | if (lineData.size() == expectedNbValues) { | ||
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 | ||
Alexandre Leroux
|
r775 | for (auto valueIndex = 1; valueIndex < expectedNbValues; ++valueIndex) { | ||
auto column = valueIndex; | ||||
Alexandre Leroux
|
r564 | |||
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(); | ||||
} | ||||
Alexandre Leroux
|
r733 | valuesData.push_back(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 | } | ||
Alexandre Leroux
|
r775 | } while (stream.readLineInto(&line)); | ||
Alexandre Leroux
|
r394 | } | ||
Alexandre Leroux
|
r733 | return std::make_pair(std::move(xData), std::move(valuesData)); | ||
Alexandre Leroux
|
r394 | } | ||
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
|
r564 | auto results = readResults(stream, valueType); | ||
Alexandre Leroux
|
r565 | // Creates data series | ||
switch (valueType) { | ||||
case ValueType::SCALAR: | ||||
Alexandre Leroux
|
r733 | return std::make_shared<ScalarSeries>(std::move(results.first), | ||
std::move(results.second), xAxisUnit, Unit{}); | ||||
case ValueType::VECTOR: | ||||
return std::make_shared<VectorSeries>(std::move(results.first), | ||||
std::move(results.second), 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 | } | ||