Separated out BikeScore as a RideMetric.

This commit is contained in:
Sean C. Rhea
2008-02-21 00:51:50 +00:00
parent aba9a29a43
commit d16330134d
6 changed files with 191 additions and 89 deletions

View File

@@ -12,9 +12,11 @@ class WorkoutTime : public RideMetric {
QString name() const { return "workout_time"; }
QString units(bool) const { return "seconds"; }
double value(bool) const { return seconds; }
void compute(const RawFile *raw, const Zones *, int) {
void compute(const RawFile *raw, const Zones *, int,
const QHash<QString,RideMetric*> &) {
seconds = raw->points.back()->secs;
}
bool canAggregate() const { return true; }
void aggregateWith(RideMetric *other) { seconds += other->value(true); }
RideMetric *clone() const { return new WorkoutTime(*this); }
};
@@ -38,6 +40,7 @@ class TimeRiding : public PointwiseRideMetric {
if ((point->mph > 0.0) || (point->cad > 0.0))
secsMovingOrPedaling += secsDelta;
}
bool canAggregate() const { return true; }
void aggregateWith(RideMetric *other) {
secsMovingOrPedaling += other->value(true);
}
@@ -60,9 +63,11 @@ class TotalDistance : public RideMetric {
double value(bool metric) const {
return metric ? (miles / MILES_PER_KM) : miles;
}
void compute(const RawFile *raw, const Zones *, int) {
void compute(const RawFile *raw, const Zones *, int,
const QHash<QString,RideMetric*> &) {
miles = raw->points.back()->miles;
}
bool canAggregate() const { return true; }
void aggregateWith(RideMetric *other) { miles += other->value(false); }
RideMetric *clone() const { return new TotalDistance(*this); }
};
@@ -86,6 +91,7 @@ class TotalWork : public PointwiseRideMetric {
if (point->watts >= 0.0)
joules += point->watts * secsDelta;
}
bool canAggregate() const { return true; }
void aggregateWith(RideMetric *other) {
assert(name() == other->name());
TotalWork *tw = dynamic_cast<TotalWork*>(other);
@@ -113,14 +119,16 @@ class AvgSpeed : public PointwiseRideMetric {
double mph = miles / secsMoving * 3600.0;
return metric ? (mph / MILES_PER_KM) : mph;
}
void compute(const RawFile *raw, const Zones *zones, int zoneRange) {
PointwiseRideMetric::compute(raw, zones, zoneRange);
void compute(const RawFile *raw, const Zones *zones, int zoneRange,
const QHash<QString,RideMetric*> &deps) {
PointwiseRideMetric::compute(raw, zones, zoneRange, deps);
miles = raw->points.back()->miles;
}
void perPoint(const RawFilePoint *point, double secsDelta,
const RawFile *, const Zones *, int) {
if (point->mph > 0.0) secsMoving += secsDelta;
}
bool canAggregate() const { return true; }
void aggregateWith(RideMetric *other) {
assert(name() == other->name());
AvgSpeed *as = dynamic_cast<AvgSpeed*>(other);

107
src/gui/BikeScore.cpp Normal file
View File

@@ -0,0 +1,107 @@
#include "RideMetric.h"
#include "Zones.h"
#include <math.h>
class XPower : public RideMetric {
double xpower;
double secs;
friend class RelativeIntensity;
public:
XPower() : xpower(0.0), secs(0) {}
QString name() const { return "skiba_xpower"; }
QString units(bool) const { return "watts"; }
double value(bool) const { return xpower; }
void compute(const RawFile *raw, const Zones *, int,
const QHash<QString,RideMetric*> &) {
double total = 0.0;
int count = 0;
// we're interested in a 25 second moving average
int movingAvgLen = (int) ceil(25000 / raw->rec_int_ms);
double *movingAvgWatts = new double[movingAvgLen];
int j = 0; // an index for the moving average function
QListIterator<RawFilePoint*> i(raw->points);
double secsDelta = raw->rec_int_ms / 1000.0;
while (i.hasNext()) {
const RawFilePoint *point = i.next();
if (point->watts >= 0.0) {
secs += secsDelta;
// 25 second moving average
// populate the array in the first 25 seconds
if (secs <= 25)
movingAvgWatts[j] = point->watts;
else {
// add the most recent value, and calculate average
movingAvgWatts[j] = point->watts;
double avg = 0.0;
for (int jj = 0; jj < movingAvgLen; jj++)
avg += movingAvgWatts[jj];
total += pow(avg / movingAvgLen, 4.0);
count++;
}
j = (j + 1) % movingAvgLen;
}
}
xpower = pow(total / count, 0.25);
delete [] movingAvgWatts;
}
RideMetric *clone() const { return new XPower(*this); }
};
class RelativeIntensity : public RideMetric {
double reli;
public:
RelativeIntensity() : reli(0.0) {}
QString name() const { return "skiba_relative_intensity"; }
QString units(bool) const { return ""; }
double value(bool) const { return reli; }
void compute(const RawFile *, const Zones *zones, int zoneRange,
const QHash<QString,RideMetric*> &deps) {
if (zones) {
assert(deps.contains("skiba_xpower"));
RideMetric *xp = deps.value("skiba_xpower");
assert(xp);
reli = xp->value(true) / zones->getFTP(zoneRange);
}
}
RideMetric *clone() const { return new RelativeIntensity(*this); }
};
class BikeScore : public RideMetric {
double score;
public:
BikeScore() : score(0.0) {}
QString name() const { return "skiba_bike_score"; }
QString units(bool) const { return ""; }
double value(bool) const { return score; }
void compute(const RawFile *raw, const Zones *, int,
const QHash<QString,RideMetric*> &deps) {
assert(deps.contains("skiba_relative_intensity"));
RideMetric *ri = deps.value("skiba_relative_intensity");
assert(ri);
score = ((raw->points.back()->secs / 60) / 60)
* (pow(ri->value(true), 2)) * 100.0;
}
RideMetric *clone() const { return new BikeScore(*this); }
};
static bool addAllThree() {
RideMetricFactory::instance().addMetric(XPower());
QVector<QString> deps;
deps.append("skiba_xpower");
RideMetricFactory::instance().addMetric(RelativeIntensity(), &deps);
deps.clear();
deps.append("skiba_relative_intensity");
RideMetricFactory::instance().addMetric(BikeScore(), &deps);
return true;
}
static bool allThreeAdded = addAllThree();

View File

@@ -155,6 +155,14 @@ static const char *metricsXml =
" <metric name=\"average_cad\" display_name=\"Cadence\"\n"
" precision=\"0\"/>\n"
" </metric_group>\n"
" <metric_group name=\"Dr. Skiba's BikeScore\">\n"
" <metric name=\"skiba_xpower\" display_name=\"xPower\"\n"
" precision=\"0\"/>\n"
" <metric name=\"skiba_relative_intensity\"\n"
" display_name=\"Relative Intensity\" precision=\"3\"/>\n"
" <metric name=\"skiba_bike_score\" display_name=\"BikeScore\"\n"
" precision=\"2\"/>\n"
" </metric_group>\n"
"</metrics>\n";
QString
@@ -193,13 +201,24 @@ RideItem::htmlSummary()
QVariant unit = settings.value(GC_UNIT);
const RideMetricFactory &factory = RideMetricFactory::instance();
RideMetricFactory::RideMetricIter metricIter(factory.metrics());
while (metricIter.hasNext()) {
metricIter.next();
QString name = metricIter.key();
RideMetric *metric = factory.newMetric(name);
metric->compute(raw, zones, zone_range);
metrics.insert(name, metric);
QSet<QString> todo;
for (int i = 0; i < factory.metricCount(); ++i)
todo.insert(factory.metricName(i));
while (!todo.empty()) {
QMutableSetIterator<QString> i(todo);
later:
while (i.hasNext()) {
const QString &name = i.next();
const QVector<QString> &deps = factory.dependencies(name);
for (int j = 0; j < deps.size(); ++j)
if (!metrics.contains(deps[j]))
goto later;
RideMetric *metric = factory.newMetric(name);
metric->compute(raw, zones, zone_range, metrics);
metrics.insert(name, metric);
i.remove();
}
}
double secs_watts = 0.0;
@@ -214,22 +233,8 @@ RideItem::htmlSummary()
double int_secs_hr = 0.0;
double int_max_power = 0.0;
// for computing "Bike Score"
// we're interested in a 25 second moving average
double moving_avg_len = 25000 / raw->rec_int_ms;
double moving_avg_watts[int(ceil(moving_avg_len))];
double fourthPower_watts = 0.0;
double fourth_counter = 0.0;
double int_fourth_watts = 0.0;
double int_fourth_counter = 0.0;
double xpower = 0.0;
double relative_intensity = 0.0;
double bike_score = 0.0;
double time_start, time_end, mile_start, mile_end, int_dur;
int j = 0; // an index for the moving average function
QListIterator<RawFilePoint*> i(raw->points);
while (i.hasNext()) {
RawFilePoint *point = i.next();
@@ -257,25 +262,6 @@ RideItem::htmlSummary()
}
if (point->watts >= 0.0) {
secs_watts += secs_delta;
// 25 second moving average
// populate the array in the first 25 seconds
if (secs_watts <= 25)
moving_avg_watts[j] = point->watts;
else {
// add the most recent value, and calculate average
moving_avg_watts[j] = point->watts;
double mov_avg = 0.0;
for(int jj = 0;jj < moving_avg_len; jj++)
mov_avg += moving_avg_watts[jj];
double current_fourth = pow(mov_avg / moving_avg_len, 4.0);
fourthPower_watts += current_fourth;
fourth_counter++;
int_fourth_watts += current_fourth;
int_fourth_counter++;
}
j = (j + 1) % ((int)(moving_avg_len));
int_watts_sum += point->watts * secs_delta;
if (point->watts > int_max_power)
int_max_power = point->watts;
@@ -302,13 +288,6 @@ RideItem::htmlSummary()
int_hr_sum, int_cad_sum, int_mph_sum,
int_secs_hr, int_max_power, int_dur);
if (zones) {
xpower = pow(int_fourth_watts/int_fourth_counter,0.25);
relative_intensity = xpower / ((float) zones->getFTP(zone_range));
bike_score = ((raw->points.back()->secs / 60) / 60)
* (pow(relative_intensity, 2)) * 100.0;
}
summary += "<p>";
bool metricUnits = (unit.toString() == "Metric");
@@ -353,9 +332,10 @@ RideItem::htmlSummary()
summary += s;
}
else {
QString s("<tr><td>%1 (%2):</td><td "
"align=\"right\">%3</td></tr>");
s = s.arg(displayName).arg(m->units(metricUnits));
QString s = "<tr><td>" + displayName;
if (m->units(metricUnits) != "")
s += " (" + m->units(metricUnits) + ")";
s += ":</td><td align=\"right\">%1</td></tr>";
if (precision == 0)
s = s.arg((unsigned) round(m->value(metricUnits)));
else
@@ -368,22 +348,6 @@ RideItem::htmlSummary()
summary += "</tr></table>";
}
if (xpower != 0) {
summary += "<h2>Dr. Skiba's BikeScore</h2>";
summary += "<table align=\"center\" width=\"40%\" ";
summary += "cellspacing=0 border=0>";
summary += QString("<tr><td>xPower:</td>"
"<td align=\"left\">%1</td></tr>")
.arg((unsigned) xpower);
summary += QString("<tr><td>Relative Intensity:</td>"
"<td align=\"left\">%1</td></tr>")
.arg(relative_intensity, 0, 'f', 3);
summary += QString("<tr><td>Bike Score: </td>"
"<td align=\"left\">%1</td></tr>")
.arg(bike_score, 0, 'f', 2);
summary += "</table>";
}
if (zones) {
summary += "<h2>Power Zones</h2>";
summary += zones->summarize(zone_range, time_in_zone, num_zones);

View File

@@ -2,4 +2,5 @@
#include "RideMetric.h"
RideMetricFactory *RideMetricFactory::_instance;
QVector<QString> RideMetricFactory::noDeps;

View File

@@ -4,6 +4,7 @@
#include <QHash>
#include <QString>
#include <QVector>
#include <assert.h>
#include "RawFile.h"
@@ -16,13 +17,20 @@ struct RideMetric {
virtual QString units(bool metric) const = 0;
virtual double value(bool metric) const = 0;
virtual void compute(const RawFile *raw,
const Zones *zones, int zoneRange) = 0;
virtual void aggregateWith(RideMetric *other) = 0;
const Zones *zones,
int zoneRange,
const QHash<QString,RideMetric*> &deps) = 0;
virtual bool canAggregate() const { return false; }
virtual void aggregateWith(RideMetric *other) {
(void) other;
assert(false);
}
virtual RideMetric *clone() const = 0;
};
struct PointwiseRideMetric : public RideMetric {
void compute(const RawFile *raw, const Zones *zones, int zoneRange) {
void compute(const RawFile *raw, const Zones *zones, int zoneRange,
const QHash<QString,RideMetric*> &) {
QListIterator<RawFilePoint*> i(raw->points);
double secsDelta = raw->rec_int_ms / 1000.0;
while (i.hasNext()) {
@@ -59,15 +67,12 @@ class AvgRideMetric : public PointwiseRideMetric {
class RideMetricFactory {
public:
typedef QHash<QString,RideMetric*> RideMetricMap;
typedef QHashIterator<QString,RideMetric*> RideMetricIter;
private:
static RideMetricFactory *_instance;
RideMetricMap _metrics;
static QVector<QString> noDeps;
QVector<QString> metricNames;
QHash<QString,RideMetric*> metrics;
QHash<QString,QVector<QString>*> dependencyMap;
RideMetricFactory() {}
RideMetricFactory(const RideMetricFactory &other);
@@ -81,20 +86,36 @@ class RideMetricFactory {
return *_instance;
}
const RideMetricMap &metrics() const { return _metrics; }
int metricCount() const { return metricNames.size(); }
const QString &metricName(int i) const { return metricNames[i]; }
RideMetric *newMetric(const QString &name) const {
if (!_metrics.contains(name))
return NULL;
RideMetric *metric = _metrics.value(name);
return metric->clone();
assert(metrics.contains(name));
return metrics.value(name)->clone();
}
bool addMetric(const RideMetric &metric) {
assert(!_metrics.contains(metric.name()));
_metrics.insert(metric.name(), metric.clone());
bool addMetric(const RideMetric &metric,
const QVector<QString> *deps = NULL) {
assert(!metrics.contains(metric.name()));
metrics.insert(metric.name(), metric.clone());
metricNames.append(metric.name());
if (deps) {
QVector<QString> *copy = new QVector<QString>;
for (int i = 0; i < deps->size(); ++i) {
assert(metrics.contains((*deps)[i]));
copy->append((*deps)[i]);
}
dependencyMap.insert(metric.name(), copy);
}
return true;
}
const QVector<QString> &dependencies(const QString &name) const {
assert(metrics.contains(name));
QVector<QString> *result = dependencyMap.value(name);
return result ? *result : noDeps;
}
};
#endif // _GC_RideMetric_h

View File

@@ -44,6 +44,7 @@ HEADERS += \
SOURCES += \
AllPlot.cpp \
BasicRideMetrics.cpp \
BikeScore.cpp \
ChooseCyclistDialog.cpp \
CpintPlot.cpp \
CsvRideFile.cpp \