##// END OF EJS Templates
Merge branch 'feature/VectorSeries' into develop
Alexandre Leroux -
r569:385fbf866df8 merge
parent child
Show More
@@ -0,0 +1,30
1 #ifndef SCIQLOP_VECTORSERIES_H
2 #define SCIQLOP_VECTORSERIES_H
3
4 #include "CoreGlobal.h"
5
6 #include <Data/DataSeries.h>
7
8 /**
9 * @brief The VectorSeries class is the implementation for a data series representing a vector.
10 */
11 class SCIQLOP_CORE_EXPORT VectorSeries : public DataSeries<2> {
12 public:
13 /**
14 * Ctor. The vectors must have the same size, otherwise a ScalarSeries with no values will be
15 * created.
16 * @param xAxisData x-axis data
17 * @param xvaluesData x-values data
18 * @param yvaluesData y-values data
19 * @param zvaluesData z-values data
20 */
21 explicit VectorSeries(QVector<double> xAxisData, QVector<double> xValuesData,
22 QVector<double> yValuesData, QVector<double> zValuesData,
23 const Unit &xAxisUnit, const Unit &valuesUnit);
24
25 std::unique_ptr<IDataSeries> clone() const;
26
27 std::shared_ptr<IDataSeries> subDataSeries(const SqpRange &range) override;
28 };
29
30 #endif // SCIQLOP_VECTORSERIES_H
@@ -0,0 +1,39
1 #include "Data/VectorSeries.h"
2
3 VectorSeries::VectorSeries(QVector<double> xAxisData, QVector<double> xValuesData,
4 QVector<double> yValuesData, QVector<double> zValuesData,
5 const Unit &xAxisUnit, const Unit &valuesUnit)
6 : DataSeries{std::make_shared<ArrayData<1> >(std::move(xAxisData)), xAxisUnit,
7 std::make_shared<ArrayData<2> >(QVector<QVector<double> >{
8 std::move(xValuesData), std::move(yValuesData), std::move(zValuesData)}),
9 valuesUnit}
10 {
11 }
12
13 std::unique_ptr<IDataSeries> VectorSeries::clone() const
14 {
15 return std::make_unique<VectorSeries>(*this);
16 }
17
18 std::shared_ptr<IDataSeries> VectorSeries::subDataSeries(const SqpRange &range)
19 {
20 auto subXAxisData = QVector<double>();
21 auto subXValuesData = QVector<double>();
22 auto subYValuesData = QVector<double>();
23 auto subZValuesData = QVector<double>();
24
25 this->lockRead();
26 {
27 auto bounds = subData(range.m_TStart, range.m_TEnd);
28 for (auto it = bounds.first; it != bounds.second; ++it) {
29 subXAxisData.append(it->x());
30 subXValuesData.append(it->value(0));
31 subYValuesData.append(it->value(1));
32 subZValuesData.append(it->value(2));
33 }
34 }
35 this->unlock();
36
37 return std::make_shared<VectorSeries>(subXAxisData, subXValuesData, subYValuesData,
38 subZValuesData, this->xAxisUnit(), this->valuesUnit());
39 }
@@ -0,0 +1,12
1 #Time Format : YYYY-MM-DDThh:mm:ss.mls
2 #imf - Type : Local Parameter @ CDPP/AMDA - Name : imf_gse - Units : nT - Size : 3 - Frame : GSE - Mission : ACE - Instrument : MFI - Dataset : mfi_final-prelim
3 2013-07-02T09:13:50.000 -0.332000 3.20600 0.0580000
4 2013-07-02T09:14:06.000 -1.01100 2.99900 0.496000
5 2013-07-02T09:14:22.000 -1.45700 2.78500 1.01800
6 2013-07-02T09:14:38.000 -1.29300 2.73600 1.48500
7 2013-07-02T09:14:54.000 -1.21700 2.61200 1.66200
8 2013-07-02T09:15:10.000 -1.44300 2.56400 1.50500
9 2013-07-02T09:15:26.000 -1.27800 2.89200 1.16800
10 2013-07-02T09:15:42.000 -1.20200 2.86200 1.24400
11 2013-07-02T09:15:58.000 -1.22000 2.85900 1.15000
12 2013-07-02T09:16:14.000 -1.25900 2.76400 1.35800 No newline at end of file
@@ -290,6 +290,21 public:
290 return m_Data[0];
290 return m_Data[0];
291 }
291 }
292
292
293 // ///////////// //
294 // 2-dim methods //
295 // ///////////// //
296
297 /**
298 * @return the data
299 * @remarks this method is only available for a two-dimensional ArrayData
300 */
301 template <int D = Dim, typename = std::enable_if_t<D == 2> >
302 DataContainer data() const noexcept
303 {
304 QReadLocker locker{&m_Lock};
305 return m_Data;
306 }
307
293 private:
308 private:
294 DataContainer m_Data;
309 DataContainer m_Data;
295 mutable QReadWriteLock m_Lock;
310 mutable QReadWriteLock m_Lock;
@@ -1,20 +1,25
1 #ifndef SCIQLOP_DATASERIES_H
1 #ifndef SCIQLOP_DATASERIES_H
2 #define SCIQLOP_DATASERIES_H
2 #define SCIQLOP_DATASERIES_H
3
3
4 #include "CoreGlobal.h"
5
4 #include <Common/SortUtils.h>
6 #include <Common/SortUtils.h>
5
7
6 #include <Data/ArrayData.h>
8 #include <Data/ArrayData.h>
7 #include <Data/IDataSeries.h>
9 #include <Data/IDataSeries.h>
8
10
9 #include <QLoggingCategory>
11 #include <QLoggingCategory>
10
11 #include <QReadLocker>
12 #include <QReadLocker>
12 #include <QReadWriteLock>
13 #include <QReadWriteLock>
13 #include <memory>
14 #include <memory>
14
15
15 Q_DECLARE_LOGGING_CATEGORY(LOG_DataSeries)
16 // We don't use the Qt macro since the log is used in the header file, which causes multiple log
16 Q_LOGGING_CATEGORY(LOG_DataSeries, "DataSeries")
17 // definitions with inheritance. Inline method is used instead
17
18 inline const QLoggingCategory &LOG_DataSeries()
19 {
20 static const QLoggingCategory category{"DataSeries"};
21 return category;
22 }
18
23
19 /**
24 /**
20 * @brief The DataSeries class is the base (abstract) implementation of IDataSeries.
25 * @brief The DataSeries class is the base (abstract) implementation of IDataSeries.
@@ -27,7 +32,7 Q_LOGGING_CATEGORY(LOG_DataSeries, "DataSeries")
27 *
32 *
28 */
33 */
29 template <int Dim>
34 template <int Dim>
30 class DataSeries : public IDataSeries {
35 class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries {
31 public:
36 public:
32 class IteratorValue {
37 class IteratorValue {
33 public:
38 public:
@@ -18,20 +18,10 std::shared_ptr<IDataSeries> ScalarSeries::subDataSeries(const SqpRange &range)
18 auto subValuesData = QVector<double>();
18 auto subValuesData = QVector<double>();
19 this->lockRead();
19 this->lockRead();
20 {
20 {
21 const auto &currentXData = this->xAxisData()->cdata();
21 auto bounds = subData(range.m_TStart, range.m_TEnd);
22 const auto &currentValuesData = this->valuesData()->cdata();
22 for (auto it = bounds.first; it != bounds.second; ++it) {
23
23 subXAxisData.append(it->x());
24 auto xDataBegin = currentXData.cbegin();
24 subValuesData.append(it->value());
25 auto xDataEnd = currentXData.cend();
26
27 auto lowerIt = std::lower_bound(xDataBegin, xDataEnd, range.m_TStart);
28 auto upperIt = std::upper_bound(xDataBegin, xDataEnd, range.m_TEnd);
29 auto distance = std::distance(xDataBegin, lowerIt);
30
31 auto valuesDataIt = currentValuesData.cbegin() + distance;
32 for (auto xAxisDataIt = lowerIt; xAxisDataIt != upperIt; ++xAxisDataIt, ++valuesDataIt) {
33 subXAxisData.append(*xAxisDataIt);
34 subValuesData.append(*valuesDataIt);
35 }
25 }
36 }
26 }
37 this->unlock();
27 this->unlock();
@@ -9,6 +9,7 ArrayData\.h:\d+:.*IPSIS_S04_VARIABLE.*found: (D)
9 ArrayData\.h:\d+:.*IPSIS_S04_NAMESPACE.*found: (arraydata_detail)
9 ArrayData\.h:\d+:.*IPSIS_S04_NAMESPACE.*found: (arraydata_detail)
10 ArrayData\.h:\d+:.*IPSIS_S06.*found: (D)
10 ArrayData\.h:\d+:.*IPSIS_S06.*found: (D)
11 ArrayData\.h:\d+:.*IPSIS_S06.*found: (Dim)
11 ArrayData\.h:\d+:.*IPSIS_S06.*found: (Dim)
12 DataSeries\.h:\d+:.*IPSIS_S04_METHOD.*found: LOG_DataSeries
12 DataSeries\.h:\d+:.*IPSIS_S04_VARIABLE.*
13 DataSeries\.h:\d+:.*IPSIS_S04_VARIABLE.*
13
14
14 # Ignore false positive relative to iterators
15 # Ignore false positive relative to iterators
@@ -9,6 +9,7
9
9
10 // Relevant keys in JSON file
10 // Relevant keys in JSON file
11 extern const QString AMDA_COMPONENT_KEY;
11 extern const QString AMDA_COMPONENT_KEY;
12 extern const QString AMDA_DATA_TYPE_KEY;
12 extern const QString AMDA_PRODUCT_KEY;
13 extern const QString AMDA_PRODUCT_KEY;
13 extern const QString AMDA_ROOT_KEY;
14 extern const QString AMDA_ROOT_KEY;
14 extern const QString AMDA_XML_ID_KEY;
15 extern const QString AMDA_XML_ID_KEY;
@@ -12,8 +12,10 class IDataSeries;
12 Q_DECLARE_LOGGING_CATEGORY(LOG_AmdaResultParser)
12 Q_DECLARE_LOGGING_CATEGORY(LOG_AmdaResultParser)
13
13
14 struct SCIQLOP_AMDA_EXPORT AmdaResultParser {
14 struct SCIQLOP_AMDA_EXPORT AmdaResultParser {
15 enum class ValueType { SCALAR, VECTOR, UNKNOWN };
15
16
16 static std::shared_ptr<IDataSeries> readTxt(const QString &filePath) noexcept;
17 static std::shared_ptr<IDataSeries> readTxt(const QString &filePath,
18 ValueType valueType) noexcept;
17 };
19 };
18
20
19 #endif // SCIQLOP_AMDARESULTPARSER_H
21 #endif // SCIQLOP_AMDARESULTPARSER_H
@@ -1,6 +1,7
1 #include "AmdaDefs.h"
1 #include "AmdaDefs.h"
2
2
3 const QString AMDA_COMPONENT_KEY = QStringLiteral("component");
3 const QString AMDA_COMPONENT_KEY = QStringLiteral("component");
4 const QString AMDA_DATA_TYPE_KEY = QStringLiteral("dataType");
4 const QString AMDA_PRODUCT_KEY = QStringLiteral("parameter");
5 const QString AMDA_PRODUCT_KEY = QStringLiteral("parameter");
5 const QString AMDA_ROOT_KEY = QStringLiteral("dataCenter");
6 const QString AMDA_ROOT_KEY = QStringLiteral("dataCenter");
6 const QString AMDA_XML_ID_KEY = QStringLiteral("xml:id");
7 const QString AMDA_XML_ID_KEY = QStringLiteral("xml:id");
@@ -32,15 +32,6 void associateActions(DataSourceItem &item, const QUuid &dataSourceUid)
32
32
33 const auto itemType = item.type();
33 const auto itemType = item.type();
34 if (itemType == DataSourceItemType::PRODUCT) {
34 if (itemType == DataSourceItemType::PRODUCT) {
35 /// @todo : As for the moment we do not manage the loading of vectors, in the case of a
36 /// parameter, we update the identifier of download of the data:
37 /// - if the parameter has no component, the identifier remains the same
38 /// - if the parameter has at least one component, the identifier is that of the first
39 /// component (for example, "imf" becomes "imf (0)")
40 if (item.childCount() != 0) {
41 item.setData(AMDA_XML_ID_KEY, item.child(0)->data(AMDA_XML_ID_KEY));
42 }
43
44 addLoadAction(QObject::tr("Load %1 product").arg(item.name()));
35 addLoadAction(QObject::tr("Load %1 product").arg(item.name()));
45 }
36 }
46 else if (itemType == DataSourceItemType::COMPONENT) {
37 else if (itemType == DataSourceItemType::COMPONENT) {
@@ -36,6 +36,19 QString dateFormat(double sqpRange) noexcept
36 return dateTime.toString(AMDA_TIME_FORMAT);
36 return dateTime.toString(AMDA_TIME_FORMAT);
37 }
37 }
38
38
39 AmdaResultParser::ValueType valueType(const QString &valueType)
40 {
41 if (valueType == QStringLiteral("scalar")) {
42 return AmdaResultParser::ValueType::SCALAR;
43 }
44 else if (valueType == QStringLiteral("vector")) {
45 return AmdaResultParser::ValueType::VECTOR;
46 }
47 else {
48 return AmdaResultParser::ValueType::UNKNOWN;
49 }
50 }
51
39 } // namespace
52 } // namespace
40
53
41 AmdaProvider::AmdaProvider()
54 AmdaProvider::AmdaProvider()
@@ -86,6 +99,10 void AmdaProvider::retrieveData(QUuid token, const SqpRange &dateTime, const QVa
86 }
99 }
87 qCDebug(LOG_AmdaProvider()) << tr("AmdaProvider::retrieveData") << dateTime;
100 qCDebug(LOG_AmdaProvider()) << tr("AmdaProvider::retrieveData") << dateTime;
88
101
102 // Retrieves the data type that determines whether the expected format for the result file is
103 // scalar, vector...
104 auto productValueType = valueType(data.value(AMDA_DATA_TYPE_KEY).toString());
105
89 // /////////// //
106 // /////////// //
90 // Creates URL //
107 // Creates URL //
91 // /////////// //
108 // /////////// //
@@ -98,30 +115,31 void AmdaProvider::retrieveData(QUuid token, const SqpRange &dateTime, const QVa
98 auto tempFile = std::make_shared<QTemporaryFile>();
115 auto tempFile = std::make_shared<QTemporaryFile>();
99
116
100 // LAMBDA
117 // LAMBDA
101 auto httpDownloadFinished
118 auto httpDownloadFinished = [this, dateTime, tempFile,
102 = [this, dateTime, tempFile](QNetworkReply *reply, QUuid dataId) noexcept {
119 productValueType](QNetworkReply *reply, QUuid dataId) noexcept {
103
120
104 // Don't do anything if the reply was abort
121 // Don't do anything if the reply was abort
105 if (reply->error() != QNetworkReply::OperationCanceledError) {
122 if (reply->error() != QNetworkReply::OperationCanceledError) {
106
123
107 if (tempFile) {
124 if (tempFile) {
108 auto replyReadAll = reply->readAll();
125 auto replyReadAll = reply->readAll();
109 if (!replyReadAll.isEmpty()) {
126 if (!replyReadAll.isEmpty()) {
110 tempFile->write(replyReadAll);
127 tempFile->write(replyReadAll);
111 }
128 }
112 tempFile->close();
129 tempFile->close();
113
130
114 // Parse results file
131 // Parse results file
115 if (auto dataSeries = AmdaResultParser::readTxt(tempFile->fileName())) {
132 if (auto dataSeries
116 emit dataProvided(dataId, dataSeries, dateTime);
133 = AmdaResultParser::readTxt(tempFile->fileName(), productValueType)) {
117 }
134 emit dataProvided(dataId, dataSeries, dateTime);
118 else {
135 }
119 /// @todo ALX : debug
136 else {
120 }
137 /// @todo ALX : debug
121 }
138 }
122 }
139 }
123
140 }
124 };
141
142 };
125 auto httpFinishedLambda
143 auto httpFinishedLambda
126 = [this, httpDownloadFinished, tempFile](QNetworkReply *reply, QUuid dataId) noexcept {
144 = [this, httpDownloadFinished, tempFile](QNetworkReply *reply, QUuid dataId) noexcept {
127
145
@@ -2,6 +2,7
2
2
3 #include <Common/DateUtils.h>
3 #include <Common/DateUtils.h>
4 #include <Data/ScalarSeries.h>
4 #include <Data/ScalarSeries.h>
5 #include <Data/VectorSeries.h>
5
6
6 #include <QDateTime>
7 #include <QDateTime>
7 #include <QFile>
8 #include <QFile>
@@ -45,6 +46,25 bool isCommentLine(const QString &line)
45 return line.startsWith("#");
46 return line.startsWith("#");
46 }
47 }
47
48
49 /// @return the number of lines to be read depending on the type of value passed in parameter
50 int nbValues(AmdaResultParser::ValueType valueType) noexcept
51 {
52 switch (valueType) {
53 case AmdaResultParser::ValueType::SCALAR:
54 return 1;
55 case AmdaResultParser::ValueType::VECTOR:
56 return 3;
57 case AmdaResultParser::ValueType::UNKNOWN:
58 // Invalid case
59 break;
60 }
61
62 // Invalid cases
63 qCCritical(LOG_AmdaResultParser())
64 << QObject::tr("Can't get the number of values to read: unsupported type");
65 return 0;
66 }
67
48 /**
68 /**
49 * Reads stream to retrieve x-axis unit
69 * Reads stream to retrieve x-axis unit
50 * @param stream the stream to read
70 * @param stream the stream to read
@@ -74,10 +94,13 Unit readXAxisUnit(QTextStream &stream)
74 * @param stream the stream to read
94 * @param stream the stream to read
75 * @return the pair of vectors x-axis data/values data that has been read in the stream
95 * @return the pair of vectors x-axis data/values data that has been read in the stream
76 */
96 */
77 QPair<QVector<double>, QVector<double> > readResults(QTextStream &stream)
97 QPair<QVector<double>, QVector<QVector<double> > >
98 readResults(QTextStream &stream, AmdaResultParser::ValueType valueType)
78 {
99 {
100 auto expectedNbValues = nbValues(valueType);
101
79 auto xData = QVector<double>{};
102 auto xData = QVector<double>{};
80 auto valuesData = QVector<double>{};
103 auto valuesData = QVector<QVector<double> >(expectedNbValues);
81
104
82 QString line{};
105 QString line{};
83
106
@@ -85,27 +108,31 QPair<QVector<double>, QVector<double> > readResults(QTextStream &stream)
85 // Ignore comment lines
108 // Ignore comment lines
86 if (!isCommentLine(line)) {
109 if (!isCommentLine(line)) {
87 auto lineData = line.split(RESULT_LINE_SEPARATOR, QString::SkipEmptyParts);
110 auto lineData = line.split(RESULT_LINE_SEPARATOR, QString::SkipEmptyParts);
88 if (lineData.size() == 2) {
111 if (lineData.size() == expectedNbValues + 1) {
89 // X : the data is converted from date to double (in secs)
112 // X : the data is converted from date to double (in secs)
90 auto x = doubleDate(lineData.at(0));
113 auto x = doubleDate(lineData.at(0));
91
114
92 // Value
93 bool valueOk;
94 auto value = lineData.at(1).toDouble(&valueOk);
95
96 // Adds result only if x is valid. Then, if value is invalid, it is set to NaN
115 // Adds result only if x is valid. Then, if value is invalid, it is set to NaN
97 if (!std::isnan(x)) {
116 if (!std::isnan(x)) {
98 xData.push_back(x);
117 xData.push_back(x);
99
118
100 if (!valueOk) {
119 // Values
101 qCWarning(LOG_AmdaResultParser())
120 for (auto valueIndex = 0; valueIndex < expectedNbValues; ++valueIndex) {
102 << QObject::tr(
121 auto column = valueIndex + 1;
103 "Value from line %1 is invalid and will be converted to NaN")
122
104 .arg(line);
123 bool valueOk;
105 value = std::numeric_limits<double>::quiet_NaN();
124 auto value = lineData.at(column).toDouble(&valueOk);
125
126 if (!valueOk) {
127 qCWarning(LOG_AmdaResultParser())
128 << QObject::tr(
129 "Value from (line %1, column %2) is invalid and will be "
130 "converted to NaN")
131 .arg(line, column);
132 value = std::numeric_limits<double>::quiet_NaN();
133 }
134 valuesData[valueIndex].append(value);
106 }
135 }
107
108 valuesData.push_back(value);
109 }
136 }
110 else {
137 else {
111 qCWarning(LOG_AmdaResultParser())
138 qCWarning(LOG_AmdaResultParser())
@@ -125,8 +152,15 QPair<QVector<double>, QVector<double> > readResults(QTextStream &stream)
125
152
126 } // namespace
153 } // namespace
127
154
128 std::shared_ptr<IDataSeries> AmdaResultParser::readTxt(const QString &filePath) noexcept
155 std::shared_ptr<IDataSeries> AmdaResultParser::readTxt(const QString &filePath,
156 ValueType valueType) noexcept
129 {
157 {
158 if (valueType == ValueType::UNKNOWN) {
159 qCCritical(LOG_AmdaResultParser())
160 << QObject::tr("Can't retrieve AMDA data: the type of values to be read is unknown");
161 return nullptr;
162 }
163
130 QFile file{filePath};
164 QFile file{filePath};
131
165
132 if (!file.open(QFile::ReadOnly | QIODevice::Text)) {
166 if (!file.open(QFile::ReadOnly | QIODevice::Text)) {
@@ -153,8 +187,30 std::shared_ptr<IDataSeries> AmdaResultParser::readTxt(const QString &filePath)
153
187
154 // Reads results
188 // Reads results
155 stream.seek(0); // returns to the beginning of the file
189 stream.seek(0); // returns to the beginning of the file
156 auto results = readResults(stream);
190 auto results = readResults(stream, valueType);
191
192 // Creates data series
193 switch (valueType) {
194 case ValueType::SCALAR:
195 Q_ASSERT(results.second.size() == 1);
196 return std::make_shared<ScalarSeries>(
197 std::move(results.first), std::move(results.second.takeFirst()), xAxisUnit, Unit{});
198 case ValueType::VECTOR: {
199 Q_ASSERT(results.second.size() == 3);
200 auto xValues = results.second.takeFirst();
201 auto yValues = results.second.takeFirst();
202 auto zValues = results.second.takeFirst();
203 return std::make_shared<VectorSeries>(std::move(results.first), std::move(xValues),
204 std::move(yValues), std::move(zValues), xAxisUnit,
205 Unit{});
206 }
207 case ValueType::UNKNOWN:
208 // Invalid case
209 break;
210 }
157
211
158 return std::make_shared<ScalarSeries>(std::move(results.first), std::move(results.second),
212 // Invalid cases
159 xAxisUnit, Unit{});
213 qCCritical(LOG_AmdaResultParser())
214 << QObject::tr("Can't create data series: unsupported value type");
215 return nullptr;
160 }
216 }
@@ -1,6 +1,7
1 #include "AmdaResultParser.h"
1 #include "AmdaResultParser.h"
2
2
3 #include <Data/ScalarSeries.h>
3 #include <Data/ScalarSeries.h>
4 #include <Data/VectorSeries.h>
4
5
5 #include <QObject>
6 #include <QObject>
6 #include <QtTest>
7 #include <QtTest>
@@ -11,6 +12,11 namespace {
11 const auto TESTS_RESOURCES_PATH
12 const auto TESTS_RESOURCES_PATH
12 = QFileInfo{QString{AMDA_TESTS_RESOURCES_DIR}, "TestAmdaResultParser"}.absoluteFilePath();
13 = QFileInfo{QString{AMDA_TESTS_RESOURCES_DIR}, "TestAmdaResultParser"}.absoluteFilePath();
13
14
15 QDateTime dateTime(int year, int month, int day, int hours, int minutes, int seconds)
16 {
17 return QDateTime{{year, month, day}, {hours, minutes, seconds}, Qt::UTC};
18 }
19
14 /// Compares two vectors that can potentially contain NaN values
20 /// Compares two vectors that can potentially contain NaN values
15 bool compareVectors(const QVector<double> &v1, const QVector<double> &v2)
21 bool compareVectors(const QVector<double> &v1, const QVector<double> &v2)
16 {
22 {
@@ -31,17 +37,50 bool compareVectors(const QVector<double> &v1, const QVector<double> &v2)
31 return result;
37 return result;
32 }
38 }
33
39
40 bool compareVectors(const QVector<QVector<double> > &v1, const QVector<QVector<double> > &v2)
41 {
42 if (v1.size() != v2.size()) {
43 return false;
44 }
45
46 auto result = true;
47 for (auto i = 0; i < v1.size() && result; ++i) {
48 result &= compareVectors(v1.at(i), v2.at(i));
49 }
50
51 return result;
52 }
53
54 QVector<QVector<double> > valuesData(const ArrayData<1> &arrayData)
55 {
56 return QVector<QVector<double> >{arrayData.data()};
57 }
58
59 QVector<QVector<double> > valuesData(const ArrayData<2> &arrayData)
60 {
61 return arrayData.data();
62 }
63
64
34 QString inputFilePath(const QString &inputFileName)
65 QString inputFilePath(const QString &inputFileName)
35 {
66 {
36 return QFileInfo{TESTS_RESOURCES_PATH, inputFileName}.absoluteFilePath();
67 return QFileInfo{TESTS_RESOURCES_PATH, inputFileName}.absoluteFilePath();
37 }
68 }
38
69
70 template <typename T>
39 struct ExpectedResults {
71 struct ExpectedResults {
40 explicit ExpectedResults() = default;
72 explicit ExpectedResults() = default;
41
73
42 /// Ctor with QVector<QDateTime> as x-axis data. Datetimes are converted to doubles
43 explicit ExpectedResults(Unit xAxisUnit, Unit valuesUnit, const QVector<QDateTime> &xAxisData,
74 explicit ExpectedResults(Unit xAxisUnit, Unit valuesUnit, const QVector<QDateTime> &xAxisData,
44 QVector<double> valuesData)
75 QVector<double> valuesData)
76 : ExpectedResults(xAxisUnit, valuesUnit, xAxisData,
77 QVector<QVector<double> >{std::move(valuesData)})
78 {
79 }
80
81 /// Ctor with QVector<QDateTime> as x-axis data. Datetimes are converted to doubles
82 explicit ExpectedResults(Unit xAxisUnit, Unit valuesUnit, const QVector<QDateTime> &xAxisData,
83 QVector<QVector<double> > valuesData)
45 : m_ParsingOK{true},
84 : m_ParsingOK{true},
46 m_XAxisUnit{xAxisUnit},
85 m_XAxisUnit{xAxisUnit},
47 m_ValuesUnit{valuesUnit},
86 m_ValuesUnit{valuesUnit},
@@ -60,17 +99,17 struct ExpectedResults {
60 void validate(std::shared_ptr<IDataSeries> results)
99 void validate(std::shared_ptr<IDataSeries> results)
61 {
100 {
62 if (m_ParsingOK) {
101 if (m_ParsingOK) {
63 auto scalarSeries = dynamic_cast<ScalarSeries *>(results.get());
102 auto dataSeries = dynamic_cast<T *>(results.get());
64 QVERIFY(scalarSeries != nullptr);
103 QVERIFY(dataSeries != nullptr);
65
104
66 // Checks units
105 // Checks units
67 QVERIFY(scalarSeries->xAxisUnit() == m_XAxisUnit);
106 QVERIFY(dataSeries->xAxisUnit() == m_XAxisUnit);
68 QVERIFY(scalarSeries->valuesUnit() == m_ValuesUnit);
107 QVERIFY(dataSeries->valuesUnit() == m_ValuesUnit);
69
108
70 // Checks values : as the vectors can potentially contain NaN values, we must use a
109 // Checks values : as the vectors can potentially contain NaN values, we must use a
71 // custom vector comparison method
110 // custom vector comparison method
72 QVERIFY(compareVectors(scalarSeries->xAxisData()->data(), m_XAxisData));
111 QVERIFY(compareVectors(dataSeries->xAxisData()->data(), m_XAxisData));
73 QVERIFY(compareVectors(scalarSeries->valuesData()->data(), m_ValuesData));
112 QVERIFY(compareVectors(valuesData(*dataSeries->valuesData()), m_ValuesData));
74 }
113 }
75 else {
114 else {
76 QVERIFY(results == nullptr);
115 QVERIFY(results == nullptr);
@@ -86,47 +125,74 struct ExpectedResults {
86 // Expected x-axis data
125 // Expected x-axis data
87 QVector<double> m_XAxisData{};
126 QVector<double> m_XAxisData{};
88 // Expected values data
127 // Expected values data
89 QVector<double> m_ValuesData{};
128 QVector<QVector<double> > m_ValuesData{};
90 };
129 };
91
130
92 } // namespace
131 } // namespace
93
132
94 Q_DECLARE_METATYPE(ExpectedResults)
133 Q_DECLARE_METATYPE(ExpectedResults<ScalarSeries>)
134 Q_DECLARE_METATYPE(ExpectedResults<VectorSeries>)
95
135
96 class TestAmdaResultParser : public QObject {
136 class TestAmdaResultParser : public QObject {
97 Q_OBJECT
137 Q_OBJECT
138 private:
139 template <typename T>
140 void testReadDataStructure()
141 {
142 // ////////////// //
143 // Test structure //
144 // ////////////// //
145
146 // Name of TXT file to read
147 QTest::addColumn<QString>("inputFileName");
148 // Expected results
149 QTest::addColumn<ExpectedResults<T> >("expectedResults");
150 }
151
152 template <typename T>
153 void testRead(AmdaResultParser::ValueType valueType)
154 {
155 QFETCH(QString, inputFileName);
156 QFETCH(ExpectedResults<T>, expectedResults);
157
158 // Parses file
159 auto filePath = inputFilePath(inputFileName);
160 auto results = AmdaResultParser::readTxt(filePath, valueType);
161
162 // ///////////////// //
163 // Validates results //
164 // ///////////////// //
165 expectedResults.validate(results);
166 }
167
98 private slots:
168 private slots:
99 /// Input test data
169 /// Input test data
100 /// @sa testTxtJson()
170 /// @sa testReadScalarTxt()
101 void testReadTxt_data();
171 void testReadScalarTxt_data();
102
172
103 /// Tests parsing of a TXT file
173 /// Tests parsing scalar series of a TXT file
104 void testReadTxt();
174 void testReadScalarTxt();
175
176 /// Input test data
177 /// @sa testReadVectorTxt()
178 void testReadVectorTxt_data();
179
180 /// Tests parsing vector series of a TXT file
181 void testReadVectorTxt();
105 };
182 };
106
183
107 void TestAmdaResultParser::testReadTxt_data()
184 void TestAmdaResultParser::testReadScalarTxt_data()
108 {
185 {
109 // ////////////// //
186 testReadDataStructure<ScalarSeries>();
110 // Test structure //
111 // ////////////// //
112
113 // Name of TXT file to read
114 QTest::addColumn<QString>("inputFileName");
115 // Expected results
116 QTest::addColumn<ExpectedResults>("expectedResults");
117
187
118 // ////////// //
188 // ////////// //
119 // Test cases //
189 // Test cases //
120 // ////////// //
190 // ////////// //
121
191
122 auto dateTime = [](int year, int month, int day, int hours, int minutes, int seconds) {
123 return QDateTime{{year, month, day}, {hours, minutes, seconds}, Qt::UTC};
124 };
125
126 // Valid files
192 // Valid files
127 QTest::newRow("Valid file")
193 QTest::newRow("Valid file")
128 << QStringLiteral("ValidScalar1.txt")
194 << QStringLiteral("ValidScalar1.txt")
129 << ExpectedResults{
195 << ExpectedResults<ScalarSeries>{
130 Unit{QStringLiteral("nT"), true}, Unit{},
196 Unit{QStringLiteral("nT"), true}, Unit{},
131 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
197 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
132 dateTime(2013, 9, 23, 9, 2, 30), dateTime(2013, 9, 23, 9, 3, 30),
198 dateTime(2013, 9, 23, 9, 2, 30), dateTime(2013, 9, 23, 9, 3, 30),
@@ -138,7 +204,7 void TestAmdaResultParser::testReadTxt_data()
138
204
139 QTest::newRow("Valid file (value of first line is invalid but it is converted to NaN")
205 QTest::newRow("Valid file (value of first line is invalid but it is converted to NaN")
140 << QStringLiteral("WrongValue.txt")
206 << QStringLiteral("WrongValue.txt")
141 << ExpectedResults{
207 << ExpectedResults<ScalarSeries>{
142 Unit{QStringLiteral("nT"), true}, Unit{},
208 Unit{QStringLiteral("nT"), true}, Unit{},
143 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
209 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
144 dateTime(2013, 9, 23, 9, 2, 30)},
210 dateTime(2013, 9, 23, 9, 2, 30)},
@@ -146,7 +212,7 void TestAmdaResultParser::testReadTxt_data()
146
212
147 QTest::newRow("Valid file that contains NaN values")
213 QTest::newRow("Valid file that contains NaN values")
148 << QStringLiteral("NaNValue.txt")
214 << QStringLiteral("NaNValue.txt")
149 << ExpectedResults{
215 << ExpectedResults<ScalarSeries>{
150 Unit{QStringLiteral("nT"), true}, Unit{},
216 Unit{QStringLiteral("nT"), true}, Unit{},
151 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
217 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
152 dateTime(2013, 9, 23, 9, 2, 30)},
218 dateTime(2013, 9, 23, 9, 2, 30)},
@@ -154,58 +220,90 void TestAmdaResultParser::testReadTxt_data()
154
220
155 // Valid files but with some invalid lines (wrong unit, wrong values, etc.)
221 // Valid files but with some invalid lines (wrong unit, wrong values, etc.)
156 QTest::newRow("No unit file") << QStringLiteral("NoUnit.txt")
222 QTest::newRow("No unit file") << QStringLiteral("NoUnit.txt")
157 << ExpectedResults{Unit{QStringLiteral(""), true}, Unit{},
223 << ExpectedResults<ScalarSeries>{Unit{QStringLiteral(""), true},
158 QVector<QDateTime>{}, QVector<double>{}};
224 Unit{}, QVector<QDateTime>{},
225 QVector<double>{}};
159 QTest::newRow("Wrong unit file")
226 QTest::newRow("Wrong unit file")
160 << QStringLiteral("WrongUnit.txt")
227 << QStringLiteral("WrongUnit.txt")
161 << ExpectedResults{Unit{QStringLiteral(""), true}, Unit{},
228 << ExpectedResults<ScalarSeries>{Unit{QStringLiteral(""), true}, Unit{},
162 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30),
229 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30),
163 dateTime(2013, 9, 23, 9, 1, 30),
230 dateTime(2013, 9, 23, 9, 1, 30),
164 dateTime(2013, 9, 23, 9, 2, 30)},
231 dateTime(2013, 9, 23, 9, 2, 30)},
165 QVector<double>{-2.83950, -2.71850, -2.52150}};
232 QVector<double>{-2.83950, -2.71850, -2.52150}};
166
233
167 QTest::newRow("Wrong results file (date of first line is invalid")
234 QTest::newRow("Wrong results file (date of first line is invalid")
168 << QStringLiteral("WrongDate.txt")
235 << QStringLiteral("WrongDate.txt")
169 << ExpectedResults{
236 << ExpectedResults<ScalarSeries>{
170 Unit{QStringLiteral("nT"), true}, Unit{},
237 Unit{QStringLiteral("nT"), true}, Unit{},
171 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
238 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
172 QVector<double>{-2.71850, -2.52150}};
239 QVector<double>{-2.71850, -2.52150}};
173
240
174 QTest::newRow("Wrong results file (too many values for first line")
241 QTest::newRow("Wrong results file (too many values for first line")
175 << QStringLiteral("TooManyValues.txt")
242 << QStringLiteral("TooManyValues.txt")
176 << ExpectedResults{
243 << ExpectedResults<ScalarSeries>{
177 Unit{QStringLiteral("nT"), true}, Unit{},
244 Unit{QStringLiteral("nT"), true}, Unit{},
178 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
245 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
179 QVector<double>{-2.71850, -2.52150}};
246 QVector<double>{-2.71850, -2.52150}};
180
247
181 QTest::newRow("Wrong results file (x of first line is NaN")
248 QTest::newRow("Wrong results file (x of first line is NaN")
182 << QStringLiteral("NaNX.txt")
249 << QStringLiteral("NaNX.txt")
183 << ExpectedResults{
250 << ExpectedResults<ScalarSeries>{
184 Unit{QStringLiteral("nT"), true}, Unit{},
251 Unit{QStringLiteral("nT"), true}, Unit{},
185 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
252 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
186 QVector<double>{-2.71850, -2.52150}};
253 QVector<double>{-2.71850, -2.52150}};
187
254
255 QTest::newRow("Invalid file type (vector)")
256 << QStringLiteral("ValidVector1.txt")
257 << ExpectedResults<ScalarSeries>{Unit{QStringLiteral("nT"), true}, Unit{},
258 QVector<QDateTime>{}, QVector<double>{}};
259
188 // Invalid files
260 // Invalid files
189 QTest::newRow("Invalid file (unexisting file)") << QStringLiteral("UnexistingFile.txt")
261 QTest::newRow("Invalid file (unexisting file)")
190 << ExpectedResults{};
262 << QStringLiteral("UnexistingFile.txt") << ExpectedResults<ScalarSeries>{};
263
264 QTest::newRow("Invalid file (file not found on server)")
265 << QStringLiteral("FileNotFound.txt") << ExpectedResults<ScalarSeries>{};
266 }
191
267
192 QTest::newRow("Invalid file (file not found on server)") << QStringLiteral("FileNotFound.txt")
268 void TestAmdaResultParser::testReadScalarTxt()
193 << ExpectedResults{};
269 {
270 testRead<ScalarSeries>(AmdaResultParser::ValueType::SCALAR);
194 }
271 }
195
272
196 void TestAmdaResultParser::testReadTxt()
273 void TestAmdaResultParser::testReadVectorTxt_data()
197 {
274 {
198 QFETCH(QString, inputFileName);
275 testReadDataStructure<VectorSeries>();
199 QFETCH(ExpectedResults, expectedResults);
276
277 // ////////// //
278 // Test cases //
279 // ////////// //
280
281 // Valid files
282 QTest::newRow("Valid file")
283 << QStringLiteral("ValidVector1.txt")
284 << ExpectedResults<VectorSeries>{
285 Unit{QStringLiteral("nT"), true}, Unit{},
286 QVector<QDateTime>{dateTime(2013, 7, 2, 9, 13, 50), dateTime(2013, 7, 2, 9, 14, 6),
287 dateTime(2013, 7, 2, 9, 14, 22), dateTime(2013, 7, 2, 9, 14, 38),
288 dateTime(2013, 7, 2, 9, 14, 54), dateTime(2013, 7, 2, 9, 15, 10),
289 dateTime(2013, 7, 2, 9, 15, 26), dateTime(2013, 7, 2, 9, 15, 42),
290 dateTime(2013, 7, 2, 9, 15, 58), dateTime(2013, 7, 2, 9, 16, 14)},
291 QVector<QVector<double> >{
292 {-0.332, -1.011, -1.457, -1.293, -1.217, -1.443, -1.278, -1.202, -1.22, -1.259},
293 {3.206, 2.999, 2.785, 2.736, 2.612, 2.564, 2.892, 2.862, 2.859, 2.764},
294 {0.058, 0.496, 1.018, 1.485, 1.662, 1.505, 1.168, 1.244, 1.15, 1.358}}};
200
295
201 // Parses file
296 // Valid files but with some invalid lines (wrong unit, wrong values, etc.)
202 auto filePath = inputFilePath(inputFileName);
297 QTest::newRow("Invalid file type (scalar)")
203 auto results = AmdaResultParser::readTxt(filePath);
298 << QStringLiteral("ValidScalar1.txt")
299 << ExpectedResults<VectorSeries>{Unit{QStringLiteral("nT"), true}, Unit{},
300 QVector<QDateTime>{},
301 QVector<QVector<double> >{{}, {}, {}}};
302 }
204
303
205 // ///////////////// //
304 void TestAmdaResultParser::testReadVectorTxt()
206 // Validates results //
305 {
207 // ///////////////// //
306 testRead<VectorSeries>(AmdaResultParser::ValueType::VECTOR);
208 expectedResults.validate(results);
209 }
307 }
210
308
211 QTEST_MAIN(TestAmdaResultParser)
309 QTEST_MAIN(TestAmdaResultParser)
General Comments 0
You need to be logged in to leave comments. Login now