2015-10-05 01:43:48 +02:00
|
|
|
#include "track.h"
|
|
|
|
|
2016-03-17 00:50:20 +01:00
|
|
|
|
2016-11-05 17:33:29 +01:00
|
|
|
#define WINDOW_OE 31
|
|
|
|
|
2016-02-11 20:58:52 +01:00
|
|
|
#define WINDOW_EF 3
|
2016-03-25 13:11:26 +01:00
|
|
|
#define WINDOW_SF 7
|
|
|
|
#define WINDOW_HF 3
|
2016-01-14 00:37:51 +01:00
|
|
|
|
2016-09-19 23:35:04 +02:00
|
|
|
|
2016-11-05 17:33:29 +01:00
|
|
|
static qreal median(QVector<qreal> v)
|
2015-10-05 01:43:48 +02:00
|
|
|
{
|
2016-11-05 17:33:29 +01:00
|
|
|
qSort(v.begin(), v.end());
|
|
|
|
return v.at(v.size() / 2);
|
2015-10-05 01:43:48 +02:00
|
|
|
}
|
|
|
|
|
2016-11-05 17:33:29 +01:00
|
|
|
static qreal MAD(QVector<qreal> v, qreal m)
|
2015-10-05 01:43:48 +02:00
|
|
|
{
|
2016-02-11 20:58:52 +01:00
|
|
|
for (int i = 0; i < v.size(); i++)
|
2016-11-05 17:33:29 +01:00
|
|
|
v[i] = qAbs(v.at(i) - m);
|
|
|
|
qSort(v.begin(), v.end());
|
|
|
|
return v.at(v.size() / 2);
|
2015-12-06 00:51:02 +01:00
|
|
|
}
|
|
|
|
|
2016-11-05 17:33:29 +01:00
|
|
|
static QSet<int> eliminate(const QVector<qreal> &v, int window)
|
2015-12-06 00:51:02 +01:00
|
|
|
{
|
2016-11-05 17:33:29 +01:00
|
|
|
QSet<int> rm;
|
2016-02-11 20:58:52 +01:00
|
|
|
qreal m, M;
|
2015-12-06 00:51:02 +01:00
|
|
|
|
2015-11-23 02:37:08 +01:00
|
|
|
|
2016-02-11 20:58:52 +01:00
|
|
|
if (v.size() < window)
|
2016-11-05 17:33:29 +01:00
|
|
|
return rm;
|
2015-10-05 01:43:48 +02:00
|
|
|
|
2016-02-11 20:58:52 +01:00
|
|
|
for (int i = window/2; i < v.size() - window/2; i++) {
|
|
|
|
m = median(v.mid(i - window/2, window));
|
|
|
|
M = MAD(v.mid(i - window/2, window), m);
|
2016-11-05 17:33:29 +01:00
|
|
|
if (qAbs((0.6745 * (v.at(i) - m)) / M) > 3.5)
|
|
|
|
rm.insert(i);
|
2015-11-25 23:17:39 +01:00
|
|
|
}
|
|
|
|
|
2016-11-05 17:33:29 +01:00
|
|
|
return rm;
|
2015-10-05 01:43:48 +02:00
|
|
|
}
|
|
|
|
|
2016-11-05 17:33:29 +01:00
|
|
|
static Graph filter(const Graph &v, int window)
|
2015-10-05 01:43:48 +02:00
|
|
|
{
|
2016-02-11 20:58:52 +01:00
|
|
|
qreal acc = 0;
|
2016-11-05 17:33:29 +01:00
|
|
|
Graph ret;
|
2015-12-06 00:51:02 +01:00
|
|
|
|
2016-02-11 20:58:52 +01:00
|
|
|
if (v.size() < window)
|
2016-11-05 17:33:29 +01:00
|
|
|
return ret;
|
2015-10-05 01:43:48 +02:00
|
|
|
|
2016-02-11 20:58:52 +01:00
|
|
|
for (int i = 0; i < window; i++)
|
2016-09-19 23:35:04 +02:00
|
|
|
acc += v.at(i).y();
|
2016-02-11 20:58:52 +01:00
|
|
|
for (int i = 0; i <= window/2; i++)
|
2016-09-19 23:35:04 +02:00
|
|
|
ret.append(GraphPoint(v.at(i).s(), v.at(i).t(), acc/window));
|
2015-10-05 01:43:48 +02:00
|
|
|
|
2016-02-11 20:58:52 +01:00
|
|
|
for (int i = window/2 + 1; i < v.size() - window/2; i++) {
|
2016-09-19 23:35:04 +02:00
|
|
|
acc += v.at(i + window/2).y() - v.at(i - (window/2 + 1)).y();
|
|
|
|
ret.append(GraphPoint(v.at(i).s(), v.at(i).t(), acc/window));
|
2015-10-05 01:43:48 +02:00
|
|
|
}
|
|
|
|
|
2016-02-11 20:58:52 +01:00
|
|
|
for (int i = v.size() - window/2; i < v.size(); i++)
|
2016-09-19 23:35:04 +02:00
|
|
|
ret.append(GraphPoint(v.at(i).s(), v.at(i).t(), acc/window));
|
2015-11-23 02:37:08 +01:00
|
|
|
|
2016-02-11 20:58:52 +01:00
|
|
|
return ret;
|
2016-01-14 00:37:51 +01:00
|
|
|
}
|
|
|
|
|
2016-10-28 14:33:36 +02:00
|
|
|
Track::Track(const TrackData &data) : _data(data)
|
2016-03-21 22:37:55 +01:00
|
|
|
{
|
2016-11-05 17:33:29 +01:00
|
|
|
qreal dt, ds, total;
|
|
|
|
|
2016-03-21 22:37:55 +01:00
|
|
|
|
2016-09-19 23:35:04 +02:00
|
|
|
_time.append(0);
|
2016-11-05 17:33:29 +01:00
|
|
|
_distance.append(0);
|
|
|
|
_speed.append(0);
|
|
|
|
|
2016-08-09 23:08:49 +02:00
|
|
|
for (int i = 1; i < data.count(); i++) {
|
2016-11-05 17:33:29 +01:00
|
|
|
ds = data.at(i).coordinates().distanceTo(data.at(i-1).coordinates());
|
|
|
|
_distance.append(ds);
|
2016-09-19 00:56:10 +02:00
|
|
|
|
2016-09-19 23:35:04 +02:00
|
|
|
if (data.first().hasTimestamp() && data.at(i).hasTimestamp())
|
|
|
|
_time.append(_data.first().timestamp().msecsTo(
|
|
|
|
_data.at(i).timestamp()) / 1000.0);
|
|
|
|
else
|
|
|
|
_time.append(NAN);
|
2016-11-05 17:33:29 +01:00
|
|
|
|
|
|
|
if (std::isnan(_time.at(i)) || std::isnan(_time.at(i-1)))
|
|
|
|
_speed.append(NAN);
|
|
|
|
else {
|
|
|
|
dt = _time.at(i) - _time.at(i-1);
|
|
|
|
if (!dt) {
|
|
|
|
_speed.append(_speed.at(i-1));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
_speed.append(ds / dt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_outliers = eliminate(_speed, WINDOW_OE);
|
|
|
|
|
|
|
|
total = 0;
|
|
|
|
for (int i = 0; i < _data.size(); i++) {
|
|
|
|
if (_outliers.contains(i))
|
|
|
|
continue;
|
|
|
|
total += _distance.at(i);
|
|
|
|
_distance[i] = total;
|
2016-09-19 23:35:04 +02:00
|
|
|
}
|
2016-03-21 22:37:55 +01:00
|
|
|
}
|
|
|
|
|
2016-09-19 00:56:10 +02:00
|
|
|
Graph Track::elevation() const
|
2015-11-23 02:37:08 +01:00
|
|
|
{
|
2016-11-05 17:33:29 +01:00
|
|
|
Graph raw;
|
2015-10-05 01:43:48 +02:00
|
|
|
|
2016-02-11 20:58:52 +01:00
|
|
|
if (!_data.size())
|
2016-09-19 23:35:04 +02:00
|
|
|
return raw;
|
2015-12-19 12:57:09 +01:00
|
|
|
|
2016-11-05 17:33:29 +01:00
|
|
|
for (int i = 0; i < _data.size(); i++) {
|
|
|
|
if (_outliers.contains(i))
|
|
|
|
continue;
|
|
|
|
|
2016-06-16 20:47:32 +02:00
|
|
|
if (_data.at(i).hasElevation())
|
2016-09-19 23:35:04 +02:00
|
|
|
raw.append(GraphPoint(_distance.at(i), _time.at(i),
|
2016-10-27 00:20:00 +02:00
|
|
|
_data.at(i).elevation()));
|
2016-11-05 17:33:29 +01:00
|
|
|
}
|
2015-10-05 01:43:48 +02:00
|
|
|
|
2016-09-19 23:35:04 +02:00
|
|
|
return filter(raw, WINDOW_EF);
|
2015-10-05 01:43:48 +02:00
|
|
|
}
|
|
|
|
|
2016-09-19 00:56:10 +02:00
|
|
|
Graph Track::speed() const
|
2015-10-05 01:43:48 +02:00
|
|
|
{
|
2016-11-05 17:33:29 +01:00
|
|
|
Graph raw;
|
|
|
|
qreal v;
|
2015-10-05 01:43:48 +02:00
|
|
|
|
2016-02-11 20:58:52 +01:00
|
|
|
if (!_data.size())
|
2016-09-19 23:35:04 +02:00
|
|
|
return raw;
|
2015-10-05 01:43:48 +02:00
|
|
|
|
2016-11-05 17:33:29 +01:00
|
|
|
for (int i = 0; i < _data.size(); i++) {
|
|
|
|
if (_outliers.contains(i))
|
|
|
|
continue;
|
|
|
|
|
2016-06-16 20:47:32 +02:00
|
|
|
if (_data.at(i).hasSpeed())
|
2016-07-28 00:23:22 +02:00
|
|
|
v = _data.at(i).speed();
|
2016-11-05 17:33:29 +01:00
|
|
|
else if (!std::isnan(_speed.at(i)))
|
|
|
|
v = _speed.at(i);
|
|
|
|
else
|
2016-06-16 20:47:32 +02:00
|
|
|
continue;
|
2015-10-05 01:43:48 +02:00
|
|
|
|
2016-09-19 23:35:04 +02:00
|
|
|
raw.append(GraphPoint(_distance.at(i), _time.at(i), v));
|
2016-02-11 20:58:52 +01:00
|
|
|
}
|
2015-10-05 01:43:48 +02:00
|
|
|
|
2016-11-05 17:33:29 +01:00
|
|
|
return filter(raw, WINDOW_SF);
|
2015-10-05 01:43:48 +02:00
|
|
|
}
|
|
|
|
|
2016-09-19 00:56:10 +02:00
|
|
|
Graph Track::heartRate() const
|
2015-11-23 02:37:08 +01:00
|
|
|
{
|
2016-11-05 17:33:29 +01:00
|
|
|
Graph raw;
|
2016-03-21 22:37:55 +01:00
|
|
|
|
2016-03-23 20:48:22 +01:00
|
|
|
if (!_data.size())
|
2016-09-19 23:35:04 +02:00
|
|
|
return raw;
|
2016-03-23 20:48:22 +01:00
|
|
|
|
2016-11-05 17:33:29 +01:00
|
|
|
for (int i = 0; i < _data.count(); i++) {
|
|
|
|
if (_outliers.contains(i))
|
|
|
|
continue;
|
|
|
|
|
2016-06-16 20:47:32 +02:00
|
|
|
if (_data.at(i).hasHeartRate())
|
2016-09-19 23:35:04 +02:00
|
|
|
raw.append(GraphPoint(_distance.at(i), _time.at(i),
|
|
|
|
_data.at(i).heartRate()));
|
2016-11-05 17:33:29 +01:00
|
|
|
}
|
2015-11-23 02:37:08 +01:00
|
|
|
|
2016-11-05 17:33:29 +01:00
|
|
|
return filter(raw, WINDOW_HF);
|
2016-03-21 22:37:55 +01:00
|
|
|
}
|
2015-11-23 02:37:08 +01:00
|
|
|
|
2016-09-19 00:56:10 +02:00
|
|
|
Graph Track::temperature() const
|
2016-06-16 20:47:32 +02:00
|
|
|
{
|
2016-11-05 17:33:29 +01:00
|
|
|
Graph raw;
|
|
|
|
|
|
|
|
for (int i = 0; i < _data.size(); i++) {
|
|
|
|
if (_outliers.contains(i))
|
|
|
|
continue;
|
2016-06-16 20:47:32 +02:00
|
|
|
|
|
|
|
if (_data.at(i).hasTemperature())
|
2016-09-19 23:35:04 +02:00
|
|
|
raw.append(GraphPoint(_distance.at(i), _time.at(i),
|
|
|
|
_data.at(i).temperature()));
|
2016-11-05 17:33:29 +01:00
|
|
|
}
|
2016-09-19 00:56:10 +02:00
|
|
|
|
2016-09-19 23:35:04 +02:00
|
|
|
return Graph(raw);
|
2016-06-16 20:47:32 +02:00
|
|
|
}
|
|
|
|
|
2016-08-09 23:08:49 +02:00
|
|
|
qreal Track::distance() const
|
2015-12-20 09:33:40 +01:00
|
|
|
{
|
2016-11-05 17:33:29 +01:00
|
|
|
return _distance.isEmpty() ? 0 : _distance.last();
|
2016-08-09 23:08:49 +02:00
|
|
|
}
|
2015-12-20 09:33:40 +01:00
|
|
|
|
2016-08-09 23:08:49 +02:00
|
|
|
qreal Track::time() const
|
|
|
|
{
|
|
|
|
return (_data.size() < 2) ? 0 :
|
|
|
|
(_data.first().timestamp().msecsTo(_data.last().timestamp()) / 1000.0);
|
2015-12-20 09:33:40 +01:00
|
|
|
}
|
2016-01-14 00:37:51 +01:00
|
|
|
|
2016-02-11 20:58:52 +01:00
|
|
|
QDateTime Track::date() const
|
2016-01-14 00:37:51 +01:00
|
|
|
{
|
2016-08-09 23:08:49 +02:00
|
|
|
return (_data.size()) ? _data.first().timestamp() : QDateTime();
|
2016-01-14 00:37:51 +01:00
|
|
|
}
|
2016-11-05 17:33:29 +01:00
|
|
|
|
|
|
|
Path Track::track() const
|
|
|
|
{
|
|
|
|
Path ret;
|
|
|
|
|
|
|
|
for (int i = 0; i < _data.size(); i++) {
|
|
|
|
if (_outliers.contains(i))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ret.append(_data.at(i).coordinates());
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|