mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 08:08:42 +00:00
All the basic ride metrics now use the RideMetric interface.
This commit is contained in:
@@ -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());
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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%\" ";
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -20,7 +20,6 @@ RC_FILE = images/gc.icns
|
||||
# Input
|
||||
HEADERS += \
|
||||
AllPlot.h \
|
||||
BasicRideMetrics.h \
|
||||
ChooseCyclistDialog.h \
|
||||
CpintPlot.h \
|
||||
CsvRideFile.h \
|
||||
|
||||
Reference in New Issue
Block a user