All the basic ride metrics now use the RideMetric interface.

This commit is contained in:
Sean C. Rhea
2008-02-20 19:24:20 +00:00
parent 5dc93dccc8
commit aba9a29a43
7 changed files with 296 additions and 177 deletions

View File

@@ -1,8 +1,192 @@
#include "BasicRideMetrics.h"
#include "RideMetric.h"
bool totalDistanceAdded =
RideMetricFactory::instance().addMetric(TotalDistanceRideMetric());
bool totalWorkAdded =
RideMetricFactory::instance().addMetric(TotalWorkRideMetric());
#define MILES_PER_KM 0.62137119
class WorkoutTime : public RideMetric {
double seconds;
public:
WorkoutTime() : seconds(0.0) {}
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) {
seconds = raw->points.back()->secs;
}
void aggregateWith(RideMetric *other) { seconds += other->value(true); }
RideMetric *clone() const { return new WorkoutTime(*this); }
};
static bool workoutTimeAdded =
RideMetricFactory::instance().addMetric(WorkoutTime());
//////////////////////////////////////////////////////////////////////////////
class TimeRiding : public PointwiseRideMetric {
double secsMovingOrPedaling;
public:
TimeRiding() : secsMovingOrPedaling(0.0) {}
QString name() const { return "time_riding"; }
QString units(bool) const { return "seconds"; }
double value(bool) const { return secsMovingOrPedaling; }
void perPoint(const RawFilePoint *point, double secsDelta,
const RawFile *, const Zones *, int) {
if ((point->mph > 0.0) || (point->cad > 0.0))
secsMovingOrPedaling += secsDelta;
}
void aggregateWith(RideMetric *other) {
secsMovingOrPedaling += other->value(true);
}
RideMetric *clone() const { return new TimeRiding(*this); }
};
static bool timeRidingAdded =
RideMetricFactory::instance().addMetric(TimeRiding());
//////////////////////////////////////////////////////////////////////////////
class TotalDistance : public RideMetric {
double miles;
public:
TotalDistance() : miles(0.0) {}
QString name() const { return "total_distance"; }
QString units(bool metric) const { return metric ? "km" : "miles"; }
double value(bool metric) const {
return metric ? (miles / MILES_PER_KM) : miles;
}
void compute(const RawFile *raw, const Zones *, int) {
miles = raw->points.back()->miles;
}
void aggregateWith(RideMetric *other) { miles += other->value(false); }
RideMetric *clone() const { return new TotalDistance(*this); }
};
static bool totalDistanceAdded =
RideMetricFactory::instance().addMetric(TotalDistance());
//////////////////////////////////////////////////////////////////////////////
class TotalWork : public PointwiseRideMetric {
double joules;
public:
TotalWork() : joules(0.0) {}
QString name() const { return "total_work"; }
QString units(bool) const { return "kJ"; }
double value(bool) const { return joules / 1000.0; }
void perPoint(const RawFilePoint *point, double secsDelta,
const RawFile *, const Zones *, int) {
if (point->watts >= 0.0)
joules += point->watts * secsDelta;
}
void aggregateWith(RideMetric *other) {
assert(name() == other->name());
TotalWork *tw = dynamic_cast<TotalWork*>(other);
joules += tw->joules;
}
RideMetric *clone() const { return new TotalWork(*this); }
};
static bool totalWorkAdded =
RideMetricFactory::instance().addMetric(TotalWork());
//////////////////////////////////////////////////////////////////////////////
class AvgSpeed : public PointwiseRideMetric {
double secsMoving;
double miles;
public:
AvgSpeed() : secsMoving(0.0), miles(0.0) {}
QString name() const { return "average_speed"; }
QString units(bool metric) const { return metric ? "kph" : "mph"; }
double value(bool metric) const {
if (secsMoving == 0.0) return 0.0;
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);
miles = raw->points.back()->miles;
}
void perPoint(const RawFilePoint *point, double secsDelta,
const RawFile *, const Zones *, int) {
if (point->mph > 0.0) secsMoving += secsDelta;
}
void aggregateWith(RideMetric *other) {
assert(name() == other->name());
AvgSpeed *as = dynamic_cast<AvgSpeed*>(other);
secsMoving += as->secsMoving;
miles += as->miles;
}
RideMetric *clone() const { return new AvgSpeed(*this); }
};
static bool avgSpeedAdded =
RideMetricFactory::instance().addMetric(AvgSpeed());
//////////////////////////////////////////////////////////////////////////////
struct AvgPower : public AvgRideMetric {
QString name() const { return "average_power"; }
QString units(bool) const { return "watts"; }
void perPoint(const RawFilePoint *point, double,
const RawFile *, const Zones *, int) {
if (point->watts >= 0.0) {
total += point->watts;
++count;
}
}
RideMetric *clone() const { return new AvgPower(*this); }
};
static bool avgPowerAdded =
RideMetricFactory::instance().addMetric(AvgPower());
//////////////////////////////////////////////////////////////////////////////
struct AvgHeartRate : public AvgRideMetric {
QString name() const { return "average_hr"; }
QString units(bool) const { return "bpm"; }
void perPoint(const RawFilePoint *point, double,
const RawFile *, const Zones *, int) {
if (point->hr > 0) {
total += point->hr;
++count;
}
}
RideMetric *clone() const { return new AvgHeartRate(*this); }
};
static bool avgHeartRateAdded =
RideMetricFactory::instance().addMetric(AvgHeartRate());
//////////////////////////////////////////////////////////////////////////////
struct AvgCadence : public AvgRideMetric {
QString name() const { return "average_cad"; }
QString units(bool) const { return "bpm"; }
void perPoint(const RawFilePoint *point, double,
const RawFile *, const Zones *, int) {
if (point->cad > 0) {
total += point->cad;
++count;
}
}
RideMetric *clone() const { return new AvgCadence(*this); }
};
static bool avgCadenceAdded =
RideMetricFactory::instance().addMetric(AvgCadence());

View File

@@ -1,62 +0,0 @@
#ifndef _GC_BasicRideMetrics_h
#define _GC_BasicRideMetrics_h 1
#include "RideMetric.h"
#include "RawFile.h"
class TotalDistanceRideMetric : public RideMetric {
double miles;
public:
TotalDistanceRideMetric() : miles(0.0) {}
QString name() const { return "total_distance"; }
QString units(bool metric) const { return metric ? "km" : "miles"; }
double value(bool metric) const {
return metric ? (miles / 0.62137119) : miles;
}
void compute(const RawFile *raw, const Zones *zones, int zoneRange) {
(void) zones;
(void) zoneRange;
miles = raw->points.back()->miles;
}
void aggregateWith(RideMetric *other) {
assert(other->name() == name());
TotalDistanceRideMetric *m = (TotalDistanceRideMetric*) other;
miles += m->miles;
}
RideMetric *clone() const { return new TotalDistanceRideMetric(*this); }
};
class TotalWorkRideMetric : public RideMetric {
double kJ;
public:
TotalWorkRideMetric() : kJ(0.0) {}
QString name() const { return "total_work"; }
QString units(bool metric) const { (void) metric; return "kJ"; }
double value(bool metric) const { (void) metric; return kJ; }
void compute(const RawFile *raw, const Zones *zones, int zoneRange) {
(void) zones;
(void) zoneRange;
QListIterator<RawFilePoint*> i(raw->points);
double secs_delta = raw->rec_int_ms / 1000.0;
while (i.hasNext()) {
RawFilePoint *point = i.next();
if (point->watts >= 0.0)
kJ += point->watts * secs_delta;
}
kJ /= 1000.0;
}
void aggregateWith(RideMetric *other) {
assert(other->name() == name());
TotalWorkRideMetric *m = (TotalWorkRideMetric*) other;
kJ += m->kJ;
}
RideMetric *clone() const { return new TotalWorkRideMetric(*this); }
};
#endif // _GC_BasicRideMetrics_h

View File

@@ -603,10 +603,12 @@ MainWindow::rideSelected()
wstart = wstart.addDays(Qt::Monday - wstart.dayOfWeek());
assert(wstart.dayOfWeek() == Qt::Monday);
QDate wend = wstart.addDays(7);
double weeklySeconds = 0.0;
const RideMetricFactory &factory = RideMetricFactory::instance();
RideMetric *weeklySeconds = factory.newMetric("time_riding");
assert(weeklySeconds);
RideMetric *weeklyDistance = factory.newMetric("total_distance");
double weeklyWork = 0.0;
assert(weeklyDistance);
RideMetric *weeklyWork = factory.newMetric("total_work");
int zone_range = -1;
double *time_in_zone = NULL;
@@ -618,9 +620,17 @@ MainWindow::rideSelected()
RideItem *item = (RideItem*) allRides->child(i);
if ((item->dateTime.date() >= wstart)
&& (item->dateTime.date() < wend)) {
weeklySeconds += item->secsMovingOrPedaling();
weeklyDistance->aggregateWith(item->metrics.value(weeklyDistance->name()));
weeklyWork += item->totalWork();
RideMetric *m;
item->htmlSummary(); // compute metrics
m = item->metrics.value(weeklySeconds->name());
assert(m);
weeklySeconds->aggregateWith(m);
m = item->metrics.value(weeklyDistance->name());
assert(m);
weeklyDistance->aggregateWith(m);
m = item->metrics.value(weeklyWork->name());
assert(m);
weeklyWork->aggregateWith(m);
if (zones) {
if (zone_range == -1) {
zone_range = item->zoneRange();
@@ -639,7 +649,7 @@ MainWindow::rideSelected()
}
}
int minutes = ((int) round(weeklySeconds)) / 60;
int minutes = ((int) round(weeklySeconds->value(true))) / 60;
int hours = (int) minutes / 60;
minutes %= 60;
@@ -671,8 +681,8 @@ MainWindow::rideSelected()
.arg(hours)
.arg(minutes, 2, 10, QLatin1Char('0'))
.arg((unsigned) round(weeklyDistance->value(true)))
.arg((unsigned) round(weeklyWork))
.arg((unsigned) round(weeklyWork / 7));
.arg((unsigned) round(weeklyWork->value(true)))
.arg((unsigned) round(weeklyWork->value(true) / 7));
}
else {
summary = tr(
@@ -697,8 +707,8 @@ MainWindow::rideSelected()
.arg(hours)
.arg(minutes, 2, 10, QLatin1Char('0'))
.arg((unsigned) round(weeklyDistance->value(false)))
.arg((unsigned) round(weeklyWork))
.arg((unsigned) round(weeklyWork / 7));
.arg((unsigned) round(weeklyWork->value(true)))
.arg((unsigned) round(weeklyWork->value(true) / 7));
}
if (zone_range != -1) {
summary += "<h2>Power Zones</h2>";
@@ -713,6 +723,7 @@ MainWindow::rideSelected()
summary += "</center>";
delete weeklyDistance;
delete weeklySeconds;
// TODO: add daily breakdown

View File

@@ -107,20 +107,6 @@ static void summarize(QString &intervals,
int_max_power = 0.0;
}
double RideItem::secsMovingOrPedaling()
{
if (summary.isEmpty())
htmlSummary();
return secs_moving_or_pedaling;
}
double RideItem::totalWork()
{
if (summary.isEmpty())
htmlSummary();
return total_work;
}
int RideItem::zoneRange()
{
if (summary.isEmpty())
@@ -150,11 +136,25 @@ double RideItem::timeInZone(int zone)
static const char *metricsXml =
"<metrics>\n"
" <metric_group name=\"Totals\">\n"
" <metric name=\"workout_time\" display_name=\"Workout time\"\n"
" precision=\"0\"/>\n"
" <metric name=\"time_riding\" display_name=\"Time riding\"\n"
" precision=\"0\"/>\n"
" <metric name=\"total_distance\" display_name=\"Distance\"\n"
" precision=\"1\"/>\n"
" <metric name=\"total_work\" display_name=\"Work\"\n"
" precision=\"0\"/>\n"
" </metric_group>\n"
" <metric_group name=\"Averages\">\n"
" <metric name=\"average_speed\" display_name=\"Speed\"\n"
" precision=\"1\"/>\n"
" <metric name=\"average_power\" display_name=\"Power\"\n"
" precision=\"0\"/>\n"
" <metric name=\"average_hr\" display_name=\"Heart rate\"\n"
" precision=\"0\"/>\n"
" <metric name=\"average_cad\" display_name=\"Cadence\"\n"
" precision=\"0\"/>\n"
" </metric_group>\n"
"</metrics>\n";
QString
@@ -202,15 +202,7 @@ RideItem::htmlSummary()
metrics.insert(name, metric);
}
secs_moving_or_pedaling = 0.0;
double secs_moving = 0.0;
double total_watts = 0.0;
double secs_watts = 0.0;
double avg_watts = 0.0;
double secs_hr = 0.0;
double total_hr = 0.0;
double secs_cad = 0.0;
double total_cad = 0.0;
QString intervals = "";
int interval_count = 0;
@@ -261,13 +253,9 @@ RideItem::htmlSummary()
}
if ((point->mph > 0.0) || (point->cad > 0.0)) {
secs_moving_or_pedaling += secs_delta;
int_dur += secs_delta;
}
if (point->mph > 0.0)
secs_moving += secs_delta;
if (point->watts >= 0.0) {
total_watts += point->watts * secs_delta;
secs_watts += secs_delta;
// 25 second moving average
@@ -298,19 +286,13 @@ RideItem::htmlSummary()
}
}
if (point->hr > 0) {
total_hr += point->hr * secs_delta;
secs_hr += secs_delta;
int_hr_sum += point->hr * secs_delta;
int_secs_hr += secs_delta;
}
if (point->cad > 0) {
total_cad += point->cad * secs_delta;
secs_cad += secs_delta;
if (point->cad > 0)
int_cad_sum += point->cad * secs_delta;
}
if (point->mph >= 0) {
if (point->mph >= 0)
int_mph_sum += point->mph * secs_delta;
}
mile_end = point->miles;
time_end = point->secs + secs_delta;
@@ -320,9 +302,6 @@ RideItem::htmlSummary()
int_hr_sum, int_cad_sum, int_mph_sum,
int_secs_hr, int_max_power, int_dur);
avg_watts = (secs_watts == 0.0) ? 0.0
: round(total_watts / secs_watts);
if (zones) {
xpower = pow(int_fourth_watts/int_fourth_counter,0.25);
relative_intensity = xpower / ((float) zones->getFTP(zone_range));
@@ -330,23 +309,12 @@ RideItem::htmlSummary()
* (pow(relative_intensity, 2)) * 100.0;
}
total_work = total_watts / 1000.0;
summary += "<p>";
summary += "<table align=\"center\" width=\"90%\" border=0>";
summary += "<tr><td align=\"center\"><h2>Totals</h2></td>";
summary += "<td align=\"center\"><h2>Averages</h2></td></tr>";
summary += "<tr><td>";
summary += "<table align=\"center\" width=\"70%\" border=0>";
summary += "<tr><td>Workout time:</td><td align=\"right\">" +
time_to_string(raw->points.back()->secs);
summary += "<tr><td>Time riding:</td><td align=\"right\">" +
time_to_string(secs_moving_or_pedaling) + "</td></tr>";
bool metricUnits = (unit.toString() == "Metric");
QDomDocument doc;
{
QDomDocument doc;
QString err;
int errLine, errCol;
if (!doc.setContent(QString(metricsXml), &err, &errLine, &errCol)){
@@ -354,13 +322,19 @@ RideItem::htmlSummary()
err.toAscii().constData(), errLine, errCol);
assert(false);
}
QDomNodeList groups = doc.elementsByTagName("metric_group");
assert(groups.size() == 1); // for now...
QDomElement group = groups.at(0).toElement();
}
QDomNodeList groups = doc.elementsByTagName("metric_group");
for (int groupNum = 0; groupNum < groups.size(); ++groupNum) {
QDomElement group = groups.at(groupNum).toElement();
assert(!group.isNull());
QString groupName = group.attribute("name");
assert(groupName.length() > 0);
assert(groupName == "Totals"); // for now...
if (groupNum % 2 == 0)
summary += "<table border=0 cellspacing=10><tr>";
summary += "<td align=\"center\" width=\"45%\"><table>"
"<tr><td align=\"center\" colspan=2><h2>%1</h2></td></tr>";
summary = summary.arg(groupName);
QDomNodeList metricsList = group.childNodes();
for (int i = 0; i < metricsList.size(); ++i) {
QDomElement metric = metricsList.at(i).toElement();
@@ -370,49 +344,30 @@ RideItem::htmlSummary()
assert(name.length() > 0);
assert(displayName.length() > 0);
const RideMetric *m = metrics.value(name);
QString s("<tr><td>%1 (%2):</td><td "
"align=\"right\">%3</td></tr>");
s = s.arg(displayName).arg(m->units(metricUnits));
if (precision == 0)
s = s.arg((unsigned) round(m->value(metricUnits)));
else
s = s.arg(m->value(metricUnits), 0, 'f', precision);
summary += s;
assert(m);
if (m->units(metricUnits) == "seconds") {
QString s("<tr><td>%1:</td><td "
"align=\"right\">%2</td></tr>");
s = s.arg(displayName);
s = s.arg(time_to_string(m->value(metricUnits)));
summary += s;
}
else {
QString s("<tr><td>%1 (%2):</td><td "
"align=\"right\">%3</td></tr>");
s = s.arg(displayName).arg(m->units(metricUnits));
if (precision == 0)
s = s.arg((unsigned) round(m->value(metricUnits)));
else
s = s.arg(m->value(metricUnits), 0, 'f', precision);
summary += s;
}
}
summary += "</table></td>";
if ((groupNum % 2 == 1) || (groupNum == groups.size() - 1))
summary += "</tr></table>";
}
summary += "</table></td><td>";
summary += "<table align=\"center\" width=\"70%\" border=0>";
if (unit.toString() == "Metric") {
summary += QString("<tr><td>Speed (km/h):</td>"
"<td align=\"right\">%1</td></tr>")
.arg(((secs_moving == 0.0) ? 0.0
: ((raw->points.back()->miles * 1.60934)
/ secs_moving * 3600.0)),
0, 'f', 1);
}
else {
summary += QString("<tr><td>Speed (mph):</td>"
"<td align=\"right\">%1</td></tr>")
.arg(((secs_moving == 0.0) ? 0.0
: raw->points.back()->miles / secs_moving * 3600.0),
0, 'f', 1);
}
summary += QString("<tr><td>Power (watts):</td>"
"<td align=\"right\">%1</td></tr>")
.arg((unsigned) avg_watts);
summary +=QString("<tr><td>Heart rate (bpm):</td>"
"<td align=\"right\">%1</td></tr>")
.arg((unsigned) ((secs_hr == 0.0) ? 0.0
: round(total_hr / secs_hr)));
summary += QString("<tr><td>Cadence (rpm):</td>"
"<td align=\"right\">%1</td></tr>")
.arg((unsigned) ((secs_cad == 0.0) ? 0.0
: round(total_cad / secs_cad)));
summary += "</table></td></tr>";
summary += "</table>";
if (xpower != 0) {
summary += "<h2>Dr. Skiba's BikeScore</h2>";
summary += "<table align=\"center\" width=\"40%\" ";

View File

@@ -31,9 +31,6 @@ class RideItem : public QTreeWidgetItem {
protected:
double secs_moving_or_pedaling;
double total_distance;
double total_work;
double *time_in_zone;
int num_zones;
int zone_range;
@@ -60,8 +57,6 @@ class RideItem : public QTreeWidgetItem {
~RideItem();
QString htmlSummary();
double secsMovingOrPedaling();
double totalWork();
int zoneRange();
int numZones();

View File

@@ -6,7 +6,8 @@
#include <QString>
#include <assert.h>
class RawFile;
#include "RawFile.h"
class Zones;
struct RideMetric {
@@ -20,6 +21,42 @@ struct RideMetric {
virtual RideMetric *clone() const = 0;
};
struct PointwiseRideMetric : public RideMetric {
void compute(const RawFile *raw, const Zones *zones, int zoneRange) {
QListIterator<RawFilePoint*> i(raw->points);
double secsDelta = raw->rec_int_ms / 1000.0;
while (i.hasNext()) {
const RawFilePoint *point = i.next();
perPoint(point, secsDelta, raw, zones, zoneRange);
}
}
virtual void perPoint(const RawFilePoint *point, double secsDelta,
const RawFile *raw, const Zones *zones,
int zoneRange) = 0;
};
class AvgRideMetric : public PointwiseRideMetric {
protected:
int count;
double total;
public:
AvgRideMetric() : count(0), total(0.0) {}
double value(bool) const {
if (count == 0) return 0.0;
return total / count;
}
void aggregateWith(RideMetric *other) {
assert(name() == other->name());
AvgRideMetric *as = dynamic_cast<AvgRideMetric*>(other);
count += as->count;
total += as->total;
}
};
class RideMetricFactory {
public:

View File

@@ -20,7 +20,6 @@ RC_FILE = images/gc.icns
# Input
HEADERS += \
AllPlot.h \
BasicRideMetrics.h \
ChooseCyclistDialog.h \
CpintPlot.h \
CsvRideFile.h \