Merge pull request #1185 from amtriathlon/master

Added xPace metric
This commit is contained in:
Mark Liversedge
2014-12-03 15:12:45 +00:00
4 changed files with 74 additions and 9 deletions

3
.gitignore vendored
View File

@@ -15,6 +15,7 @@ bin/
plugins/
resources/
src/debug/
src/release/
qwt/src/debug/
qwt/src/release/
@@ -26,4 +27,6 @@ qwt/src/object_script.libqwt.Release
qwt/src/object_script.libqwtd.Debug
qwt/src/Makefile.Release
qwt/src/Makefile.Debug
qwt/textengines/mathml/debug/
qwt/textengines/mathml/release/
build.pro.user

View File

@@ -113,8 +113,9 @@
// 91 16 Nov 2014 Damien Grauser Do not include values if data not present in TimeInZone and HRTimeInZone
// 92 21 Nov 2014 Mark Liversedge Added Watts:RPE ratio
// 93 26 Nov 2014 Mark Liversedge Added Min, Max, Avg SmO2
// 94 02 Dic 2014 Ale Martinez Added xPace
int DBSchemaVersion = 93;
int DBSchemaVersion = 94;
DBAccess::DBAccess(Context* context) : context(context), db(NULL)
{

View File

@@ -19,6 +19,7 @@
#include "RideMetric.h"
#include "PaceZones.h"
#include "Units.h"
#include <math.h>
#include <algorithm>
#include <QApplication>
@@ -32,8 +33,8 @@
// Running Power based on speed and slope
static inline double running_power( double weight, double height,
double speed, double slope,
double distance) {
double speed, double slope=0.0,
double distance=0.0) {
// Aero contribution - probably needs refinement
double cAero = 0.25*0.01*pow(speed, 2)*pow(height,-3);
// Energy Cost of Running according to slope
@@ -45,7 +46,7 @@ static inline double running_power( double weight, double height,
return (cAero + cSlope*eff*(1 - 0.5*speed/8.33) + cKin)*weight*speed;
}
// Lactate Normalized Power, used for GOVSS calculation
// Lactate Normalized Power, used for GOVSS and xPace calculation
class LNP : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(LNP)
double lnp;
@@ -117,7 +118,7 @@ class LNP : public RideMetric {
double slope120 = sumSlope/std::min(count+1, rollingwindowsize120); // slope rolling average
// running power based on 120sec averages
double watts = running_power(weight, height, speed120, slope120, 0.0);// sumSpeed*ride->recIntSecs()); KE contribution disabled
double watts = running_power(weight, height, speed120, slope120); // sumSpeed*ride->recIntSecs()); KE contribution disabled
sumPower += watts;
sumPower -= rollingPower[index30];
@@ -144,6 +145,65 @@ class LNP : public RideMetric {
RideMetric *clone() const { return new LNP(*this); }
};
// xPace: constant Pace which, on flat surface, gives same Lactate Normalized Power
class XPace : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(XPace)
double xPace;
public:
XPace() : xPace(0.0)
{
setSymbol("xPace");
setInternalName("xPace");
}
void initialize() {
setName(tr("xPace"));
setType(RideMetric::Average);
setMetricUnits(tr("min/km"));
setImperialUnits(tr("min/mile"));
setPrecision(1);
setConversion(KM_PER_MILE);
}
void compute(const RideFile *ride, const Zones *, int,
const HrZones *, int,
const QHash<QString,RideMetric*> &deps,
const Context *) {
// xPace only makes sense for running
if (!ride->isRun()) return;
// unconst naughty boy, get athlete's data
RideFile *uride = const_cast<RideFile*>(ride);
double weight = uride->getWeight();
double height = uride->getWeight();
assert(deps.contains("govss_lnp"));
LNP *lnp = dynamic_cast<LNP*>(deps.value("govss_lnp"));
assert(lnp);
double lnp_watts = lnp->value(true);
// search for speed which gives flat power within 0.001watt of LNP
// up to around 10 iterations for speed within 0.01m/s or ~1sec/km
double low = 0.0, high = 10.0, speed;
if (lnp_watts <= 0.0) speed = low;
else if (lnp_watts >= running_power(weight, height, high)) speed = high;
else do {
speed = (low + high)/2.0;
double watts = running_power(weight, height, speed);
if (abs(watts - lnp_watts) < 0.001) break;
else if (watts < lnp_watts) low = speed;
else if (watts > lnp_watts) high = speed;
} while (high - low > 0.01);
// divide by zero or stupidly low pace
if (speed > 0.01) xPace = (1000.0/60.0) / speed;
else xPace = 0.0;
setValue(xPace);
}
RideMetric *clone() const { return new XPace(*this); }
};
// Running Threshold Power based on CV, used for GOVSS calculation
class RTP : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(RTP)
@@ -186,7 +246,7 @@ class RTP : public RideMetric {
cv = zones->getCV(zoneRange);
// Running power at cv on flat surface
double watts = running_power(weight, height, cv/3.6, 0.0, 0.0); //120*cv/3.6); KE contribution disabled
double watts = running_power(weight, height, cv/3.6); //120*cv/3.6); KE contribution disabled
setValue(watts);
}
@@ -285,6 +345,7 @@ static bool addAllGOVSS() {
RideMetricFactory::instance().addMetric(RTP());
QVector<QString> deps;
deps.append("govss_lnp");
RideMetricFactory::instance().addMetric(XPace(), &deps);
deps.append("govss_rtp");
RideMetricFactory::instance().addMetric(IWF(), &deps);
deps.append("govss_iwf");

View File

@@ -558,7 +558,7 @@ RideSummaryWindow::htmlSummary()
s = s.arg(ride->getTag("Temperature", "-"));
} else if (m->internalName() == "Pace") { // pace is mm:ss
} else if (m->internalName() == "Pace" || m->internalName() == "xPace") { // pace is mm:ss
double pace;
bool metricPace = appsettings->value(this, GC_PACE, true).toBool();
@@ -927,7 +927,7 @@ RideSummaryWindow::htmlSummary()
RideMetricPtr m = metrics.value(symbol);
if (!m) continue;
summary += "<td align=\"center\" valign=\"bottom\">" + m->name();
if (m->internalName() == "Pace") { // pace is mm:ss
if (m->internalName() == "Pace" || m->internalName() == "xPace") { // pace is mm:ss
summary += " (" + m->units(metricPace) + ")";
@@ -958,7 +958,7 @@ RideSummaryWindow::htmlSummary()
QString s("<td align=\"center\">%1</td>");
if (m->units(useMetricUnits) == "seconds" || m->units(useMetricUnits) == tr("seconds"))
summary += s.arg(time_to_string(m->value(useMetricUnits)));
else if (m->internalName() == "Pace") { // pace is mm:ss
else if (m->internalName() == "Pace" || m->internalName() == "xPace") { // pace is mm:ss
double pace = m->value(metricPace);
summary += s.arg(QTime(0,0,0,0).addSecs(pace*60).toString("mm:ss"));