1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2025-02-20 09:40:49 +01:00

Redesigned track data filtering

This commit is contained in:
Martin Tůma 2016-11-05 17:33:29 +01:00
parent 584245785e
commit b9098c0dc5
7 changed files with 134 additions and 74 deletions

View File

@ -64,7 +64,9 @@ HEADERS += src/config.h \
src/trackdata.h \ src/trackdata.h \
src/routedata.h \ src/routedata.h \
src/fitparser.h \ src/fitparser.h \
src/format.h src/format.h \
src/path.h \
src/assert.h
SOURCES += src/main.cpp \ SOURCES += src/main.cpp \
src/gui.cpp \ src/gui.cpp \
src/poi.cpp \ src/poi.cpp \

View File

@ -27,7 +27,22 @@ private:
qreal _y; qreal _y;
}; };
typedef QVector<GraphPoint> Graph; class Graph : public QVector<GraphPoint>
{
public:
Graph() : QVector<GraphPoint>() {_time = true;}
void append(const GraphPoint &p)
{
if (std::isnan(p.t()))
_time = false;
QVector<GraphPoint>::append(p);
}
bool hasTime() const {return _time;}
private:
bool _time;
};
Q_DECLARE_TYPEINFO(GraphPoint, Q_PRIMITIVE_TYPE); Q_DECLARE_TYPEINFO(GraphPoint, Q_PRIMITIVE_TYPE);

View File

@ -5,15 +5,6 @@
#define GRAPH_WIDTH 1 #define GRAPH_WIDTH 1
#define HOVER_WIDTH 2 #define HOVER_WIDTH 2
static bool hasTime(const Graph &graph)
{
for (int i = 0; i < graph.count(); i++)
if (std::isnan(graph.at(i).t()))
return false;
return true;
}
GraphItem::GraphItem(const Graph &graph, QGraphicsItem *parent) GraphItem::GraphItem(const Graph &graph, QGraphicsItem *parent)
: QGraphicsObject(parent) : QGraphicsObject(parent)
{ {
@ -24,7 +15,7 @@ GraphItem::GraphItem(const Graph &graph, QGraphicsItem *parent)
_type = Distance; _type = Distance;
_graph = graph; _graph = graph;
_sx = 1.0; _sy = 1.0; _sx = 1.0; _sy = 1.0;
_time = hasTime(_graph); _time = _graph.hasTime();
updatePath(); updatePath();
updateBounds(); updateBounds();

9
src/path.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef PATH_H
#define PATH_H
#include <QVector>
#include "coordinates.h"
typedef QVector<Coordinates> Path;
#endif // PATH_H

View File

@ -1,67 +1,53 @@
#include "track.h" #include "track.h"
#define WINDOW_OE 31
#define WINDOW_EF 3 #define WINDOW_EF 3
#define WINDOW_SE 11
#define WINDOW_SF 7 #define WINDOW_SF 7
#define WINDOW_HE 11
#define WINDOW_HF 3 #define WINDOW_HF 3
static bool lt(const GraphPoint &v1, const GraphPoint &v2) static qreal median(QVector<qreal> v)
{ {
return v1.y() < v2.y(); qSort(v.begin(), v.end());
return v.at(v.size() / 2);
} }
static qreal median(QVector<GraphPoint> v) static qreal MAD(QVector<qreal> v, qreal m)
{
qSort(v.begin(), v.end(), lt);
return v.at(v.size() / 2).y();
}
static qreal MAD(QVector<GraphPoint> v, qreal m)
{ {
for (int i = 0; i < v.size(); i++) for (int i = 0; i < v.size(); i++)
v[i].setY(qAbs(v.at(i).y() - m)); v[i] = qAbs(v.at(i) - m);
qSort(v.begin(), v.end(), lt); qSort(v.begin(), v.end());
return v.at(v.size() / 2).y(); return v.at(v.size() / 2);
} }
static QVector<GraphPoint> eliminate(const QVector<GraphPoint> &v, int window) static QSet<int> eliminate(const QVector<qreal> &v, int window)
{ {
QList<int> rm; QSet<int> rm;
QVector<GraphPoint> ret;
qreal m, M; qreal m, M;
if (v.size() < window) if (v.size() < window)
return QVector<GraphPoint>(v); return rm;
for (int i = window/2; i < v.size() - window/2; i++) { for (int i = window/2; i < v.size() - window/2; i++) {
m = median(v.mid(i - window/2, window)); m = median(v.mid(i - window/2, window));
M = MAD(v.mid(i - window/2, window), m); M = MAD(v.mid(i - window/2, window), m);
if (qAbs((0.6745 * (v.at(i).y() - m)) / M) > 3.5) if (qAbs((0.6745 * (v.at(i) - m)) / M) > 3.5)
rm.append(i); rm.insert(i);
} }
QList<int>::const_iterator it = rm.begin(); return rm;
for (int i = 0; i < v.size(); i++) {
if (it == rm.end() || *it != i)
ret.append(v.at(i));
else
it++;
}
return ret;
} }
static QVector<GraphPoint> filter(const QVector<GraphPoint> &v, int window) static Graph filter(const Graph &v, int window)
{ {
qreal acc = 0; qreal acc = 0;
QVector<GraphPoint> ret; Graph ret;
if (v.size() < window) if (v.size() < window)
return QVector<GraphPoint>(v); return ret;
for (int i = 0; i < window; i++) for (int i = 0; i < window; i++)
acc += v.at(i).y(); acc += v.at(i).y();
@ -81,94 +67,128 @@ static QVector<GraphPoint> filter(const QVector<GraphPoint> &v, int window)
Track::Track(const TrackData &data) : _data(data) Track::Track(const TrackData &data) : _data(data)
{ {
qreal dist = 0; qreal dt, ds, total;
_distance.append(0);
_time.append(0); _time.append(0);
_distance.append(0);
_speed.append(0);
for (int i = 1; i < data.count(); i++) { for (int i = 1; i < data.count(); i++) {
dist += data.at(i).coordinates().distanceTo(data.at(i-1).coordinates()); ds = data.at(i).coordinates().distanceTo(data.at(i-1).coordinates());
_distance.append(dist); _distance.append(ds);
if (data.first().hasTimestamp() && data.at(i).hasTimestamp()) if (data.first().hasTimestamp() && data.at(i).hasTimestamp())
_time.append(_data.first().timestamp().msecsTo( _time.append(_data.first().timestamp().msecsTo(
_data.at(i).timestamp()) / 1000.0); _data.at(i).timestamp()) / 1000.0);
else else
_time.append(NAN); _time.append(NAN);
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;
} }
} }
Graph Track::elevation() const Graph Track::elevation() const
{ {
QVector<GraphPoint> raw; Graph raw;
if (!_data.size()) if (!_data.size())
return raw; return raw;
for (int i = 0; i < _data.size(); i++) for (int i = 0; i < _data.size(); i++) {
if (_outliers.contains(i))
continue;
if (_data.at(i).hasElevation()) if (_data.at(i).hasElevation())
raw.append(GraphPoint(_distance.at(i), _time.at(i), raw.append(GraphPoint(_distance.at(i), _time.at(i),
_data.at(i).elevation())); _data.at(i).elevation()));
}
return filter(raw, WINDOW_EF); return filter(raw, WINDOW_EF);
} }
Graph Track::speed() const Graph Track::speed() const
{ {
QVector<GraphPoint> raw; Graph raw;
qreal v, ds, dt; qreal v;
if (!_data.size()) if (!_data.size())
return raw; return raw;
raw.append(GraphPoint(_distance.at(0), _time.at(0), 0)); for (int i = 0; i < _data.size(); i++) {
for (int i = 1; i < _data.size(); i++) { if (_outliers.contains(i))
continue;
if (_data.at(i).hasSpeed()) if (_data.at(i).hasSpeed())
v = _data.at(i).speed(); v = _data.at(i).speed();
else if (_data.at(i).hasTimestamp() && _data.at(i-1).hasTimestamp()) { else if (!std::isnan(_speed.at(i)))
dt = _time.at(i) - _time.at(i-1); v = _speed.at(i);
if (!dt) else
continue;
ds = _distance.at(i) - _distance.at(i-1);
v = ds / dt;
} else
continue; continue;
raw.append(GraphPoint(_distance.at(i), _time.at(i), v)); raw.append(GraphPoint(_distance.at(i), _time.at(i), v));
} }
return filter(eliminate(raw, WINDOW_SE), WINDOW_SF); return filter(raw, WINDOW_SF);
} }
Graph Track::heartRate() const Graph Track::heartRate() const
{ {
QVector<GraphPoint> raw; Graph raw;
if (!_data.size()) if (!_data.size())
return raw; return raw;
for (int i = 0; i < _data.count(); i++) for (int i = 0; i < _data.count(); i++) {
if (_outliers.contains(i))
continue;
if (_data.at(i).hasHeartRate()) if (_data.at(i).hasHeartRate())
raw.append(GraphPoint(_distance.at(i), _time.at(i), raw.append(GraphPoint(_distance.at(i), _time.at(i),
_data.at(i).heartRate())); _data.at(i).heartRate()));
}
return filter(eliminate(raw, WINDOW_HE), WINDOW_HF); return filter(raw, WINDOW_HF);
} }
Graph Track::temperature() const Graph Track::temperature() const
{ {
QVector<GraphPoint> raw; Graph raw;
for (int i = 0; i < _data.size(); i++) {
if (_outliers.contains(i))
continue;
for (int i = 0; i < _data.size(); i++)
if (_data.at(i).hasTemperature()) if (_data.at(i).hasTemperature())
raw.append(GraphPoint(_distance.at(i), _time.at(i), raw.append(GraphPoint(_distance.at(i), _time.at(i),
_data.at(i).temperature())); _data.at(i).temperature()));
}
return Graph(raw); return Graph(raw);
} }
qreal Track::distance() const qreal Track::distance() const
{ {
return (_distance.isEmpty()) ? 0 : _distance.last(); return _distance.isEmpty() ? 0 : _distance.last();
} }
qreal Track::time() const qreal Track::time() const
@ -181,3 +201,17 @@ QDateTime Track::date() const
{ {
return (_data.size()) ? _data.first().timestamp() : QDateTime(); return (_data.size()) ? _data.first().timestamp() : QDateTime();
} }
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;
}

View File

@ -2,9 +2,11 @@
#define TRACK_H #define TRACK_H
#include <QVector> #include <QVector>
#include <QSet>
#include <QDateTime> #include <QDateTime>
#include "trackdata.h" #include "trackdata.h"
#include "graph.h" #include "graph.h"
#include "path.h"
class Track class Track
@ -12,7 +14,7 @@ class Track
public: public:
Track(const TrackData &data); Track(const TrackData &data);
const TrackData &track() const {return _data;} Path track() const;
Graph elevation() const; Graph elevation() const;
Graph speed() const; Graph speed() const;
Graph heartRate() const; Graph heartRate() const;
@ -22,12 +24,19 @@ public:
qreal time() const; qreal time() const;
QDateTime date() const; QDateTime date() const;
const QString &name() const {return _data.name();}
const QString &description() const {return _data.description();}
bool isNull() const {return (_data.size() < 2);} bool isNull() const {return (_data.size() < 2);}
private: private:
const TrackData &_data; const TrackData &_data;
QVector<qreal> _distance; QVector<qreal> _distance;
QVector<qreal> _time; QVector<qreal> _time;
QVector<qreal> _speed;
QSet<int> _outliers;
}; };
#endif // TRACK_H #endif // TRACK_H

View File

@ -30,20 +30,20 @@ TrackItem::TrackItem(const Track &track, QGraphicsItem *parent)
: PathItem(parent) : PathItem(parent)
{ {
QPointF p; QPointF p;
const TrackData &t = track.track(); QVector<Coordinates> t = track.track();
Q_ASSERT(t.count() >= 2); Q_ASSERT(t.count() >= 2);
p = t.first().coordinates().toMercator(); p = t.first().toMercator();
_path.moveTo(QPointF(p.x(), -p.y())); _path.moveTo(QPointF(p.x(), -p.y()));
for (int i = 1; i < t.size(); i++) { for (int i = 1; i < t.size(); i++) {
p = t.at(i).coordinates().toMercator(); p = t.at(i).toMercator();
_path.lineTo(QPointF(p.x(), -p.y())); _path.lineTo(QPointF(p.x(), -p.y()));
} }
updateShape(); updateShape();
_name = t.name(); _name = track.name();
_desc = t.description(); _desc = track.description();
_date = track.date(); _date = track.date();
_distance = track.distance(); _distance = track.distance();
_time = track.time(); _time = track.time();