From aba9a29a43ed85e8ffb8553c7b51f7ef1e3c726f Mon Sep 17 00:00:00 2001 From: "Sean C. Rhea" Date: Wed, 20 Feb 2008 19:24:20 +0000 Subject: [PATCH] All the basic ride metrics now use the RideMetric interface. --- src/gui/BasicRideMetrics.cpp | 194 ++++++++++++++++++++++++++++++++++- src/gui/BasicRideMetrics.h | 62 ----------- src/gui/MainWindow.cpp | 31 ++++-- src/gui/RideItem.cpp | 141 +++++++++---------------- src/gui/RideItem.h | 5 - src/gui/RideMetric.h | 39 ++++++- src/gui/gui.pro | 1 - 7 files changed, 296 insertions(+), 177 deletions(-) delete mode 100644 src/gui/BasicRideMetrics.h diff --git a/src/gui/BasicRideMetrics.cpp b/src/gui/BasicRideMetrics.cpp index 9ce5868e6..0b8e3fca0 100644 --- a/src/gui/BasicRideMetrics.cpp +++ b/src/gui/BasicRideMetrics.cpp @@ -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(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(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()); diff --git a/src/gui/BasicRideMetrics.h b/src/gui/BasicRideMetrics.h deleted file mode 100644 index 75a362f8c..000000000 --- a/src/gui/BasicRideMetrics.h +++ /dev/null @@ -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 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 - diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index ade86cb51..d188dec08 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -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 += "

Power Zones

"; @@ -713,6 +723,7 @@ MainWindow::rideSelected() summary += ""; delete weeklyDistance; + delete weeklySeconds; // TODO: add daily breakdown diff --git a/src/gui/RideItem.cpp b/src/gui/RideItem.cpp index 893aef967..1d59dc92f 100644 --- a/src/gui/RideItem.cpp +++ b/src/gui/RideItem.cpp @@ -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 = "\n" " \n" + " \n" + " \n" " \n" " \n" " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" "\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 += "

"; - summary += ""; - summary += ""; - summary += ""; - summary += "

Totals

Averages

"; - summary += ""; - summary += ""; 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 += "
Workout time:" + - time_to_string(raw->points.back()->secs); - summary += "
Time riding:" + - time_to_string(secs_moving_or_pedaling) + "
"; + summary += ""; + if ((groupNum % 2 == 1) || (groupNum == groups.size() - 1)) + summary += "
" + ""; + 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(""); - 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(""); + s = s.arg(displayName); + s = s.arg(time_to_string(m->value(metricUnits))); + summary += s; + } + else { + QString s(""); + 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 += "

%1

%1 (%2):%3
%1:%2
%1 (%2):%3
"; } - summary += "
"; - summary += ""; - - if (unit.toString() == "Metric") { - summary += QString("" - "") - .arg(((secs_moving == 0.0) ? 0.0 - : ((raw->points.back()->miles * 1.60934) - / secs_moving * 3600.0)), - 0, 'f', 1); - } - else { - summary += QString("" - "") - .arg(((secs_moving == 0.0) ? 0.0 - : raw->points.back()->miles / secs_moving * 3600.0), - 0, 'f', 1); - } - summary += QString("" - "") - .arg((unsigned) avg_watts); - summary +=QString("" - "") - .arg((unsigned) ((secs_hr == 0.0) ? 0.0 - : round(total_hr / secs_hr))); - summary += QString("" - "") - .arg((unsigned) ((secs_cad == 0.0) ? 0.0 - : round(total_cad / secs_cad))); - summary += "
Speed (km/h):%1
Speed (mph):%1
Power (watts):%1
Heart rate (bpm):%1
Cadence (rpm):%1
"; - summary += ""; - if (xpower != 0) { summary += "

Dr. Skiba's BikeScore

"; summary += " #include -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 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(other); + count += as->count; + total += as->total; + } +}; + class RideMetricFactory { public: diff --git a/src/gui/gui.pro b/src/gui/gui.pro index 4cdcc469e..a58ec70d6 100644 --- a/src/gui/gui.pro +++ b/src/gui/gui.pro @@ -20,7 +20,6 @@ RC_FILE = images/gc.icns # Input HEADERS += \ AllPlot.h \ - BasicRideMetrics.h \ ChooseCyclistDialog.h \ CpintPlot.h \ CsvRideFile.h \