Add NP and xPower Derived Data Series

Calculate NP and xPower as a data series so we can
plot on the ride plot.

Have also added 'aPower' which will be coming in the
next few days - altitude adjusted power, which will also
have some associated metrics (a-xPower, a-NP, a-TSS etc).
This commit is contained in:
Mark Liversedge
2013-11-05 17:14:05 +00:00
parent 949335fea8
commit 5cc70c2164
2 changed files with 163 additions and 6 deletions

View File

@@ -41,7 +41,7 @@
RideFile::RideFile(const QDateTime &startTime, double recIntSecs) :
startTime_(startTime), recIntSecs_(recIntSecs),
deviceType_("unknown"), data(NULL), weight_(0),
totalCount(0)
totalCount(0), dstale(true)
{
command = new RideFileCommand(this);
@@ -51,7 +51,7 @@ RideFile::RideFile(const QDateTime &startTime, double recIntSecs) :
totalPoint = new RideFilePoint();
}
RideFile::RideFile() : recIntSecs_(0.0), deviceType_("unknown"), data(NULL), weight_(0), totalCount(0)
RideFile::RideFile() : recIntSecs_(0.0), deviceType_("unknown"), data(NULL), weight_(0), totalCount(0), dstale(true)
{
command = new RideFileCommand(this);
@@ -82,6 +82,7 @@ RideFile::seriesName(SeriesType series)
case RideFile::nm: return QString(tr("Torque"));
case RideFile::watts: return QString(tr("Power"));
case RideFile::xPower: return QString(tr("xPower"));
case RideFile::aPower: return QString(tr("aPower"));
case RideFile::NP: return QString(tr("Normalized Power"));
case RideFile::alt: return QString(tr("Altitude"));
case RideFile::lon: return QString(tr("Longitude"));
@@ -111,6 +112,7 @@ RideFile::unitName(SeriesType series, Context *context)
case RideFile::nm: return QString(tr("N"));
case RideFile::watts: return QString(tr("watts"));
case RideFile::xPower: return QString(tr("watts"));
case RideFile::aPower: return QString(tr("watts"));
case RideFile::NP: return QString(tr("watts"));
case RideFile::alt: return QString(useMetricUnits ? tr("metres") : tr("feet"));
case RideFile::lon: return QString(tr("lon"));
@@ -338,6 +340,9 @@ RideFile *RideFileFactory::openRideFile(Context *context, QFile &file,
result->setTag("Month", result->startTime().toString("MMMM"));
result->setTag("Weekday", result->startTime().toString("ddd"));
// calculate derived data series
result->recalculateDerivedSeries();
DataProcessorFactory::instance().autoProcess(result);
// what data is present - after processor in case 'derived' or adjusted
@@ -637,6 +642,9 @@ RideFilePoint::value(RideFile::SeriesType series) const
case RideFile::temp : return temp; break;
case RideFile::lrbalance : return lrbalance; break;
case RideFile::interval : return interval; break;
case RideFile::NP : return np; break;
case RideFile::xPower : return xp; break;
case RideFile::aPower : return apower; break;
default:
case RideFile::none : break;
@@ -696,6 +704,7 @@ RideFile::decimalsFor(SeriesType series)
case nm : return 2; break;
case watts : return 0; break;
case xPower : return 0; break;
case aPower : return 0; break;
case NP : return 0; break;
case alt : return 3; break;
case lon : return 6; break;
@@ -725,6 +734,7 @@ RideFile::maximumFor(SeriesType series)
case watts : return 2500; break;
case NP : return 2500; break;
case xPower : return 2500; break;
case aPower : return 2500; break;
case alt : return 8850; break; // mt everest is highest point above sea level
case lon : return 180; break;
case lat : return 90; break;
@@ -752,6 +762,7 @@ RideFile::minimumFor(SeriesType series)
case nm : return 0; break;
case watts : return 0; break;
case xPower : return 0; break;
case aPower : return 0; break;
case NP : return 0; break;
case alt : return -413; break; // the Red Sea is lowest land point on earth
case lon : return -180; break;
@@ -798,6 +809,7 @@ void
RideFile::emitSaved()
{
weight_ = 0;
dstale = true;
emit saved();
}
@@ -805,6 +817,7 @@ void
RideFile::emitReverted()
{
weight_ = 0;
dstale = true;
emit reverted();
}
@@ -812,6 +825,7 @@ void
RideFile::emitModified()
{
weight_ = 0;
dstale = true;
emit modified();
}
@@ -876,3 +890,114 @@ RideFile::parseRideFileName(const QString &name, QDateTime *dt)
return true;
}
void
RideFile::recalculateDerivedSeries()
{
// derived data is calculated from the data that is present
// we should set to 0 where we cannot derive since we may
// be called after data is deleted or added
if (dstale == false) return; // we're already up to date
//
// NP Initialisation -- working variables
//
QVector<double> NProlling;
int NProllingwindowsize = 30 / (recIntSecs_ ? recIntSecs_ : 1);
if (NProllingwindowsize > 1) NProlling.resize(NProllingwindowsize);
double NPtotal = 0;
int NPcount = 0;
int NPindex = 0;
double NPsum = 0;
//
// XPower Initialisation -- working variables
//
static const double EPSILON = 0.1;
static const double NEGLIGIBLE = 0.1;
double XPsecsDelta = recIntSecs_ ? recIntSecs_ : 1;
double XPsampsPerWindow = 25.0 / XPsecsDelta;
double XPattenuation = XPsampsPerWindow / (XPsampsPerWindow + XPsecsDelta);
double XPsampleWeight = XPsecsDelta / (XPsampsPerWindow + XPsecsDelta);
double XPlastSecs = 0.0;
double XPweighted = 0.0;
double XPtotal = 0.0;
int XPcount = 0;
foreach(RideFilePoint *p, dataPoints_) {
//
// NP
//
if (dataPresent.watts && NProllingwindowsize > 1) {
dataPresent.np = true;
// sum last 30secs
NPsum += p->watts;
NPsum -= NProlling[NPindex];
NProlling[NPindex] = p->watts;
// running total and count
NPtotal += pow(NPsum/NProllingwindowsize,4); // raise rolling average to 4th power
NPcount ++;
// root for ride so far
if (NPcount && NPcount*recIntSecs_ > 30) {
p->np = pow(NPtotal / (NPcount), 0.25);
} else {
p->np = 0.00f;
}
// move index on/round
NPindex = (NPindex >= NProllingwindowsize-1) ? 0 : NPindex+1;
} else {
p->np = 0.00f;
}
// now the min and max values for NP
if (p->np > maxPoint->np) maxPoint->np = p->np;
if (p->np < minPoint->np) minPoint->np = p->np;
//
// xPower
//
if (dataPresent.watts) {
dataPresent.np = true;
while ((XPweighted > NEGLIGIBLE) && (p->secs > XPlastSecs + XPsecsDelta + EPSILON)) {
XPweighted *= XPattenuation;
XPlastSecs += XPsecsDelta;
XPtotal += pow(XPweighted, 4.0);
XPcount++;
}
XPweighted *= XPattenuation;
XPweighted += XPsampleWeight * p->watts;
XPlastSecs = p->secs;
XPtotal += pow(XPweighted, 4.0);
XPcount++;
p->xp = pow(XPtotal / XPcount, 0.25);
}
// now the min and max values for NP
if (p->xp > maxPoint->xp) maxPoint->xp = p->xp;
if (p->xp < minPoint->xp) minPoint->xp = p->xp;
// aPower
// XXX coming soon.
}
// Averages and Totals
avgPoint->np = NPcount ? (NPtotal / NPcount) : 0;
totalPoint->np = NPtotal;
avgPoint->xp = XPcount ? (XPtotal / XPcount) : 0;
totalPoint->xp = XPtotal;
// and we're done
dstale=false;
}

View File

@@ -54,12 +54,18 @@ class Context; // for context; cyclist, homedir
struct RideFileDataPresent
{
// basic
bool secs, cad, hr, km, kph, nm, watts, alt, lon, lat, headwind, slope, temp, lrbalance, interval;
// derived
bool np,xp,apower;
// whether non-zero data of each field is present
RideFileDataPresent():
secs(false), cad(false), hr(false), km(false),
kph(false), nm(false), watts(false), alt(false), lon(false), lat(false),
headwind(false), slope(false), temp(false), lrbalance(false), interval(false) {}
headwind(false), slope(false), temp(false), lrbalance(false), interval(false),
np(false), xp(false), apower(false) {}
};
struct RideFileInterval
@@ -105,7 +111,7 @@ class RideFile : public QObject // QObject to emit signals
virtual ~RideFile();
// Working with DATASERIES
enum seriestype { secs=0, cad, hr, km, kph, nm, watts, alt, lon, lat, headwind, slope, temp, interval, NP, xPower, vam, wattsKg, lrbalance, none };
enum seriestype { secs=0, cad, hr, km, kph, nm, watts, alt, lon, lat, headwind, slope, temp, interval, NP, xPower, vam, wattsKg, lrbalance, aPower, none };
enum specialValues { noTemp = -255 };
typedef enum seriestype SeriesType;
@@ -133,6 +139,17 @@ class RideFile : public QObject // QObject to emit signals
void appendPoint(const RideFilePoint &);
const QVector<RideFilePoint*> &dataPoints() const { return dataPoints_; }
// recalculate all the derived data series
// might want to move to a factory for these
// at some point, but for now hard coded
//
// YOU MUST ALWAYS CALL THIS BEFORE ACESSING
// THE DERIVED DATA. IT IS REFRESHED ON DEMAND.
// STATE IS MAINTAINED IN 'bool dstale' BELOW
// TO ENSURE IT IS ONLY REFRESHED IF NEEDED
//
void recalculateDerivedSeries();
// Working with DATAPRESENT flags
inline const RideFileDataPresent *areDataPresent() const { return &dataPresent; }
bool isDataPresent(SeriesType series);
@@ -211,10 +228,12 @@ class RideFile : public QObject // QObject to emit signals
void deleted();
protected:
void emitSaved();
void emitReverted();
void emitModified();
private:
QString id_; // global uuid@goldencheetah.org
@@ -240,19 +259,32 @@ class RideFile : public QObject // QObject to emit signals
void updateMin(RideFilePoint* point);
void updateMax(RideFilePoint* point);
void updateAvg(RideFilePoint* point);
bool dstale; // is derived data up to date?
};
struct RideFilePoint
{
// recorded data
double secs, cad, hr, km, kph, nm, watts, alt, lon, lat, headwind, slope, temp, lrbalance;
int interval;
// derived data (we calculate it)
// xPower, normalised power, aPower
double xp, np, apower;
// create blank point
RideFilePoint() : secs(0.0), cad(0.0), hr(0.0), km(0.0), kph(0.0),
nm(0.0), watts(0.0), alt(0.0), lon(0.0), lat(0.0), headwind(0.0), slope(0.0), temp(-255.0), lrbalance(0), interval(0) {}
nm(0.0), watts(0.0), alt(0.0), lon(0.0), lat(0.0), headwind(0.0), slope(0.0), temp(-255.0), lrbalance(0), interval(0), xp(0), np(0), apower(0) {}
// create point supplying all values
RideFilePoint(double secs, double cad, double hr, double km, double kph,
double nm, double watts, double alt, double lon, double lat,
double headwind, double slope, double temp, double lrbalance, int interval) :
secs(secs), cad(cad), hr(hr), km(km), kph(kph), nm(nm),
watts(watts), alt(alt), lon(lon), lat(lat), headwind(headwind), slope(slope), temp(temp), lrbalance(lrbalance), interval(interval) {}
watts(watts), alt(alt), lon(lon), lat(lat), headwind(headwind), slope(slope), temp(temp), lrbalance(lrbalance), interval(interval), xp(0), np(0), apower(0) {}
// get the value via the series type rather than access direct to the values
double value(RideFile::SeriesType series) const;
};