##// 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
@@ -1,298 +1,313
1 1 #ifndef SCIQLOP_ARRAYDATA_H
2 2 #define SCIQLOP_ARRAYDATA_H
3 3
4 4 #include <Common/SortUtils.h>
5 5
6 6 #include <QReadLocker>
7 7 #include <QReadWriteLock>
8 8 #include <QVector>
9 9
10 10 #include <memory>
11 11
12 12 template <int Dim>
13 13 class ArrayData;
14 14
15 15 using DataContainer = QVector<QVector<double> >;
16 16
17 17 namespace arraydata_detail {
18 18
19 19 /// Struct used to sort ArrayData
20 20 template <int Dim>
21 21 struct Sort {
22 22 static std::shared_ptr<ArrayData<Dim> > sort(const DataContainer &data,
23 23 const std::vector<int> &sortPermutation)
24 24 {
25 25 auto nbComponents = data.size();
26 26 auto sortedData = DataContainer(nbComponents);
27 27
28 28 for (auto i = 0; i < nbComponents; ++i) {
29 29 sortedData[i] = SortUtils::sort(data.at(i), sortPermutation);
30 30 }
31 31
32 32 return std::make_shared<ArrayData<Dim> >(std::move(sortedData));
33 33 }
34 34 };
35 35
36 36 /// Specialization for uni-dimensional ArrayData
37 37 template <>
38 38 struct Sort<1> {
39 39 static std::shared_ptr<ArrayData<1> > sort(const DataContainer &data,
40 40 const std::vector<int> &sortPermutation)
41 41 {
42 42 return std::make_shared<ArrayData<1> >(SortUtils::sort(data.at(0), sortPermutation));
43 43 }
44 44 };
45 45
46 46 } // namespace arraydata_detail
47 47
48 48 /**
49 49 * @brief The ArrayData class represents a dataset for a data series.
50 50 *
51 51 * A dataset can be unidimensional or two-dimensional. This property is determined by the Dim
52 52 * template-parameter. In a case of a two-dimensional dataset, each dataset component has the same
53 53 * number of values
54 54 *
55 55 * @tparam Dim the dimension of the ArrayData (one or two)
56 56 * @sa IDataSeries
57 57 */
58 58 template <int Dim>
59 59 class ArrayData {
60 60 public:
61 61 class IteratorValue {
62 62 public:
63 63 explicit IteratorValue(const DataContainer &container, bool begin) : m_Its{}
64 64 {
65 65 for (auto i = 0; i < container.size(); ++i) {
66 66 m_Its.push_back(begin ? container.at(i).cbegin() : container.at(i).cend());
67 67 }
68 68 }
69 69
70 70 double at(int index) const { return *m_Its.at(index); }
71 71 double first() const { return *m_Its.front(); }
72 72
73 73 void next()
74 74 {
75 75 for (auto &it : m_Its) {
76 76 ++it;
77 77 }
78 78 }
79 79
80 80 bool operator==(const IteratorValue &other) const { return m_Its == other.m_Its; }
81 81
82 82 private:
83 83 std::vector<DataContainer::value_type::const_iterator> m_Its;
84 84 };
85 85
86 86 class Iterator {
87 87 public:
88 88 using iterator_category = std::forward_iterator_tag;
89 89 using value_type = const IteratorValue;
90 90 using difference_type = std::ptrdiff_t;
91 91 using pointer = value_type *;
92 92 using reference = value_type &;
93 93
94 94 Iterator(const DataContainer &container, bool begin) : m_CurrentValue{container, begin} {}
95 95
96 96 virtual ~Iterator() noexcept = default;
97 97 Iterator(const Iterator &) = default;
98 98 Iterator(Iterator &&) = default;
99 99 Iterator &operator=(const Iterator &) = default;
100 100 Iterator &operator=(Iterator &&) = default;
101 101
102 102 Iterator &operator++()
103 103 {
104 104 m_CurrentValue.next();
105 105 return *this;
106 106 }
107 107
108 108 pointer operator->() const { return &m_CurrentValue; }
109 109 reference operator*() const { return m_CurrentValue; }
110 110
111 111 bool operator==(const Iterator &other) const
112 112 {
113 113 return m_CurrentValue == other.m_CurrentValue;
114 114 }
115 115
116 116 bool operator!=(const Iterator &other) const { return !(*this == other); }
117 117
118 118 private:
119 119 IteratorValue m_CurrentValue;
120 120 };
121 121
122 122 // ///// //
123 123 // Ctors //
124 124 // ///// //
125 125
126 126 /**
127 127 * Ctor for a unidimensional ArrayData
128 128 * @param data the data the ArrayData will hold
129 129 */
130 130 template <int D = Dim, typename = std::enable_if_t<D == 1> >
131 131 explicit ArrayData(QVector<double> data) : m_Data{1, QVector<double>{}}
132 132 {
133 133 m_Data[0] = std::move(data);
134 134 }
135 135
136 136 /**
137 137 * Ctor for a two-dimensional ArrayData. The number of components (number of vectors) must be
138 138 * greater than 2 and each component must have the same number of values
139 139 * @param data the data the ArrayData will hold
140 140 * @throws std::invalid_argument if the number of components is less than 2
141 141 * @remarks if the number of values is not the same for each component, no value is set
142 142 */
143 143 template <int D = Dim, typename = std::enable_if_t<D == 2> >
144 144 explicit ArrayData(DataContainer data)
145 145 {
146 146 auto nbComponents = data.size();
147 147 if (nbComponents < 2) {
148 148 throw std::invalid_argument{
149 149 QString{"A multidimensional ArrayData must have at least 2 components (found: %1"}
150 150 .arg(data.size())
151 151 .toStdString()};
152 152 }
153 153
154 154 auto nbValues = data.front().size();
155 155 if (std::all_of(data.cbegin(), data.cend(), [nbValues](const auto &component) {
156 156 return component.size() == nbValues;
157 157 })) {
158 158 m_Data = std::move(data);
159 159 }
160 160 else {
161 161 m_Data = DataContainer{nbComponents, QVector<double>{}};
162 162 }
163 163 }
164 164
165 165 /// Copy ctor
166 166 explicit ArrayData(const ArrayData &other)
167 167 {
168 168 QReadLocker otherLocker{&other.m_Lock};
169 169 m_Data = other.m_Data;
170 170 }
171 171
172 172 // /////////////// //
173 173 // General methods //
174 174 // /////////////// //
175 175
176 176 /**
177 177 * Merges into the array data an other array data. The two array datas must have the same number
178 178 * of components so the merge can be done
179 179 * @param other the array data to merge with
180 180 * @param prepend if true, the other array data is inserted at the beginning, otherwise it is
181 181 * inserted at the end
182 182 */
183 183 void add(const ArrayData<Dim> &other, bool prepend = false)
184 184 {
185 185 QWriteLocker locker{&m_Lock};
186 186 QReadLocker otherLocker{&other.m_Lock};
187 187
188 188 auto nbComponents = m_Data.size();
189 189 if (nbComponents != other.m_Data.size()) {
190 190 return;
191 191 }
192 192
193 193 for (auto componentIndex = 0; componentIndex < nbComponents; ++componentIndex) {
194 194 if (prepend) {
195 195 const auto &otherData = other.data(componentIndex);
196 196 const auto otherDataSize = otherData.size();
197 197
198 198 auto &data = m_Data[componentIndex];
199 199 data.insert(data.begin(), otherDataSize, 0.);
200 200
201 201 for (auto i = 0; i < otherDataSize; ++i) {
202 202 data.replace(i, otherData.at(i));
203 203 }
204 204 }
205 205 else {
206 206 m_Data[componentIndex] += other.data(componentIndex);
207 207 }
208 208 }
209 209 }
210 210
211 211 void clear()
212 212 {
213 213 QWriteLocker locker{&m_Lock};
214 214
215 215 auto nbComponents = m_Data.size();
216 216 for (auto i = 0; i < nbComponents; ++i) {
217 217 m_Data[i].clear();
218 218 }
219 219 }
220 220
221 221 /**
222 222 * @return the data of a component
223 223 * @param componentIndex the index of the component to retrieve the data
224 224 * @return the component's data, empty vector if the index is invalid
225 225 */
226 226 QVector<double> data(int componentIndex) const noexcept
227 227 {
228 228 QReadLocker locker{&m_Lock};
229 229
230 230 return (componentIndex >= 0 && componentIndex < m_Data.size()) ? m_Data.at(componentIndex)
231 231 : QVector<double>{};
232 232 }
233 233
234 234 /// @return the size (i.e. number of values) of a single component
235 235 /// @remarks in a case of a two-dimensional ArrayData, each component has the same size
236 236 int size() const
237 237 {
238 238 QReadLocker locker{&m_Lock};
239 239 return m_Data[0].size();
240 240 }
241 241
242 242 std::shared_ptr<ArrayData<Dim> > sort(const std::vector<int> &sortPermutation)
243 243 {
244 244 QReadLocker locker{&m_Lock};
245 245 return arraydata_detail::Sort<Dim>::sort(m_Data, sortPermutation);
246 246 }
247 247
248 248 // ///////// //
249 249 // Iterators //
250 250 // ///////// //
251 251
252 252 Iterator cbegin() const { return Iterator{m_Data, true}; }
253 253 Iterator cend() const { return Iterator{m_Data, false}; }
254 254
255 255 // ///////////// //
256 256 // 1-dim methods //
257 257 // ///////////// //
258 258
259 259 /**
260 260 * @return the data at a specified index
261 261 * @remarks index must be a valid position
262 262 * @remarks this method is only available for a unidimensional ArrayData
263 263 */
264 264 template <int D = Dim, typename = std::enable_if_t<D == 1> >
265 265 double at(int index) const noexcept
266 266 {
267 267 QReadLocker locker{&m_Lock};
268 268 return m_Data[0].at(index);
269 269 }
270 270
271 271 /**
272 272 * @return the data as a vector, as a const reference
273 273 * @remarks this method is only available for a unidimensional ArrayData
274 274 */
275 275 template <int D = Dim, typename = std::enable_if_t<D == 1> >
276 276 const QVector<double> &cdata() const noexcept
277 277 {
278 278 QReadLocker locker{&m_Lock};
279 279 return m_Data.at(0);
280 280 }
281 281
282 282 /**
283 283 * @return the data as a vector
284 284 * @remarks this method is only available for a unidimensional ArrayData
285 285 */
286 286 template <int D = Dim, typename = std::enable_if_t<D == 1> >
287 287 QVector<double> data() const noexcept
288 288 {
289 289 QReadLocker locker{&m_Lock};
290 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 308 private:
294 309 DataContainer m_Data;
295 310 mutable QReadWriteLock m_Lock;
296 311 };
297 312
298 313 #endif // SCIQLOP_ARRAYDATA_H
@@ -1,288 +1,293
1 1 #ifndef SCIQLOP_DATASERIES_H
2 2 #define SCIQLOP_DATASERIES_H
3 3
4 #include "CoreGlobal.h"
5
4 6 #include <Common/SortUtils.h>
5 7
6 8 #include <Data/ArrayData.h>
7 9 #include <Data/IDataSeries.h>
8 10
9 11 #include <QLoggingCategory>
10
11 12 #include <QReadLocker>
12 13 #include <QReadWriteLock>
13 14 #include <memory>
14 15
15 Q_DECLARE_LOGGING_CATEGORY(LOG_DataSeries)
16 Q_LOGGING_CATEGORY(LOG_DataSeries, "DataSeries")
17
16 // We don't use the Qt macro since the log is used in the header file, which causes multiple log
17 // definitions with inheritance. Inline method is used instead
18 inline const QLoggingCategory &LOG_DataSeries()
19 {
20 static const QLoggingCategory category{"DataSeries"};
21 return category;
22 }
18 23
19 24 /**
20 25 * @brief The DataSeries class is the base (abstract) implementation of IDataSeries.
21 26 *
22 27 * It proposes to set a dimension for the values ​​data.
23 28 *
24 29 * A DataSeries is always sorted on its x-axis data.
25 30 *
26 31 * @tparam Dim The dimension of the values data
27 32 *
28 33 */
29 34 template <int Dim>
30 class DataSeries : public IDataSeries {
35 class SCIQLOP_CORE_EXPORT DataSeries : public IDataSeries {
31 36 public:
32 37 class IteratorValue {
33 38 public:
34 39 explicit IteratorValue(const DataSeries &dataSeries, bool begin)
35 40 : m_XIt(begin ? dataSeries.xAxisData()->cbegin() : dataSeries.xAxisData()->cend()),
36 41 m_ValuesIt(begin ? dataSeries.valuesData()->cbegin()
37 42 : dataSeries.valuesData()->cend())
38 43 {
39 44 }
40 45
41 46 double x() const { return m_XIt->at(0); }
42 47 double value() const { return m_ValuesIt->at(0); }
43 48 double value(int componentIndex) const { return m_ValuesIt->at(componentIndex); }
44 49
45 50 void next()
46 51 {
47 52 ++m_XIt;
48 53 ++m_ValuesIt;
49 54 }
50 55
51 56 bool operator==(const IteratorValue &other) const
52 57 {
53 58 return std::tie(m_XIt, m_ValuesIt) == std::tie(other.m_XIt, other.m_ValuesIt);
54 59 }
55 60
56 61 private:
57 62 ArrayData<1>::Iterator m_XIt;
58 63 typename ArrayData<Dim>::Iterator m_ValuesIt;
59 64 };
60 65
61 66 class Iterator {
62 67 public:
63 68 using iterator_category = std::forward_iterator_tag;
64 69 using value_type = const IteratorValue;
65 70 using difference_type = std::ptrdiff_t;
66 71 using pointer = value_type *;
67 72 using reference = value_type &;
68 73
69 74 Iterator(const DataSeries &dataSeries, bool begin) : m_CurrentValue{dataSeries, begin} {}
70 75 virtual ~Iterator() noexcept = default;
71 76 Iterator(const Iterator &) = default;
72 77 Iterator(Iterator &&) = default;
73 78 Iterator &operator=(const Iterator &) = default;
74 79 Iterator &operator=(Iterator &&) = default;
75 80
76 81 Iterator &operator++()
77 82 {
78 83 m_CurrentValue.next();
79 84 return *this;
80 85 }
81 86
82 87 pointer operator->() const { return &m_CurrentValue; }
83 88
84 89 reference operator*() const { return m_CurrentValue; }
85 90
86 91 bool operator==(const Iterator &other) const
87 92 {
88 93 return m_CurrentValue == other.m_CurrentValue;
89 94 }
90 95
91 96 bool operator!=(const Iterator &other) const { return !(*this == other); }
92 97
93 98 private:
94 99 IteratorValue m_CurrentValue;
95 100 };
96 101
97 102 /// @sa IDataSeries::xAxisData()
98 103 std::shared_ptr<ArrayData<1> > xAxisData() override { return m_XAxisData; }
99 104 const std::shared_ptr<ArrayData<1> > xAxisData() const { return m_XAxisData; }
100 105
101 106 /// @sa IDataSeries::xAxisUnit()
102 107 Unit xAxisUnit() const override { return m_XAxisUnit; }
103 108
104 109 /// @return the values dataset
105 110 std::shared_ptr<ArrayData<Dim> > valuesData() { return m_ValuesData; }
106 111 const std::shared_ptr<ArrayData<Dim> > valuesData() const { return m_ValuesData; }
107 112
108 113 /// @sa IDataSeries::valuesUnit()
109 114 Unit valuesUnit() const override { return m_ValuesUnit; }
110 115
111 116
112 117 SqpRange range() const override
113 118 {
114 119 if (!m_XAxisData->cdata().isEmpty()) {
115 120 return SqpRange{m_XAxisData->cdata().first(), m_XAxisData->cdata().last()};
116 121 }
117 122
118 123 return SqpRange{};
119 124 }
120 125
121 126 void clear()
122 127 {
123 128 m_XAxisData->clear();
124 129 m_ValuesData->clear();
125 130 }
126 131
127 132 /// Merges into the data series an other data series
128 133 /// @remarks the data series to merge with is cleared after the operation
129 134 void merge(IDataSeries *dataSeries) override
130 135 {
131 136 dataSeries->lockWrite();
132 137 lockWrite();
133 138
134 139 if (auto other = dynamic_cast<DataSeries<Dim> *>(dataSeries)) {
135 140 const auto &otherXAxisData = other->xAxisData()->cdata();
136 141 const auto &xAxisData = m_XAxisData->cdata();
137 142
138 143 // As data series are sorted, we can improve performances of merge, by call the sort
139 144 // method only if the two data series overlap.
140 145 if (!otherXAxisData.empty()) {
141 146 auto firstValue = otherXAxisData.front();
142 147 auto lastValue = otherXAxisData.back();
143 148
144 149 auto xAxisDataBegin = xAxisData.cbegin();
145 150 auto xAxisDataEnd = xAxisData.cend();
146 151
147 152 bool prepend;
148 153 bool sortNeeded;
149 154
150 155 if (std::lower_bound(xAxisDataBegin, xAxisDataEnd, firstValue) == xAxisDataEnd) {
151 156 // Other data series if after data series
152 157 prepend = false;
153 158 sortNeeded = false;
154 159 }
155 160 else if (std::upper_bound(xAxisDataBegin, xAxisDataEnd, lastValue)
156 161 == xAxisDataBegin) {
157 162 // Other data series if before data series
158 163 prepend = true;
159 164 sortNeeded = false;
160 165 }
161 166 else {
162 167 // The two data series overlap
163 168 prepend = false;
164 169 sortNeeded = true;
165 170 }
166 171
167 172 // Makes the merge
168 173 m_XAxisData->add(*other->xAxisData(), prepend);
169 174 m_ValuesData->add(*other->valuesData(), prepend);
170 175
171 176 if (sortNeeded) {
172 177 sort();
173 178 }
174 179 }
175 180
176 181 // Clears the other data series
177 182 other->clear();
178 183 }
179 184 else {
180 185 qCWarning(LOG_DataSeries())
181 186 << QObject::tr("Detection of a type of IDataSeries we cannot merge with !");
182 187 }
183 188 unlock();
184 189 dataSeries->unlock();
185 190 }
186 191
187 192 // ///////// //
188 193 // Iterators //
189 194 // ///////// //
190 195
191 196 Iterator cbegin() const { return Iterator{*this, true}; }
192 197
193 198 Iterator cend() const { return Iterator{*this, false}; }
194 199
195 200 std::pair<Iterator, Iterator> subData(double min, double max) const
196 201 {
197 202 if (min > max) {
198 203 std::swap(min, max);
199 204 }
200 205
201 206 auto begin = cbegin();
202 207 auto end = cend();
203 208
204 209 auto lowerIt
205 210 = std::lower_bound(begin, end, min, [](const auto &itValue, const auto &value) {
206 211 return itValue.x() == value;
207 212 });
208 213 auto upperIt
209 214 = std::upper_bound(begin, end, max, [](const auto &value, const auto &itValue) {
210 215 return itValue.x() == value;
211 216 });
212 217
213 218 return std::make_pair(lowerIt, upperIt);
214 219 }
215 220
216 221 // /////// //
217 222 // Mutexes //
218 223 // /////// //
219 224
220 225 virtual void lockRead() { m_Lock.lockForRead(); }
221 226 virtual void lockWrite() { m_Lock.lockForWrite(); }
222 227 virtual void unlock() { m_Lock.unlock(); }
223 228
224 229 protected:
225 230 /// Protected ctor (DataSeries is abstract). The vectors must have the same size, otherwise a
226 231 /// DataSeries with no values will be created.
227 232 /// @remarks data series is automatically sorted on its x-axis data
228 233 explicit DataSeries(std::shared_ptr<ArrayData<1> > xAxisData, const Unit &xAxisUnit,
229 234 std::shared_ptr<ArrayData<Dim> > valuesData, const Unit &valuesUnit)
230 235 : m_XAxisData{xAxisData},
231 236 m_XAxisUnit{xAxisUnit},
232 237 m_ValuesData{valuesData},
233 238 m_ValuesUnit{valuesUnit}
234 239 {
235 240 if (m_XAxisData->size() != m_ValuesData->size()) {
236 241 clear();
237 242 }
238 243
239 244 // Sorts data if it's not the case
240 245 const auto &xAxisCData = m_XAxisData->cdata();
241 246 if (!std::is_sorted(xAxisCData.cbegin(), xAxisCData.cend())) {
242 247 sort();
243 248 }
244 249 }
245 250
246 251 /// Copy ctor
247 252 explicit DataSeries(const DataSeries<Dim> &other)
248 253 : m_XAxisData{std::make_shared<ArrayData<1> >(*other.m_XAxisData)},
249 254 m_XAxisUnit{other.m_XAxisUnit},
250 255 m_ValuesData{std::make_shared<ArrayData<Dim> >(*other.m_ValuesData)},
251 256 m_ValuesUnit{other.m_ValuesUnit}
252 257 {
253 258 // Since a series is ordered from its construction and is always ordered, it is not
254 259 // necessary to call the sort method here ('other' is sorted)
255 260 }
256 261
257 262 /// Assignment operator
258 263 template <int D>
259 264 DataSeries &operator=(DataSeries<D> other)
260 265 {
261 266 std::swap(m_XAxisData, other.m_XAxisData);
262 267 std::swap(m_XAxisUnit, other.m_XAxisUnit);
263 268 std::swap(m_ValuesData, other.m_ValuesData);
264 269 std::swap(m_ValuesUnit, other.m_ValuesUnit);
265 270
266 271 return *this;
267 272 }
268 273
269 274 private:
270 275 /**
271 276 * Sorts data series on its x-axis data
272 277 */
273 278 void sort() noexcept
274 279 {
275 280 auto permutation = SortUtils::sortPermutation(*m_XAxisData, std::less<double>());
276 281 m_XAxisData = m_XAxisData->sort(permutation);
277 282 m_ValuesData = m_ValuesData->sort(permutation);
278 283 }
279 284
280 285 std::shared_ptr<ArrayData<1> > m_XAxisData;
281 286 Unit m_XAxisUnit;
282 287 std::shared_ptr<ArrayData<Dim> > m_ValuesData;
283 288 Unit m_ValuesUnit;
284 289
285 290 QReadWriteLock m_Lock;
286 291 };
287 292
288 293 #endif // SCIQLOP_DATASERIES_H
@@ -1,41 +1,31
1 1 #include <Data/ScalarSeries.h>
2 2
3 3 ScalarSeries::ScalarSeries(QVector<double> xAxisData, QVector<double> valuesData,
4 4 const Unit &xAxisUnit, const Unit &valuesUnit)
5 5 : DataSeries{std::make_shared<ArrayData<1> >(std::move(xAxisData)), xAxisUnit,
6 6 std::make_shared<ArrayData<1> >(std::move(valuesData)), valuesUnit}
7 7 {
8 8 }
9 9
10 10 std::unique_ptr<IDataSeries> ScalarSeries::clone() const
11 11 {
12 12 return std::make_unique<ScalarSeries>(*this);
13 13 }
14 14
15 15 std::shared_ptr<IDataSeries> ScalarSeries::subDataSeries(const SqpRange &range)
16 16 {
17 17 auto subXAxisData = QVector<double>();
18 18 auto subValuesData = QVector<double>();
19 19 this->lockRead();
20 20 {
21 const auto &currentXData = this->xAxisData()->cdata();
22 const auto &currentValuesData = this->valuesData()->cdata();
23
24 auto xDataBegin = currentXData.cbegin();
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);
21 auto bounds = subData(range.m_TStart, range.m_TEnd);
22 for (auto it = bounds.first; it != bounds.second; ++it) {
23 subXAxisData.append(it->x());
24 subValuesData.append(it->value());
35 25 }
36 26 }
37 27 this->unlock();
38 28
39 29 return std::make_shared<ScalarSeries>(subXAxisData, subValuesData, this->xAxisUnit(),
40 30 this->valuesUnit());
41 31 }
@@ -1,44 +1,45
1 1 # On ignore toutes les règles vera++ pour le fichier spimpl
2 2 Common/spimpl\.h:\d+:.*
3 3
4 4 # Ignore false positive relative to two class definitions in a same file
5 5 DataSourceItem\.h:\d+:.*IPSIS_S01.*
6 6
7 7 # Ignore false positive relative to a template class
8 8 ArrayData\.h:\d+:.*IPSIS_S04_VARIABLE.*found: (D)
9 9 ArrayData\.h:\d+:.*IPSIS_S04_NAMESPACE.*found: (arraydata_detail)
10 10 ArrayData\.h:\d+:.*IPSIS_S06.*found: (D)
11 11 ArrayData\.h:\d+:.*IPSIS_S06.*found: (Dim)
12 DataSeries\.h:\d+:.*IPSIS_S04_METHOD.*found: LOG_DataSeries
12 13 DataSeries\.h:\d+:.*IPSIS_S04_VARIABLE.*
13 14
14 15 # Ignore false positive relative to iterators
15 16 ArrayData\.h:\d+:.*IPSIS_S04_VARIABLE.*found: (forward_iterator_tag)
16 17 ArrayData\.h:\d+:.*IPSIS_S04_VARIABLE.*found: (IteratorValue)
17 18 ArrayData\.h:\d+:.*IPSIS_S04_VARIABLE.*found: (ptrdiff_t)
18 19 ArrayData\.h:\d+:.*IPSIS_S04_VARIABLE.*found: (value_type)
19 20 ArrayData\.h:\d+:.*IPSIS_S05.*
20 21 ArrayData\.h:\d+:.*IPSIS_S06.*found: (iterator_category)
21 22 ArrayData\.h:\d+:.*IPSIS_S06.*found: (forward_iterator_tag)
22 23 ArrayData\.h:\d+:.*IPSIS_S06.*found: (value_type)
23 24 ArrayData\.h:\d+:.*IPSIS_S06.*found: (IteratorValue)
24 25 ArrayData\.h:\d+:.*IPSIS_S06.*found: (difference_type)
25 26 ArrayData\.h:\d+:.*IPSIS_S06.*found: (ptrdiff_t)
26 27 ArrayData\.h:\d+:.*IPSIS_S06.*found: (pointer)
27 28 ArrayData\.h:\d+:.*IPSIS_S06.*found: (reference)
28 29 ArrayData\.h:\d+:.*IPSIS_S06.*found: (value_type)
29 30 DataSeries\.h:\d+:.*IPSIS_S05.*
30 31 DataSeries\.h:\d+:.*IPSIS_S06.*found: (iterator_category)
31 32 DataSeries\.h:\d+:.*IPSIS_S06.*found: (forward_iterator_tag)
32 33 DataSeries\.h:\d+:.*IPSIS_S06.*found: (value_type)
33 34 DataSeries\.h:\d+:.*IPSIS_S06.*found: (IteratorValue)
34 35 DataSeries\.h:\d+:.*IPSIS_S06.*found: (difference_type)
35 36 DataSeries\.h:\d+:.*IPSIS_S06.*found: (ptrdiff_t)
36 37 DataSeries\.h:\d+:.*IPSIS_S06.*found: (pointer)
37 38 DataSeries\.h:\d+:.*IPSIS_S06.*found: (reference)
38 39 DataSeries\.h:\d+:.*IPSIS_S06.*found: (value_type)
39 40
40 41 # Ignore false positive relative to an alias
41 42 DataSourceItemAction\.h:\d+:.*IPSIS_S06.*found: (ExecuteFunction)
42 43
43 44 # Ignore false positive relative to unnamed namespace
44 45 VariableController\.cpp:\d+:.*IPSIS_F13.*
@@ -1,16 +1,17
1 1 #ifndef SCIQLOP_AMDADEFS_H
2 2 #define SCIQLOP_AMDADEFS_H
3 3
4 4 #include <QString>
5 5
6 6 // ////////////// //
7 7 // AMDA constants //
8 8 // ////////////// //
9 9
10 10 // Relevant keys in JSON file
11 11 extern const QString AMDA_COMPONENT_KEY;
12 extern const QString AMDA_DATA_TYPE_KEY;
12 13 extern const QString AMDA_PRODUCT_KEY;
13 14 extern const QString AMDA_ROOT_KEY;
14 15 extern const QString AMDA_XML_ID_KEY;
15 16
16 17 #endif // SCIQLOP_AMDADEFS_H
@@ -1,19 +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 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 21 #endif // SCIQLOP_AMDARESULTPARSER_H
@@ -1,6 +1,7
1 1 #include "AmdaDefs.h"
2 2
3 3 const QString AMDA_COMPONENT_KEY = QStringLiteral("component");
4 const QString AMDA_DATA_TYPE_KEY = QStringLiteral("dataType");
4 5 const QString AMDA_PRODUCT_KEY = QStringLiteral("parameter");
5 6 const QString AMDA_ROOT_KEY = QStringLiteral("dataCenter");
6 7 const QString AMDA_XML_ID_KEY = QStringLiteral("xml:id");
@@ -1,83 +1,74
1 1 #include "AmdaPlugin.h"
2 2 #include "AmdaDefs.h"
3 3 #include "AmdaParser.h"
4 4 #include "AmdaProvider.h"
5 5
6 6 #include <DataSource/DataSourceController.h>
7 7 #include <DataSource/DataSourceItem.h>
8 8 #include <DataSource/DataSourceItemAction.h>
9 9
10 10 #include <SqpApplication.h>
11 11
12 12 Q_LOGGING_CATEGORY(LOG_AmdaPlugin, "AmdaPlugin")
13 13
14 14 namespace {
15 15
16 16 /// Name of the data source
17 17 const auto DATA_SOURCE_NAME = QStringLiteral("AMDA");
18 18
19 19 /// Path of the file used to generate the data source item for AMDA
20 20 const auto JSON_FILE_PATH = QStringLiteral(":/samples/AmdaSampleV2.json");
21 21
22 22 void associateActions(DataSourceItem &item, const QUuid &dataSourceUid)
23 23 {
24 24 auto addLoadAction = [&item, dataSourceUid](const QString &label) {
25 25 item.addAction(
26 26 std::make_unique<DataSourceItemAction>(label, [dataSourceUid](DataSourceItem &item) {
27 27 if (auto app = sqpApp) {
28 28 app->dataSourceController().loadProductItem(dataSourceUid, item);
29 29 }
30 30 }));
31 31 };
32 32
33 33 const auto itemType = item.type();
34 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 35 addLoadAction(QObject::tr("Load %1 product").arg(item.name()));
45 36 }
46 37 else if (itemType == DataSourceItemType::COMPONENT) {
47 38 addLoadAction(QObject::tr("Load %1 component").arg(item.name()));
48 39 }
49 40
50 41 auto count = item.childCount();
51 42 for (auto i = 0; i < count; ++i) {
52 43 if (auto child = item.child(i)) {
53 44 associateActions(*child, dataSourceUid);
54 45 }
55 46 }
56 47 }
57 48
58 49 } // namespace
59 50
60 51 void AmdaPlugin::initialize()
61 52 {
62 53 if (auto app = sqpApp) {
63 54 // Registers to the data source controller
64 55 auto &dataSourceController = app->dataSourceController();
65 56 auto dataSourceUid = dataSourceController.registerDataSource(DATA_SOURCE_NAME);
66 57
67 58 // Sets data source tree
68 59 if (auto dataSourceItem = AmdaParser::readJson(JSON_FILE_PATH)) {
69 60 associateActions(*dataSourceItem, dataSourceUid);
70 61
71 62 dataSourceController.setDataSourceItem(dataSourceUid, std::move(dataSourceItem));
72 63 }
73 64 else {
74 65 qCCritical(LOG_AmdaPlugin()) << tr("No data source item could be generated for AMDA");
75 66 }
76 67
77 68 // Sets data provider
78 69 dataSourceController.setDataProvider(dataSourceUid, std::make_unique<AmdaProvider>());
79 70 }
80 71 else {
81 72 qCWarning(LOG_AmdaPlugin()) << tr("Can't access to SciQlop application");
82 73 }
83 74 }
@@ -1,150 +1,168
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 format for a request on AMDA server. The parameters are as follows:
21 21 /// - %1: start date
22 22 /// - %2: end date
23 23 /// - %3: parameter id
24 24 const auto AMDA_URL_FORMAT = QStringLiteral(
25 25 "http://amda.irap.omp.eu/php/rest/"
26 26 "getParameter.php?startTime=%1&stopTime=%2&parameterID=%3&outputFormat=ASCII&"
27 27 "timeFormat=ISO8601&gzip=0");
28 28
29 29 /// Dates format passed in the URL (e.g 2013-09-23T09:00)
30 30 const auto AMDA_TIME_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss");
31 31
32 32 /// Formats a time to a date that can be passed in URL
33 33 QString dateFormat(double sqpRange) noexcept
34 34 {
35 35 auto dateTime = DateUtils::dateTime(sqpRange);
36 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 52 } // namespace
40 53
41 54 AmdaProvider::AmdaProvider()
42 55 {
43 56 qCDebug(LOG_AmdaProvider()) << tr("AmdaProvider::AmdaProvider") << QThread::currentThread();
44 57 if (auto app = sqpApp) {
45 58 auto &networkController = app->networkController();
46 59 connect(this, SIGNAL(requestConstructed(QNetworkRequest, QUuid,
47 60 std::function<void(QNetworkReply *, QUuid)>)),
48 61 &networkController,
49 62 SLOT(onProcessRequested(QNetworkRequest, QUuid,
50 63 std::function<void(QNetworkReply *, QUuid)>)));
51 64
52 65
53 66 connect(&sqpApp->networkController(), SIGNAL(replyDownloadProgress(QUuid, double)), this,
54 67 SIGNAL(dataProvidedProgress(QUuid, double)));
55 68 }
56 69 }
57 70
58 71 void AmdaProvider::requestDataLoading(QUuid acqIdentifier, const DataProviderParameters &parameters)
59 72 {
60 73 // NOTE: Try to use multithread if possible
61 74 const auto times = parameters.m_Times;
62 75 const auto data = parameters.m_Data;
63 76 for (const auto &dateTime : qAsConst(times)) {
64 77 this->retrieveData(acqIdentifier, dateTime, data);
65 78
66 79 // TORM
67 80 // QThread::msleep(200);
68 81 }
69 82 }
70 83
71 84 void AmdaProvider::requestDataAborting(QUuid acqIdentifier)
72 85 {
73 86 if (auto app = sqpApp) {
74 87 auto &networkController = app->networkController();
75 88 networkController.onReplyCanceled(acqIdentifier);
76 89 }
77 90 }
78 91
79 92 void AmdaProvider::retrieveData(QUuid token, const SqpRange &dateTime, const QVariantHash &data)
80 93 {
81 94 // Retrieves product ID from data: if the value is invalid, no request is made
82 95 auto productId = data.value(AMDA_XML_ID_KEY).toString();
83 96 if (productId.isNull()) {
84 97 qCCritical(LOG_AmdaProvider()) << tr("Can't retrieve data: unknown product id");
85 98 return;
86 99 }
87 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 107 // Creates URL //
91 108 // /////////// //
92 109
93 110 auto startDate = dateFormat(dateTime.m_TStart);
94 111 auto endDate = dateFormat(dateTime.m_TEnd);
95 112
96 113 auto url = QUrl{QString{AMDA_URL_FORMAT}.arg(startDate, endDate, productId)};
97 114 qCInfo(LOG_AmdaProvider()) << tr("TORM AmdaProvider::retrieveData url:") << url;
98 115 auto tempFile = std::make_shared<QTemporaryFile>();
99 116
100 117 // LAMBDA
101 auto httpDownloadFinished
102 = [this, dateTime, tempFile](QNetworkReply *reply, QUuid dataId) noexcept {
103
104 // Don't do anything if the reply was abort
105 if (reply->error() != QNetworkReply::OperationCanceledError) {
106
107 if (tempFile) {
108 auto replyReadAll = reply->readAll();
109 if (!replyReadAll.isEmpty()) {
110 tempFile->write(replyReadAll);
111 }
112 tempFile->close();
113
114 // Parse results file
115 if (auto dataSeries = AmdaResultParser::readTxt(tempFile->fileName())) {
116 emit dataProvided(dataId, dataSeries, dateTime);
117 }
118 else {
119 /// @todo ALX : debug
120 }
121 }
122 }
123
124 };
118 auto httpDownloadFinished = [this, dateTime, tempFile,
119 productValueType](QNetworkReply *reply, QUuid dataId) noexcept {
120
121 // Don't do anything if the reply was abort
122 if (reply->error() != QNetworkReply::OperationCanceledError) {
123
124 if (tempFile) {
125 auto replyReadAll = reply->readAll();
126 if (!replyReadAll.isEmpty()) {
127 tempFile->write(replyReadAll);
128 }
129 tempFile->close();
130
131 // Parse results file
132 if (auto dataSeries
133 = AmdaResultParser::readTxt(tempFile->fileName(), productValueType)) {
134 emit dataProvided(dataId, dataSeries, dateTime);
135 }
136 else {
137 /// @todo ALX : debug
138 }
139 }
140 }
141
142 };
125 143 auto httpFinishedLambda
126 144 = [this, httpDownloadFinished, tempFile](QNetworkReply *reply, QUuid dataId) noexcept {
127 145
128 146 // Don't do anything if the reply was abort
129 147 if (reply->error() != QNetworkReply::OperationCanceledError) {
130 148 auto downloadFileUrl = QUrl{QString{reply->readAll()}};
131 149
132 150
133 151 qCInfo(LOG_AmdaProvider())
134 152 << tr("TORM AmdaProvider::retrieveData downloadFileUrl:") << downloadFileUrl;
135 153 // Executes request for downloading file //
136 154
137 155 // Creates destination file
138 156 if (tempFile->open()) {
139 157 // Executes request
140 158 emit requestConstructed(QNetworkRequest{downloadFileUrl}, dataId,
141 159 httpDownloadFinished);
142 160 }
143 161 }
144 162 };
145 163
146 164 // //////////////// //
147 165 // Executes request //
148 166 // //////////////// //
149 167 emit requestConstructed(QNetworkRequest{url}, token, httpFinishedLambda);
150 168 }
@@ -1,160 +1,216
1 1 #include "AmdaResultParser.h"
2 2
3 3 #include <Common/DateUtils.h>
4 4 #include <Data/ScalarSeries.h>
5 #include <Data/VectorSeries.h>
5 6
6 7 #include <QDateTime>
7 8 #include <QFile>
8 9 #include <QRegularExpression>
9 10
10 11 #include <cmath>
11 12
12 13 Q_LOGGING_CATEGORY(LOG_AmdaResultParser, "AmdaResultParser")
13 14
14 15 namespace {
15 16
16 17 /// Message in result file when the file was not found on server
17 18 const auto FILE_NOT_FOUND_MESSAGE = QStringLiteral("Not Found");
18 19
19 20 /// Format for dates in result files
20 21 const auto DATE_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz");
21 22
22 23 /// Separator between values in a result line
23 24 const auto RESULT_LINE_SEPARATOR = QRegularExpression{QStringLiteral("\\s+")};
24 25
25 26 /// Regex to find unit in a line. Examples of valid lines:
26 27 /// ... - Units : nT - ...
27 28 /// ... -Units:nT- ...
28 29 /// ... -Units: mΒ²- ...
29 30 /// ... - Units : m/s - ...
30 31 const auto UNIT_REGEX = QRegularExpression{QStringLiteral("-\\s*Units\\s*:\\s*(.+?)\\s*-")};
31 32
32 33 /// Converts a string date to a double date
33 34 /// @return a double that represents the date in seconds, NaN if the string date can't be converted
34 35 double doubleDate(const QString &stringDate) noexcept
35 36 {
36 37 auto dateTime = QDateTime::fromString(stringDate, DATE_FORMAT);
37 38 dateTime.setTimeSpec(Qt::UTC);
38 39 return dateTime.isValid() ? DateUtils::secondsSinceEpoch(dateTime)
39 40 : std::numeric_limits<double>::quiet_NaN();
40 41 }
41 42
42 43 /// Checks if a line is a comment line
43 44 bool isCommentLine(const QString &line)
44 45 {
45 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 69 * Reads stream to retrieve x-axis unit
50 70 * @param stream the stream to read
51 71 * @return the unit that has been read in the stream, a default unit (time unit with no label) if an
52 72 * error occured during reading
53 73 */
54 74 Unit readXAxisUnit(QTextStream &stream)
55 75 {
56 76 QString line{};
57 77
58 78 // Searches unit in the comment lines
59 79 while (stream.readLineInto(&line) && isCommentLine(line)) {
60 80 auto match = UNIT_REGEX.match(line);
61 81 if (match.hasMatch()) {
62 82 return Unit{match.captured(1), true};
63 83 }
64 84 }
65 85
66 86 qCWarning(LOG_AmdaResultParser()) << QObject::tr("The unit could not be found in the file");
67 87
68 88 // Error cases
69 89 return Unit{{}, true};
70 90 }
71 91
72 92 /**
73 93 * Reads stream to retrieve results
74 94 * @param stream the stream to read
75 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 102 auto xData = QVector<double>{};
80 auto valuesData = QVector<double>{};
103 auto valuesData = QVector<QVector<double> >(expectedNbValues);
81 104
82 105 QString line{};
83 106
84 107 while (stream.readLineInto(&line)) {
85 108 // Ignore comment lines
86 109 if (!isCommentLine(line)) {
87 110 auto lineData = line.split(RESULT_LINE_SEPARATOR, QString::SkipEmptyParts);
88 if (lineData.size() == 2) {
111 if (lineData.size() == expectedNbValues + 1) {
89 112 // X : the data is converted from date to double (in secs)
90 113 auto x = doubleDate(lineData.at(0));
91 114
92 // Value
93 bool valueOk;
94 auto value = lineData.at(1).toDouble(&valueOk);
95
96 115 // Adds result only if x is valid. Then, if value is invalid, it is set to NaN
97 116 if (!std::isnan(x)) {
98 117 xData.push_back(x);
99 118
100 if (!valueOk) {
101 qCWarning(LOG_AmdaResultParser())
102 << QObject::tr(
103 "Value from line %1 is invalid and will be converted to NaN")
104 .arg(line);
105 value = std::numeric_limits<double>::quiet_NaN();
119 // Values
120 for (auto valueIndex = 0; valueIndex < expectedNbValues; ++valueIndex) {
121 auto column = valueIndex + 1;
122
123 bool valueOk;
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 137 else {
111 138 qCWarning(LOG_AmdaResultParser())
112 139 << QObject::tr("Can't retrieve results from line %1: x is invalid")
113 140 .arg(line);
114 141 }
115 142 }
116 143 else {
117 144 qCWarning(LOG_AmdaResultParser())
118 145 << QObject::tr("Can't retrieve results from line %1: invalid line").arg(line);
119 146 }
120 147 }
121 148 }
122 149
123 150 return qMakePair(std::move(xData), std::move(valuesData));
124 151 }
125 152
126 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 164 QFile file{filePath};
131 165
132 166 if (!file.open(QFile::ReadOnly | QIODevice::Text)) {
133 167 qCCritical(LOG_AmdaResultParser())
134 168 << QObject::tr("Can't retrieve AMDA data from file %1: %2")
135 169 .arg(filePath, file.errorString());
136 170 return nullptr;
137 171 }
138 172
139 173 QTextStream stream{&file};
140 174
141 175 // Checks if the file was found on the server
142 176 auto firstLine = stream.readLine();
143 177 if (firstLine.compare(FILE_NOT_FOUND_MESSAGE) == 0) {
144 178 qCCritical(LOG_AmdaResultParser())
145 179 << QObject::tr("Can't retrieve AMDA data from file %1: file was not found on server")
146 180 .arg(filePath);
147 181 return nullptr;
148 182 }
149 183
150 184 // Reads x-axis unit
151 185 stream.seek(0); // returns to the beginning of the file
152 186 auto xAxisUnit = readXAxisUnit(stream);
153 187
154 188 // Reads results
155 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),
159 xAxisUnit, Unit{});
212 // Invalid cases
213 qCCritical(LOG_AmdaResultParser())
214 << QObject::tr("Can't create data series: unsupported value type");
215 return nullptr;
160 216 }
@@ -1,212 +1,310
1 1 #include "AmdaResultParser.h"
2 2
3 3 #include <Data/ScalarSeries.h>
4 #include <Data/VectorSeries.h>
4 5
5 6 #include <QObject>
6 7 #include <QtTest>
7 8
8 9 namespace {
9 10
10 11 /// Path for the tests
11 12 const auto TESTS_RESOURCES_PATH
12 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 20 /// Compares two vectors that can potentially contain NaN values
15 21 bool compareVectors(const QVector<double> &v1, const QVector<double> &v2)
16 22 {
17 23 if (v1.size() != v2.size()) {
18 24 return false;
19 25 }
20 26
21 27 auto result = true;
22 28 auto v2It = v2.cbegin();
23 29 for (auto v1It = v1.cbegin(), v1End = v1.cend(); v1It != v1End && result; ++v1It, ++v2It) {
24 30 auto v1Value = *v1It;
25 31 auto v2Value = *v2It;
26 32
27 33 // If v1 is NaN, v2 has to be NaN too
28 34 result = std::isnan(v1Value) ? std::isnan(v2Value) : (v1Value == v2Value);
29 35 }
30 36
31 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 65 QString inputFilePath(const QString &inputFileName)
35 66 {
36 67 return QFileInfo{TESTS_RESOURCES_PATH, inputFileName}.absoluteFilePath();
37 68 }
38 69
70 template <typename T>
39 71 struct ExpectedResults {
40 72 explicit ExpectedResults() = default;
41 73
42 /// Ctor with QVector<QDateTime> as x-axis data. Datetimes are converted to doubles
43 74 explicit ExpectedResults(Unit xAxisUnit, Unit valuesUnit, const QVector<QDateTime> &xAxisData,
44 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 84 : m_ParsingOK{true},
46 85 m_XAxisUnit{xAxisUnit},
47 86 m_ValuesUnit{valuesUnit},
48 87 m_XAxisData{},
49 88 m_ValuesData{std::move(valuesData)}
50 89 {
51 90 // Converts QVector<QDateTime> to QVector<double>
52 91 std::transform(xAxisData.cbegin(), xAxisData.cend(), std::back_inserter(m_XAxisData),
53 92 [](const auto &dateTime) { return dateTime.toMSecsSinceEpoch() / 1000.; });
54 93 }
55 94
56 95 /**
57 96 * Validates a DataSeries compared to the expected results
58 97 * @param results the DataSeries to validate
59 98 */
60 99 void validate(std::shared_ptr<IDataSeries> results)
61 100 {
62 101 if (m_ParsingOK) {
63 auto scalarSeries = dynamic_cast<ScalarSeries *>(results.get());
64 QVERIFY(scalarSeries != nullptr);
102 auto dataSeries = dynamic_cast<T *>(results.get());
103 QVERIFY(dataSeries != nullptr);
65 104
66 105 // Checks units
67 QVERIFY(scalarSeries->xAxisUnit() == m_XAxisUnit);
68 QVERIFY(scalarSeries->valuesUnit() == m_ValuesUnit);
106 QVERIFY(dataSeries->xAxisUnit() == m_XAxisUnit);
107 QVERIFY(dataSeries->valuesUnit() == m_ValuesUnit);
69 108
70 109 // Checks values : as the vectors can potentially contain NaN values, we must use a
71 110 // custom vector comparison method
72 QVERIFY(compareVectors(scalarSeries->xAxisData()->data(), m_XAxisData));
73 QVERIFY(compareVectors(scalarSeries->valuesData()->data(), m_ValuesData));
111 QVERIFY(compareVectors(dataSeries->xAxisData()->data(), m_XAxisData));
112 QVERIFY(compareVectors(valuesData(*dataSeries->valuesData()), m_ValuesData));
74 113 }
75 114 else {
76 115 QVERIFY(results == nullptr);
77 116 }
78 117 }
79 118
80 119 // Parsing was successfully completed
81 120 bool m_ParsingOK{false};
82 121 // Expected x-axis unit
83 122 Unit m_XAxisUnit{};
84 123 // Expected values unit
85 124 Unit m_ValuesUnit{};
86 125 // Expected x-axis data
87 126 QVector<double> m_XAxisData{};
88 127 // Expected values data
89 QVector<double> m_ValuesData{};
128 QVector<QVector<double> > m_ValuesData{};
90 129 };
91 130
92 131 } // namespace
93 132
94 Q_DECLARE_METATYPE(ExpectedResults)
133 Q_DECLARE_METATYPE(ExpectedResults<ScalarSeries>)
134 Q_DECLARE_METATYPE(ExpectedResults<VectorSeries>)
95 135
96 136 class TestAmdaResultParser : public QObject {
97 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 168 private slots:
99 169 /// Input test data
100 /// @sa testTxtJson()
101 void testReadTxt_data();
170 /// @sa testReadScalarTxt()
171 void testReadScalarTxt_data();
102 172
103 /// Tests parsing of a TXT file
104 void testReadTxt();
173 /// Tests parsing scalar series of a TXT file
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 // ////////////// //
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");
186 testReadDataStructure<ScalarSeries>();
117 187
118 188 // ////////// //
119 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 192 // Valid files
127 193 QTest::newRow("Valid file")
128 194 << QStringLiteral("ValidScalar1.txt")
129 << ExpectedResults{
195 << ExpectedResults<ScalarSeries>{
130 196 Unit{QStringLiteral("nT"), true}, Unit{},
131 197 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
132 198 dateTime(2013, 9, 23, 9, 2, 30), dateTime(2013, 9, 23, 9, 3, 30),
133 199 dateTime(2013, 9, 23, 9, 4, 30), dateTime(2013, 9, 23, 9, 5, 30),
134 200 dateTime(2013, 9, 23, 9, 6, 30), dateTime(2013, 9, 23, 9, 7, 30),
135 201 dateTime(2013, 9, 23, 9, 8, 30), dateTime(2013, 9, 23, 9, 9, 30)},
136 202 QVector<double>{-2.83950, -2.71850, -2.52150, -2.57633, -2.58050, -2.48325, -2.63025,
137 203 -2.55800, -2.43250, -2.42200}};
138 204
139 205 QTest::newRow("Valid file (value of first line is invalid but it is converted to NaN")
140 206 << QStringLiteral("WrongValue.txt")
141 << ExpectedResults{
207 << ExpectedResults<ScalarSeries>{
142 208 Unit{QStringLiteral("nT"), true}, Unit{},
143 209 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
144 210 dateTime(2013, 9, 23, 9, 2, 30)},
145 211 QVector<double>{std::numeric_limits<double>::quiet_NaN(), -2.71850, -2.52150}};
146 212
147 213 QTest::newRow("Valid file that contains NaN values")
148 214 << QStringLiteral("NaNValue.txt")
149 << ExpectedResults{
215 << ExpectedResults<ScalarSeries>{
150 216 Unit{QStringLiteral("nT"), true}, Unit{},
151 217 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
152 218 dateTime(2013, 9, 23, 9, 2, 30)},
153 219 QVector<double>{std::numeric_limits<double>::quiet_NaN(), -2.71850, -2.52150}};
154 220
155 221 // Valid files but with some invalid lines (wrong unit, wrong values, etc.)
156 222 QTest::newRow("No unit file") << QStringLiteral("NoUnit.txt")
157 << ExpectedResults{Unit{QStringLiteral(""), true}, Unit{},
158 QVector<QDateTime>{}, QVector<double>{}};
223 << ExpectedResults<ScalarSeries>{Unit{QStringLiteral(""), true},
224 Unit{}, QVector<QDateTime>{},
225 QVector<double>{}};
159 226 QTest::newRow("Wrong unit file")
160 227 << QStringLiteral("WrongUnit.txt")
161 << ExpectedResults{Unit{QStringLiteral(""), true}, Unit{},
162 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30),
163 dateTime(2013, 9, 23, 9, 1, 30),
164 dateTime(2013, 9, 23, 9, 2, 30)},
165 QVector<double>{-2.83950, -2.71850, -2.52150}};
228 << ExpectedResults<ScalarSeries>{Unit{QStringLiteral(""), true}, Unit{},
229 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30),
230 dateTime(2013, 9, 23, 9, 1, 30),
231 dateTime(2013, 9, 23, 9, 2, 30)},
232 QVector<double>{-2.83950, -2.71850, -2.52150}};
166 233
167 234 QTest::newRow("Wrong results file (date of first line is invalid")
168 235 << QStringLiteral("WrongDate.txt")
169 << ExpectedResults{
236 << ExpectedResults<ScalarSeries>{
170 237 Unit{QStringLiteral("nT"), true}, Unit{},
171 238 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
172 239 QVector<double>{-2.71850, -2.52150}};
173 240
174 241 QTest::newRow("Wrong results file (too many values for first line")
175 242 << QStringLiteral("TooManyValues.txt")
176 << ExpectedResults{
243 << ExpectedResults<ScalarSeries>{
177 244 Unit{QStringLiteral("nT"), true}, Unit{},
178 245 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
179 246 QVector<double>{-2.71850, -2.52150}};
180 247
181 248 QTest::newRow("Wrong results file (x of first line is NaN")
182 249 << QStringLiteral("NaNX.txt")
183 << ExpectedResults{
250 << ExpectedResults<ScalarSeries>{
184 251 Unit{QStringLiteral("nT"), true}, Unit{},
185 252 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
186 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 260 // Invalid files
189 QTest::newRow("Invalid file (unexisting file)") << QStringLiteral("UnexistingFile.txt")
190 << ExpectedResults{};
261 QTest::newRow("Invalid file (unexisting file)")
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")
193 << ExpectedResults{};
268 void TestAmdaResultParser::testReadScalarTxt()
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);
199 QFETCH(ExpectedResults, expectedResults);
275 testReadDataStructure<VectorSeries>();
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
202 auto filePath = inputFilePath(inputFileName);
203 auto results = AmdaResultParser::readTxt(filePath);
296 // Valid files but with some invalid lines (wrong unit, wrong values, etc.)
297 QTest::newRow("Invalid file type (scalar)")
298 << QStringLiteral("ValidScalar1.txt")
299 << ExpectedResults<VectorSeries>{Unit{QStringLiteral("nT"), true}, Unit{},
300 QVector<QDateTime>{},
301 QVector<QVector<double> >{{}, {}, {}}};
302 }
204 303
205 // ///////////////// //
206 // Validates results //
207 // ///////////////// //
208 expectedResults.validate(results);
304 void TestAmdaResultParser::testReadVectorTxt()
305 {
306 testRead<VectorSeries>(AmdaResultParser::ValueType::VECTOR);
209 307 }
210 308
211 309 QTEST_MAIN(TestAmdaResultParser)
212 310 #include "TestAmdaResultParser.moc"
General Comments 0
You need to be logged in to leave comments. Login now