##// END OF EJS Templates
Spectrograms implementation (1)...
Alexandre Leroux -
r990:2da9b0f28a10
parent child
Show More
@@ -1,21 +1,21
1 1 #ifndef SCIQLOP_AMDARESULTPARSER_H
2 2 #define SCIQLOP_AMDARESULTPARSER_H
3 3
4 4 #include "AmdaGlobal.h"
5 5
6 6 #include <QLoggingCategory>
7 7
8 8 #include <memory>
9 9
10 10 class IDataSeries;
11 11
12 12 Q_DECLARE_LOGGING_CATEGORY(LOG_AmdaResultParser)
13 13
14 14 struct SCIQLOP_AMDA_EXPORT AmdaResultParser {
15 enum class ValueType { SCALAR, VECTOR, UNKNOWN };
15 enum class ValueType { SCALAR, SPECTROGRAM, VECTOR, UNKNOWN };
16 16
17 17 static std::shared_ptr<IDataSeries> readTxt(const QString &filePath,
18 18 ValueType valueType) noexcept;
19 19 };
20 20
21 21 #endif // SCIQLOP_AMDARESULTPARSER_H
@@ -1,86 +1,97
1 1 #ifndef SCIQLOP_AMDARESULTPARSERHELPER_H
2 2 #define SCIQLOP_AMDARESULTPARSERHELPER_H
3 3
4 4 #include "AmdaResultParserDefs.h"
5 5
6 6 #include <QtCore/QLoggingCategory>
7 7 #include <QtCore/QString>
8 8
9 9 #include <memory>
10 10
11 11 class IDataSeries;
12 12
13 13 Q_DECLARE_LOGGING_CATEGORY(LOG_AmdaResultParserHelper)
14 14
15 15 /**
16 16 * Helper used to interpret the data of an AMDA result file and generate the corresponding data
17 17 * series.
18 18 *
19 19 * It proposes methods allowing to read line by line an AMDA file and to extract the properties
20 20 * (from the header) and the values corresponding to the data series
21 21 *
22 22 * @sa DataSeries
23 23 */
24 24 struct IAmdaResultParserHelper {
25 25 virtual ~IAmdaResultParserHelper() noexcept = default;
26 26
27 27 /// Verifies that the extracted properties are well formed and possibly applies other treatments
28 28 /// on them
29 29 /// @return true if the properties are well formed, false otherwise
30 30 virtual bool checkProperties() = 0;
31 31
32 32 /// Creates the data series from the properties and values extracted from the AMDA file.
33 33 /// @warning as the data are moved in the data series, the helper shouldn't be used after
34 34 /// calling this method
35 35 /// @return the data series created
36 36 virtual std::shared_ptr<IDataSeries> createSeries() = 0;
37 37
38 38 /// Reads a line from the AMDA file to extract a property that will be used to generate the data
39 39 /// series
40 40 /// @param line tahe line to interpret
41 41 virtual void readPropertyLine(const QString &line) = 0;
42 42
43 43 /// Reads a line from the AMDA file to extract a value that will be set in the data series
44 44 /// @param line the line to interpret
45 45 virtual void readResultLine(const QString &line) = 0;
46 46 };
47 47
48 48 /**
49 49 * Implementation of @sa IAmdaResultParserHelper for scalars
50 50 */
51 51 class ScalarParserHelper : public IAmdaResultParserHelper {
52 52 public:
53 53 bool checkProperties() override;
54 54 std::shared_ptr<IDataSeries> createSeries() override;
55 55 void readPropertyLine(const QString &line) override;
56 56 void readResultLine(const QString &line) override;
57 57
58 58 private:
59 59 /// @return the reading order of the "value" columns for a result line of the AMDA file
60 60 std::vector<int> valuesIndexes() const;
61 61
62 62 Properties m_Properties{};
63 63 std::vector<double> m_XAxisData{};
64 64 std::vector<double> m_ValuesData{};
65 65 };
66 66
67 67 /**
68 * Implementation of @sa IAmdaResultParserHelper for spectrograms
69 */
70 class SpectrogramParserHelper : public IAmdaResultParserHelper {
71 public:
72 bool checkProperties() override;
73 std::shared_ptr<IDataSeries> createSeries() override;
74 void readPropertyLine(const QString &line) override;
75 void readResultLine(const QString &line) override;
76 };
77
78 /**
68 79 * Implementation of @sa IAmdaResultParserHelper for vectors
69 80 */
70 81 class VectorParserHelper : public IAmdaResultParserHelper {
71 82 public:
72 83 bool checkProperties() override;
73 84 std::shared_ptr<IDataSeries> createSeries() override;
74 85 void readPropertyLine(const QString &line) override;
75 86 void readResultLine(const QString &line) override;
76 87
77 88 private:
78 89 /// @return the reading order of the "value" columns for a result line of the AMDA file
79 90 std::vector<int> valuesIndexes() const;
80 91
81 92 Properties m_Properties{};
82 93 std::vector<double> m_XAxisData{};
83 94 std::vector<double> m_ValuesData{};
84 95 };
85 96
86 97 #endif // SCIQLOP_AMDARESULTPARSERHELPER_H
@@ -1,304 +1,307
1 1 #include "AmdaProvider.h"
2 2 #include "AmdaDefs.h"
3 3 #include "AmdaResultParser.h"
4 4
5 5 #include <Common/DateUtils.h>
6 6 #include <Data/DataProviderParameters.h>
7 7 #include <Network/NetworkController.h>
8 8 #include <SqpApplication.h>
9 9 #include <Variable/Variable.h>
10 10
11 11 #include <QNetworkAccessManager>
12 12 #include <QNetworkReply>
13 13 #include <QTemporaryFile>
14 14 #include <QThread>
15 15
16 16 Q_LOGGING_CATEGORY(LOG_AmdaProvider, "AmdaProvider")
17 17
18 18 namespace {
19 19
20 20 /// URL of the default AMDA server
21 21 const auto AMDA_SERVER_URL = QStringLiteral("amda.irap.omp.eu");
22 22
23 23 /// URL of the AMDA test server
24 24 const auto AMDA_TEST_SERVER_URL = QStringLiteral("amdatest.irap.omp.eu");
25 25
26 26 /// URL format for a request on AMDA server. The parameters are as follows:
27 27 /// - %1: server URL
28 28 /// - %2: start date
29 29 /// - %3: end date
30 30 /// - %4: parameter id
31 31 /// AMDA V2: http://amdatest.irap.omp.eu/php/rest/
32 32 const auto AMDA_URL_FORMAT = QStringLiteral(
33 33 "http://%1/php/rest/"
34 34 "getParameter.php?startTime=%2&stopTime=%3&parameterID=%4&outputFormat=ASCII&"
35 35 "timeFormat=ISO8601&gzip=0");
36 36
37 37 /// Dates format passed in the URL (e.g 2013-09-23T09:00)
38 38 const auto AMDA_TIME_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss");
39 39
40 40 /// Formats a time to a date that can be passed in URL
41 41 QString dateFormat(double sqpRange) noexcept
42 42 {
43 43 auto dateTime = DateUtils::dateTime(sqpRange);
44 44 return dateTime.toString(AMDA_TIME_FORMAT);
45 45 }
46 46
47 47 /// Returns the URL of the AMDA server queried for requests, depending on the type of server passed
48 48 /// as a parameter
49 49 QString serverURL(const QString &server)
50 50 {
51 51 if (server == QString{"amdatest"}) {
52 52 return AMDA_TEST_SERVER_URL;
53 53 }
54 54 else {
55 55 return AMDA_SERVER_URL;
56 56 }
57 57 }
58 58
59 59 AmdaResultParser::ValueType valueType(const QString &valueType)
60 60 {
61 61 if (valueType == QStringLiteral("scalar")) {
62 62 return AmdaResultParser::ValueType::SCALAR;
63 63 }
64 else if (valueType == QStringLiteral("spectrogram")) {
65 return AmdaResultParser::ValueType::SPECTROGRAM;
66 }
64 67 else if (valueType == QStringLiteral("vector")) {
65 68 return AmdaResultParser::ValueType::VECTOR;
66 69 }
67 70 else {
68 71 return AmdaResultParser::ValueType::UNKNOWN;
69 72 }
70 73 }
71 74
72 75 } // namespace
73 76
74 77 AmdaProvider::AmdaProvider()
75 78 {
76 79 qCDebug(LOG_AmdaProvider()) << tr("AmdaProvider::AmdaProvider") << QThread::currentThread();
77 80 if (auto app = sqpApp) {
78 81 auto &networkController = app->networkController();
79 82 connect(this, SIGNAL(requestConstructed(std::shared_ptr<QNetworkRequest>, QUuid,
80 83 std::function<void(QNetworkReply *, QUuid)>)),
81 84 &networkController,
82 85 SLOT(onProcessRequested(std::shared_ptr<QNetworkRequest>, QUuid,
83 86 std::function<void(QNetworkReply *, QUuid)>)));
84 87
85 88
86 89 connect(&sqpApp->networkController(),
87 90 SIGNAL(replyDownloadProgress(QUuid, std::shared_ptr<QNetworkRequest>, double)),
88 91 this,
89 92 SLOT(onReplyDownloadProgress(QUuid, std::shared_ptr<QNetworkRequest>, double)));
90 93 }
91 94 }
92 95
93 96 std::shared_ptr<IDataProvider> AmdaProvider::clone() const
94 97 {
95 98 // No copy is made in the clone
96 99 return std::make_shared<AmdaProvider>();
97 100 }
98 101
99 102 void AmdaProvider::requestDataLoading(QUuid acqIdentifier, const DataProviderParameters &parameters)
100 103 {
101 104 // NOTE: Try to use multithread if possible
102 105 const auto times = parameters.m_Times;
103 106 const auto data = parameters.m_Data;
104 107 for (const auto &dateTime : qAsConst(times)) {
105 108 qCDebug(LOG_AmdaProvider()) << tr("TORM AmdaProvider::requestDataLoading ") << acqIdentifier
106 109 << dateTime;
107 110 this->retrieveData(acqIdentifier, dateTime, data);
108 111
109 112
110 113 // TORM when AMDA will support quick asynchrone request
111 114 QThread::msleep(1000);
112 115 }
113 116 }
114 117
115 118 void AmdaProvider::requestDataAborting(QUuid acqIdentifier)
116 119 {
117 120 if (auto app = sqpApp) {
118 121 auto &networkController = app->networkController();
119 122 networkController.onReplyCanceled(acqIdentifier);
120 123 }
121 124 }
122 125
123 126 void AmdaProvider::onReplyDownloadProgress(QUuid acqIdentifier,
124 127 std::shared_ptr<QNetworkRequest> networkRequest,
125 128 double progress)
126 129 {
127 130 qCDebug(LOG_AmdaProvider()) << tr("onReplyDownloadProgress") << acqIdentifier
128 131 << networkRequest.get() << progress;
129 132 auto acqIdToRequestProgressMapIt = m_AcqIdToRequestProgressMap.find(acqIdentifier);
130 133 if (acqIdToRequestProgressMapIt != m_AcqIdToRequestProgressMap.end()) {
131 134
132 135 // Update the progression for the current request
133 136 auto requestPtr = networkRequest;
134 137 auto findRequest = [requestPtr](const auto &entry) { return requestPtr == entry.first; };
135 138
136 139 auto &requestProgressMap = acqIdToRequestProgressMapIt->second;
137 140 auto requestProgressMapEnd = requestProgressMap.end();
138 141 auto requestProgressMapIt
139 142 = std::find_if(requestProgressMap.begin(), requestProgressMapEnd, findRequest);
140 143
141 144 if (requestProgressMapIt != requestProgressMapEnd) {
142 145 requestProgressMapIt->second = progress;
143 146 }
144 147 else {
145 148 // This case can happened when a progression is send after the request has been
146 149 // finished.
147 150 // Generaly the case when aborting a request
148 151 qCDebug(LOG_AmdaProvider()) << tr("Can't retrieve Request in progress") << acqIdentifier
149 152 << networkRequest.get() << progress;
150 153 }
151 154
152 155 // Compute the current final progress and notify it
153 156 double finalProgress = 0.0;
154 157
155 158 auto fraq = requestProgressMap.size();
156 159
157 160 for (auto requestProgress : requestProgressMap) {
158 161 finalProgress += requestProgress.second;
159 162 qCDebug(LOG_AmdaProvider()) << tr("Current final progress without fraq:")
160 163 << finalProgress << requestProgress.second;
161 164 }
162 165
163 166 if (fraq > 0) {
164 167 finalProgress = finalProgress / fraq;
165 168 }
166 169
167 170 qCDebug(LOG_AmdaProvider()) << tr("Current final progress: ") << fraq << finalProgress;
168 171 emit dataProvidedProgress(acqIdentifier, finalProgress);
169 172 }
170 173 else {
171 174 // This case can happened when a progression is send after the request has been finished.
172 175 // Generaly the case when aborting a request
173 176 emit dataProvidedProgress(acqIdentifier, 100.0);
174 177 }
175 178 }
176 179
177 180 void AmdaProvider::retrieveData(QUuid token, const SqpRange &dateTime, const QVariantHash &data)
178 181 {
179 182 // Retrieves product ID from data: if the value is invalid, no request is made
180 183 auto productId = data.value(AMDA_XML_ID_KEY).toString();
181 184 if (productId.isNull()) {
182 185 qCCritical(LOG_AmdaProvider()) << tr("Can't retrieve data: unknown product id");
183 186 return;
184 187 }
185 188
186 189 // Retrieves the data type that determines whether the expected format for the result file is
187 190 // scalar, vector...
188 191 auto productValueType = valueType(data.value(AMDA_DATA_TYPE_KEY).toString());
189 192
190 193 // Gets the server being queried to retrieve the product. It's then used to set the server URL
191 194 auto productServer = data.value(AMDA_SERVER_KEY).toString();
192 195
193 196 // /////////// //
194 197 // Creates URL //
195 198 // /////////// //
196 199
197 200 auto startDate = dateFormat(dateTime.m_TStart);
198 201 auto endDate = dateFormat(dateTime.m_TEnd);
199 202
200 203 auto url = QUrl{
201 204 QString{AMDA_URL_FORMAT}.arg(serverURL(productServer), startDate, endDate, productId)};
202 205 qCInfo(LOG_AmdaProvider()) << tr("TORM AmdaProvider::retrieveData url:") << url;
203 206 auto tempFile = std::make_shared<QTemporaryFile>();
204 207
205 208 // LAMBDA
206 209 auto httpDownloadFinished = [this, dateTime, tempFile,
207 210 productValueType](QNetworkReply *reply, QUuid dataId) noexcept {
208 211
209 212 // Don't do anything if the reply was abort
210 213 if (reply->error() == QNetworkReply::NoError) {
211 214
212 215 if (tempFile) {
213 216 auto replyReadAll = reply->readAll();
214 217 if (!replyReadAll.isEmpty()) {
215 218 tempFile->write(replyReadAll);
216 219 }
217 220 tempFile->close();
218 221
219 222 // Parse results file
220 223 if (auto dataSeries
221 224 = AmdaResultParser::readTxt(tempFile->fileName(), productValueType)) {
222 225 emit dataProvided(dataId, dataSeries, dateTime);
223 226 }
224 227 else {
225 228 /// @todo ALX : debug
226 229 emit dataProvidedFailed(dataId);
227 230 }
228 231 }
229 232 m_AcqIdToRequestProgressMap.erase(dataId);
230 233 }
231 234 else {
232 235 qCCritical(LOG_AmdaProvider()) << tr("httpDownloadFinished ERROR");
233 236 emit dataProvidedFailed(dataId);
234 237 }
235 238
236 239 };
237 240 auto httpFinishedLambda
238 241 = [this, httpDownloadFinished, tempFile](QNetworkReply *reply, QUuid dataId) noexcept {
239 242
240 243 // Don't do anything if the reply was abort
241 244 if (reply->error() == QNetworkReply::NoError) {
242 245 auto downloadFileUrl = QUrl{QString{reply->readAll()}.trimmed()};
243 246
244 247 qCInfo(LOG_AmdaProvider())
245 248 << tr("TORM AmdaProvider::retrieveData downloadFileUrl:") << downloadFileUrl;
246 249 // Executes request for downloading file //
247 250
248 251 // Creates destination file
249 252 if (tempFile->open()) {
250 253 // Executes request and store the request for progression
251 254 auto request = std::make_shared<QNetworkRequest>(downloadFileUrl);
252 255 updateRequestProgress(dataId, request, 0.0);
253 256 emit requestConstructed(request, dataId, httpDownloadFinished);
254 257 }
255 258 else {
256 259 emit dataProvidedFailed(dataId);
257 260 }
258 261 }
259 262 else {
260 263 qCCritical(LOG_AmdaProvider()) << tr("httpFinishedLambda ERROR");
261 264 m_AcqIdToRequestProgressMap.erase(dataId);
262 265 emit dataProvidedFailed(dataId);
263 266 }
264 267 };
265 268
266 269 // //////////////// //
267 270 // Executes request //
268 271 // //////////////// //
269 272
270 273 auto request = std::make_shared<QNetworkRequest>(url);
271 274 qCDebug(LOG_AmdaProvider()) << tr("First Request creation") << request.get();
272 275 updateRequestProgress(token, request, 0.0);
273 276
274 277 emit requestConstructed(request, token, httpFinishedLambda);
275 278 }
276 279
277 280 void AmdaProvider::updateRequestProgress(QUuid acqIdentifier,
278 281 std::shared_ptr<QNetworkRequest> request, double progress)
279 282 {
280 283 qCDebug(LOG_AmdaProvider()) << tr("updateRequestProgress request") << request.get();
281 284 auto acqIdToRequestProgressMapIt = m_AcqIdToRequestProgressMap.find(acqIdentifier);
282 285 if (acqIdToRequestProgressMapIt != m_AcqIdToRequestProgressMap.end()) {
283 286 auto &requestProgressMap = acqIdToRequestProgressMapIt->second;
284 287 auto requestProgressMapIt = requestProgressMap.find(request);
285 288 if (requestProgressMapIt != requestProgressMap.end()) {
286 289 requestProgressMapIt->second = progress;
287 290 qCDebug(LOG_AmdaProvider()) << tr("updateRequestProgress new progress for request")
288 291 << acqIdentifier << request.get() << progress;
289 292 }
290 293 else {
291 294 qCDebug(LOG_AmdaProvider()) << tr("updateRequestProgress new request") << acqIdentifier
292 295 << request.get() << progress;
293 296 acqIdToRequestProgressMapIt->second.insert(std::make_pair(request, progress));
294 297 }
295 298 }
296 299 else {
297 300 qCDebug(LOG_AmdaProvider()) << tr("updateRequestProgress new acqIdentifier")
298 301 << acqIdentifier << request.get() << progress;
299 302 auto requestProgressMap = std::map<std::shared_ptr<QNetworkRequest>, double>{};
300 303 requestProgressMap.insert(std::make_pair(request, progress));
301 304 m_AcqIdToRequestProgressMap.insert(
302 305 std::make_pair(acqIdentifier, std::move(requestProgressMap)));
303 306 }
304 307 }
@@ -1,131 +1,133
1 1 #include "AmdaResultParser.h"
2 2
3 3 #include "AmdaResultParserHelper.h"
4 4
5 5 #include <QFile>
6 6
7 7 #include <cmath>
8 8
9 9 Q_LOGGING_CATEGORY(LOG_AmdaResultParser, "AmdaResultParser")
10 10
11 11 namespace {
12 12
13 13 /// Message in result file when the file was not found on server
14 14 const auto FILE_NOT_FOUND_MESSAGE = QStringLiteral("Not Found");
15 15
16 16 /// Checks if a line is a comment line
17 17 bool isCommentLine(const QString &line)
18 18 {
19 19 return line.startsWith("#");
20 20 }
21 21
22 22 /**
23 23 * Creates helper that will be used to read AMDA file, according to the type passed as parameter
24 24 * @param valueType the type of values expected in the AMDA file (scalars, vectors, spectrograms...)
25 25 * @return the helper created
26 26 */
27 27 std::unique_ptr<IAmdaResultParserHelper> createHelper(AmdaResultParser::ValueType valueType)
28 28 {
29 29 switch (valueType) {
30 30 case AmdaResultParser::ValueType::SCALAR:
31 31 return std::make_unique<ScalarParserHelper>();
32 case AmdaResultParser::ValueType::SPECTROGRAM:
33 return std::make_unique<SpectrogramParserHelper>();
32 34 case AmdaResultParser::ValueType::VECTOR:
33 35 return std::make_unique<VectorParserHelper>();
34 36 case AmdaResultParser::ValueType::UNKNOWN:
35 37 // Invalid case
36 38 break;
37 39 }
38 40
39 41 // Invalid cases
40 42 qCCritical(LOG_AmdaResultParser())
41 43 << QObject::tr("Can't create helper to read result file: unsupported type");
42 44 return nullptr;
43 45 }
44 46
45 47 /**
46 48 * Reads properties of the stream passed as parameter
47 49 * @param helper the helper used to read properties line by line
48 50 * @param stream the stream to read
49 51 */
50 52 void readProperties(IAmdaResultParserHelper &helper, QTextStream &stream)
51 53 {
52 54 // Searches properties in the comment lines (as long as the reading has not reached the data)
53 55 // AMDA V2: while (stream.readLineInto(&line) && !line.contains(DATA_HEADER_REGEX)) {
54 56 QString line{};
55 57 while (stream.readLineInto(&line) && isCommentLine(line)) {
56 58 helper.readPropertyLine(line);
57 59 }
58 60 }
59 61
60 62 /**
61 63 * Reads results of the stream passed as parameter
62 64 * @param helper the helper used to read results line by line
63 65 * @param stream the stream to read
64 66 */
65 67 void readResults(IAmdaResultParserHelper &helper, QTextStream &stream)
66 68 {
67 69 QString line{};
68 70
69 71 // Skip comment lines
70 72 while (stream.readLineInto(&line) && isCommentLine(line)) {
71 73 }
72 74
73 75 if (!stream.atEnd()) {
74 76 do {
75 77 helper.readResultLine(line);
76 78 } while (stream.readLineInto(&line));
77 79 }
78 80 }
79 81
80 82 } // namespace
81 83
82 84 std::shared_ptr<IDataSeries> AmdaResultParser::readTxt(const QString &filePath,
83 85 ValueType valueType) noexcept
84 86 {
85 87 if (valueType == ValueType::UNKNOWN) {
86 88 qCCritical(LOG_AmdaResultParser())
87 89 << QObject::tr("Can't retrieve AMDA data: the type of values to be read is unknown");
88 90 return nullptr;
89 91 }
90 92
91 93 QFile file{filePath};
92 94
93 95 if (!file.open(QFile::ReadOnly | QIODevice::Text)) {
94 96 qCCritical(LOG_AmdaResultParser())
95 97 << QObject::tr("Can't retrieve AMDA data from file %1: %2")
96 98 .arg(filePath, file.errorString());
97 99 return nullptr;
98 100 }
99 101
100 102 QTextStream stream{&file};
101 103
102 104 // Checks if the file was found on the server
103 105 auto firstLine = stream.readLine();
104 106 if (firstLine.compare(FILE_NOT_FOUND_MESSAGE) == 0) {
105 107 qCCritical(LOG_AmdaResultParser())
106 108 << QObject::tr("Can't retrieve AMDA data from file %1: file was not found on server")
107 109 .arg(filePath);
108 110 return nullptr;
109 111 }
110 112
111 113 auto helper = createHelper(valueType);
112 114 Q_ASSERT(helper != nullptr);
113 115
114 116 // Reads header file to retrieve properties
115 117 stream.seek(0); // returns to the beginning of the file
116 118 readProperties(*helper, stream);
117 119
118 120 // Checks properties
119 121 if (helper->checkProperties()) {
120 122 // Reads results
121 123 // AMDA V2: remove line
122 124 stream.seek(0); // returns to the beginning of the file
123 125 readResults(*helper, stream);
124 126
125 127 // Creates data series
126 128 return helper->createSeries();
127 129 }
128 130 else {
129 131 return nullptr;
130 132 }
131 133 }
@@ -1,236 +1,260
1 1 #include "AmdaResultParserHelper.h"
2 2
3 3 #include <Common/DateUtils.h>
4 4
5 5 #include <Data/ScalarSeries.h>
6 6 #include <Data/Unit.h>
7 7 #include <Data/VectorSeries.h>
8 8
9 9 #include <QtCore/QDateTime>
10 10 #include <QtCore/QRegularExpression>
11 11
12 12 Q_LOGGING_CATEGORY(LOG_AmdaResultParserHelper, "AmdaResultParserHelper")
13 13
14 14 namespace {
15 15
16 16 // ///////// //
17 17 // Constants //
18 18 // ///////// //
19 19
20 20 /// Separator between values in a result line
21 21 const auto RESULT_LINE_SEPARATOR = QRegularExpression{QStringLiteral("\\s+")};
22 22
23 23 /// Format for dates in result files
24 24 const auto DATE_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz");
25 25
26 26 // /////// //
27 27 // Methods //
28 28 // /////// //
29 29
30 30 /**
31 31 * Checks that the properties contain a specific unit and that this unit is valid
32 32 * @param properties the properties map in which to search unit
33 33 * @param key the key to search for the unit in the properties
34 34 * @param errorMessage the error message to log in case the unit is invalid
35 35 * @return true if the unit is valid, false it it's invalid or was not found in the properties
36 36 */
37 37 bool checkUnit(const Properties &properties, const QString &key, const QString &errorMessage)
38 38 {
39 39 auto unit = properties.value(key).value<Unit>();
40 40 if (unit.m_Name.isEmpty()) {
41 41 qCWarning(LOG_AmdaResultParserHelper()) << errorMessage;
42 42 return false;
43 43 }
44 44
45 45 return true;
46 46 }
47 47
48 48 QDateTime dateTimeFromString(const QString &stringDate) noexcept
49 49 {
50 50 #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
51 51 return QDateTime::fromString(stringDate, Qt::ISODateWithMs);
52 52 #else
53 53 return QDateTime::fromString(stringDate, DATE_FORMAT);
54 54 #endif
55 55 }
56 56
57 57 /// Converts a string date to a double date
58 58 /// @return a double that represents the date in seconds, NaN if the string date can't be converted
59 59 double doubleDate(const QString &stringDate) noexcept
60 60 {
61 61 // Format: yyyy-MM-ddThh:mm:ss.zzz
62 62 auto dateTime = dateTimeFromString(stringDate);
63 63 dateTime.setTimeSpec(Qt::UTC);
64 64 return dateTime.isValid() ? DateUtils::secondsSinceEpoch(dateTime)
65 65 : std::numeric_limits<double>::quiet_NaN();
66 66 }
67 67
68 68 /**
69 69 * Reads a line from the AMDA file and tries to extract a x-axis data and value data from it
70 70 * @param xAxisData the vector in which to store the x-axis data extracted
71 71 * @param valuesData the vector in which to store the value extracted
72 72 * @param line the line to read to extract the property
73 73 * @param valuesIndexes indexes of insertion of read values. For example, if the line contains three
74 74 * columns of values, and valuesIndexes are {2, 0, 1}, the value of the third column will be read
75 75 * and inserted first, then the value of the first column, and finally the value of the second
76 76 * column.
77 77 * @param fillValue value that tags an invalid data. For example, if fillValue is -1 and a read
78 78 * value is -1, then this value is considered as invalid and converted to NaN
79 79 */
80 80 void tryReadResult(std::vector<double> &xAxisData, std::vector<double> &valuesData,
81 81 const QString &line, const std::vector<int> &valuesIndexes,
82 82 double fillValue = std::numeric_limits<double>::quiet_NaN())
83 83 {
84 84 auto lineData = line.split(RESULT_LINE_SEPARATOR, QString::SkipEmptyParts);
85 85
86 86 // Checks that the line contains expected number of values + x-axis value
87 87 if (lineData.size() == valuesIndexes.size() + 1) {
88 88 // X : the data is converted from date to double (in secs)
89 89 auto x = doubleDate(lineData.at(0));
90 90
91 91 // Adds result only if x is valid. Then, if value is invalid, it is set to NaN
92 92 if (!std::isnan(x)) {
93 93 xAxisData.push_back(x);
94 94
95 95 // Values
96 96 for (auto valueIndex : valuesIndexes) {
97 97 bool valueOk;
98 98 // we use valueIndex + 1 to skip column 0 (x-axis value)
99 99 auto value = lineData.at(valueIndex + 1).toDouble(&valueOk);
100 100
101 101 if (!valueOk) {
102 102 qCWarning(LOG_AmdaResultParserHelper())
103 103 << QObject::tr(
104 104 "Value from (line %1, column %2) is invalid and will be "
105 105 "converted to NaN")
106 106 .arg(line, valueIndex);
107 107 value = std::numeric_limits<double>::quiet_NaN();
108 108 }
109 109
110 110 // Handles fill value
111 111 if (!std::isnan(fillValue) && !std::isnan(value) && fillValue == value) {
112 112 value = std::numeric_limits<double>::quiet_NaN();
113 113 }
114 114
115 115 valuesData.push_back(value);
116 116 }
117 117 }
118 118 else {
119 119 qCWarning(LOG_AmdaResultParserHelper())
120 120 << QObject::tr("Can't retrieve results from line %1: x is invalid").arg(line);
121 121 }
122 122 }
123 123 else {
124 124 qCWarning(LOG_AmdaResultParserHelper())
125 125 << QObject::tr("Can't retrieve results from line %1: invalid line").arg(line);
126 126 }
127 127 }
128 128
129 129 /**
130 130 * Reads a line from the AMDA file and tries to extract a property from it
131 131 * @param properties the properties map in which to put the property extracted from the line
132 132 * @param key the key to which the property is added in the properties map
133 133 * @param line the line to read to extract the property
134 134 * @param regex the expected regex to extract the property. If the line matches this regex, the
135 135 * property is generated
136 136 * @param fun the function used to generate the property
137 137 * @return true if the property could be generated, false if the line does not match the regex, or
138 138 * if a property has already been generated for the key
139 139 */
140 140 template <typename GeneratePropertyFun>
141 141 bool tryReadProperty(Properties &properties, const QString &key, const QString &line,
142 142 const QRegularExpression &regex, GeneratePropertyFun fun)
143 143 {
144 144 if (properties.contains(key)) {
145 145 return false;
146 146 }
147 147
148 148 auto match = regex.match(line);
149 149 if (match.hasMatch()) {
150 150 properties.insert(key, fun(match));
151 151 }
152 152
153 153 return match.hasMatch();
154 154 }
155 155
156 156 /**
157 157 * Reads a line from the AMDA file and tries to extract a unit from it
158 158 * @sa tryReadProperty()
159 159 */
160 160 bool tryReadUnit(Properties &properties, const QString &key, const QString &line,
161 161 const QRegularExpression &regex, bool timeUnit = false)
162 162 {
163 163 return tryReadProperty(properties, key, line, regex, [timeUnit](const auto &match) {
164 164 return QVariant::fromValue(Unit{match.captured(1), timeUnit});
165 165 });
166 166 }
167 167
168 168 } // namespace
169 169
170 170 // ////////////////// //
171 171 // ScalarParserHelper //
172 172 // ////////////////// //
173 173
174 174 bool ScalarParserHelper::checkProperties()
175 175 {
176 176 return checkUnit(m_Properties, X_AXIS_UNIT_PROPERTY,
177 177 QObject::tr("The x-axis unit could not be found in the file"));
178 178 }
179 179
180 180 std::shared_ptr<IDataSeries> ScalarParserHelper::createSeries()
181 181 {
182 182 return std::make_shared<ScalarSeries>(std::move(m_XAxisData), std::move(m_ValuesData),
183 183 m_Properties.value(X_AXIS_UNIT_PROPERTY).value<Unit>(),
184 184 m_Properties.value(VALUES_UNIT_PROPERTY).value<Unit>());
185 185 }
186 186
187 187 void ScalarParserHelper::readPropertyLine(const QString &line)
188 188 {
189 189 tryReadUnit(m_Properties, X_AXIS_UNIT_PROPERTY, line, DEFAULT_X_AXIS_UNIT_REGEX, true);
190 190 }
191 191
192 192 void ScalarParserHelper::readResultLine(const QString &line)
193 193 {
194 194 tryReadResult(m_XAxisData, m_ValuesData, line, valuesIndexes());
195 195 }
196 196
197 197 std::vector<int> ScalarParserHelper::valuesIndexes() const
198 198 {
199 199 // Only one value to read
200 200 static auto result = std::vector<int>{0};
201 201 return result;
202 202 }
203 203
204 // /////////////////////// //
205 // SpectrogramParserHelper //
206 // /////////////////////// //
207
208 bool SpectrogramParserHelper::checkProperties()
209 {
210 /// @todo ALX
211 }
212
213 std::shared_ptr<IDataSeries> SpectrogramParserHelper::createSeries()
214 {
215 /// @todo ALX
216 }
217
218 void SpectrogramParserHelper::readPropertyLine(const QString &line)
219 {
220 /// @todo ALX
221 }
222
223 void SpectrogramParserHelper::readResultLine(const QString &line)
224 {
225 /// @todo ALX
226 }
227
204 228 // ////////////////// //
205 229 // VectorParserHelper //
206 230 // ////////////////// //
207 231
208 232 bool VectorParserHelper::checkProperties()
209 233 {
210 234 return checkUnit(m_Properties, X_AXIS_UNIT_PROPERTY,
211 235 QObject::tr("The x-axis unit could not be found in the file"));
212 236 }
213 237
214 238 std::shared_ptr<IDataSeries> VectorParserHelper::createSeries()
215 239 {
216 240 return std::make_shared<VectorSeries>(std::move(m_XAxisData), std::move(m_ValuesData),
217 241 m_Properties.value(X_AXIS_UNIT_PROPERTY).value<Unit>(),
218 242 m_Properties.value(VALUES_UNIT_PROPERTY).value<Unit>());
219 243 }
220 244
221 245 void VectorParserHelper::readPropertyLine(const QString &line)
222 246 {
223 247 tryReadUnit(m_Properties, X_AXIS_UNIT_PROPERTY, line, DEFAULT_X_AXIS_UNIT_REGEX, true);
224 248 }
225 249
226 250 void VectorParserHelper::readResultLine(const QString &line)
227 251 {
228 252 tryReadResult(m_XAxisData, m_ValuesData, line, valuesIndexes());
229 253 }
230 254
231 255 std::vector<int> VectorParserHelper::valuesIndexes() const
232 256 {
233 257 // 3 values to read, in order in the file (x, y, z)
234 258 static auto result = std::vector<int>{0, 1, 2};
235 259 return result;
236 260 }
General Comments 0
You need to be logged in to leave comments. Login now