diff --git a/src/gui/BasicRideMetrics.cpp b/src/gui/BasicRideMetrics.cpp index 0b8e3fca0..8792bf960 100644 --- a/src/gui/BasicRideMetrics.cpp +++ b/src/gui/BasicRideMetrics.cpp @@ -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 &) { 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 &) { 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(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 &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(other); diff --git a/src/gui/BikeScore.cpp b/src/gui/BikeScore.cpp new file mode 100644 index 000000000..4df0260ab --- /dev/null +++ b/src/gui/BikeScore.cpp @@ -0,0 +1,107 @@ + +#include "RideMetric.h" +#include "Zones.h" +#include + +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 &) { + 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 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 &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 &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 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(); + diff --git a/src/gui/RideItem.cpp b/src/gui/RideItem.cpp index 1d59dc92f..ef021107d 100644 --- a/src/gui/RideItem.cpp +++ b/src/gui/RideItem.cpp @@ -155,6 +155,14 @@ static const char *metricsXml = " \n" " \n" + " \n" + " \n" + " \n" + " \n" + " \n" "\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 todo; + for (int i = 0; i < factory.metricCount(); ++i) + todo.insert(factory.metricName(i)); + + while (!todo.empty()) { + QMutableSetIterator i(todo); +later: + while (i.hasNext()) { + const QString &name = i.next(); + const QVector &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 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 += "

"; bool metricUnits = (unit.toString() == "Metric"); @@ -353,9 +332,10 @@ RideItem::htmlSummary() summary += s; } else { - QString s("%1 (%2):%3"); - s = s.arg(displayName).arg(m->units(metricUnits)); + QString s = "" + displayName; + if (m->units(metricUnits) != "") + s += " (" + m->units(metricUnits) + ")"; + s += ":%1"; if (precision == 0) s = s.arg((unsigned) round(m->value(metricUnits))); else @@ -368,22 +348,6 @@ RideItem::htmlSummary() summary += ""; } - if (xpower != 0) { - summary += "

Dr. Skiba's BikeScore

"; - summary += "" - "") - .arg((unsigned) xpower); - summary += QString("" - "") - .arg(relative_intensity, 0, 'f', 3); - summary += QString("" - "") - .arg(bike_score, 0, 'f', 2); - summary += "
xPower:%1
Relative Intensity:%1
Bike Score: %1
"; - } - if (zones) { summary += "

Power Zones

"; summary += zones->summarize(zone_range, time_in_zone, num_zones); diff --git a/src/gui/RideMetric.cpp b/src/gui/RideMetric.cpp index 70727870f..a84385afa 100644 --- a/src/gui/RideMetric.cpp +++ b/src/gui/RideMetric.cpp @@ -2,4 +2,5 @@ #include "RideMetric.h" RideMetricFactory *RideMetricFactory::_instance; +QVector RideMetricFactory::noDeps; diff --git a/src/gui/RideMetric.h b/src/gui/RideMetric.h index 66e65423b..60fa92807 100644 --- a/src/gui/RideMetric.h +++ b/src/gui/RideMetric.h @@ -4,6 +4,7 @@ #include #include +#include #include #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 &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 &) { QListIterator 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 RideMetricMap; - typedef QHashIterator RideMetricIter; - - private: - static RideMetricFactory *_instance; - RideMetricMap _metrics; + static QVector noDeps; + + QVector metricNames; + QHash metrics; + QHash*> 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 *deps = NULL) { + assert(!metrics.contains(metric.name())); + metrics.insert(metric.name(), metric.clone()); + metricNames.append(metric.name()); + if (deps) { + QVector *copy = new QVector; + 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 &dependencies(const QString &name) const { + assert(metrics.contains(name)); + QVector *result = dependencyMap.value(name); + return result ? *result : noDeps; + } }; #endif // _GC_RideMetric_h diff --git a/src/gui/gui.pro b/src/gui/gui.pro index a58ec70d6..94dcd1bb3 100644 --- a/src/gui/gui.pro +++ b/src/gui/gui.pro @@ -44,6 +44,7 @@ HEADERS += \ SOURCES += \ AllPlot.cpp \ BasicRideMetrics.cpp \ + BikeScore.cpp \ ChooseCyclistDialog.cpp \ CpintPlot.cpp \ CsvRideFile.cpp \