/* * Copyright (c) 2008 Sean C. Rhea (srhea@srhea.net) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "RideMetric.h" #include "LTMOutliers.h" #include "Units.h" #include "math.h" #include #include #define tr(s) QObject::tr(s) class RideCount : public RideMetric { public: RideCount() { setSymbol("ride_count"); setName(tr("Rides")); setMetricUnits(tr("")); setImperialUnits(tr("")); } void compute(const RideFile *, const Zones *, int, const HrZones *, int, const QHash &, const MainWindow *) { setValue(1); } RideMetric *clone() const { return new RideCount(*this); } }; static bool countAdded = RideMetricFactory::instance().addMetric(RideCount()); ////////////////////////////////////////////////////////////////////////////// class WorkoutTime : public RideMetric { double seconds; public: WorkoutTime() : seconds(0.0) { setSymbol("workout_time"); setName(tr("Duration")); setMetricUnits(tr("seconds")); setImperialUnits(tr("seconds")); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const MainWindow *) { seconds = ride->dataPoints().back()->secs - ride->dataPoints().front()->secs + ride->recIntSecs(); setValue(seconds); } RideMetric *clone() const { return new WorkoutTime(*this); } }; static bool workoutTimeAdded = RideMetricFactory::instance().addMetric(WorkoutTime()); ////////////////////////////////////////////////////////////////////////////// class TimeRiding : public RideMetric { double secsMovingOrPedaling; public: TimeRiding() : secsMovingOrPedaling(0.0) { setSymbol("time_riding"); setName(tr("Time Riding")); setMetricUnits(tr("seconds")); setImperialUnits(tr("seconds")); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const MainWindow *) { secsMovingOrPedaling = 0; foreach (const RideFilePoint *point, ride->dataPoints()) { if ((point->kph > 0.0) || (point->cad > 0.0)) secsMovingOrPedaling += ride->recIntSecs(); } setValue(secsMovingOrPedaling); } void override(const QMap &map) { if (map.contains("value")) secsMovingOrPedaling = map.value("value").toDouble(); } RideMetric *clone() const { return new TimeRiding(*this); } }; static bool timeRidingAdded = RideMetricFactory::instance().addMetric(TimeRiding()); ////////////////////////////////////////////////////////////////////////////// class TotalDistance : public RideMetric { double km; public: TotalDistance() : km(0.0) { setSymbol("total_distance"); setName(tr("Distance")); setType(RideMetric::Total); setMetricUnits(tr("km")); setImperialUnits(tr("miles")); setPrecision(2); setConversion(MILES_PER_KM); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const MainWindow *) { // Note: The 'km' in each sample is the distance travelled by the // *end* of the sampling period. The last term in this equation // accounts for the distance traveled *during* the first sample. km = ride->dataPoints().back()->km - ride->dataPoints().front()->km + ride->dataPoints().front()->kph / 3600.0 * ride->recIntSecs(); setValue(km); } RideMetric *clone() const { return new TotalDistance(*this); } }; static bool totalDistanceAdded = RideMetricFactory::instance().addMetric(TotalDistance()); ////////////////////////////////////////////////////////////////////////////// class ElevationGain : public RideMetric { double elegain; double prevalt; public: ElevationGain() : elegain(0.0), prevalt(0.0) { setSymbol("elevation_gain"); setName(tr("Elevation Gain")); setType(RideMetric::Total); setMetricUnits(tr("meters")); setImperialUnits(tr("feet")); setConversion(FEET_PER_METER); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const MainWindow *) { const double hysteresis = 3.0; bool first = true; foreach (const RideFilePoint *point, ride->dataPoints()) { if (first) { first = false; prevalt = point->alt; } else if (point->alt > prevalt + hysteresis) { elegain += point->alt - prevalt; prevalt = point->alt; } else if (point->alt < prevalt - hysteresis) { prevalt = point->alt; } } setValue(elegain); } RideMetric *clone() const { return new ElevationGain(*this); } }; static bool elevationGainAdded = RideMetricFactory::instance().addMetric(ElevationGain()); ////////////////////////////////////////////////////////////////////////////// class TotalWork : public RideMetric { double joules; public: TotalWork() : joules(0.0) { setSymbol("total_work"); setName(tr("Work")); setMetricUnits(tr("kJ")); setImperialUnits(tr("kJ")); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const MainWindow *) { foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->watts >= 0.0) joules += point->watts * ride->recIntSecs(); } setValue(joules/1000); } RideMetric *clone() const { return new TotalWork(*this); } }; static bool totalWorkAdded = RideMetricFactory::instance().addMetric(TotalWork()); ////////////////////////////////////////////////////////////////////////////// class AvgSpeed : public RideMetric { double secsMoving; double km; public: AvgSpeed() : secsMoving(0.0), km(0.0) { setSymbol("average_speed"); setName(tr("Average Speed")); setMetricUnits(tr("kph")); setImperialUnits(tr("mph")); setType(RideMetric::Average); setPrecision(1); setConversion(MILES_PER_KM); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &deps, const MainWindow *) { assert(deps.contains("total_distance")); km = deps.value("total_distance")->value(true); foreach (const RideFilePoint *point, ride->dataPoints()) if (point->kph > 0.0) secsMoving += ride->recIntSecs(); setValue(secsMoving ? km / secsMoving * 3600.0 : 0.0); } void aggregateWith(const RideMetric &other) { assert(symbol() == other.symbol()); const AvgSpeed &as = dynamic_cast(other); secsMoving += as.secsMoving; km += as.km; setValue(secsMoving ? km / secsMoving * 3600.0 : 0.0); } RideMetric *clone() const { return new AvgSpeed(*this); } }; static bool avgSpeedAdded = RideMetricFactory::instance().addMetric( AvgSpeed(), &(QVector() << "total_distance")); ////////////////////////////////////////////////////////////////////////////// struct AvgPower : public RideMetric { double count, total; AvgPower() { setSymbol("average_power"); setName(tr("Average Power")); setMetricUnits(tr("watts")); setImperialUnits(tr("watts")); setType(RideMetric::Average); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const MainWindow *) { total = count = 0; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->watts >= 0.0) { total += point->watts; ++count; } } setValue(count > 0 ? total / count : 0); setCount(count); } RideMetric *clone() const { return new AvgPower(*this); } }; static bool avgPowerAdded = RideMetricFactory::instance().addMetric(AvgPower()); ////////////////////////////////////////////////////////////////////////////// struct AvgHeartRate : public RideMetric { double total, count; AvgHeartRate() { setSymbol("average_hr"); setName(tr("Average Heart Rate")); setMetricUnits(tr("bpm")); setImperialUnits(tr("bpm")); setType(RideMetric::Average); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const MainWindow *) { total = count = 0; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->hr > 0) { total += point->hr; ++count; } } setValue(count > 0 ? total / count : 0); setCount(count); } RideMetric *clone() const { return new AvgHeartRate(*this); } }; static bool avgHeartRateAdded = RideMetricFactory::instance().addMetric(AvgHeartRate()); ////////////////////////////////////////////////////////////////////////////// struct AvgCadence : public RideMetric { double total, count; AvgCadence() { setSymbol("average_cad"); setName(tr("Average Cadence")); setMetricUnits(tr("rpm")); setImperialUnits(tr("rpm")); setType(RideMetric::Average); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const MainWindow *) { total = count = 0; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->cad > 0) { total += point->cad; ++count; } } setValue(count > 0 ? total / count : count); setCount(count); } RideMetric *clone() const { return new AvgCadence(*this); } }; static bool avgCadenceAdded = RideMetricFactory::instance().addMetric(AvgCadence()); ////////////////////////////////////////////////////////////////////////////// class MaxPower : public RideMetric { double max; public: MaxPower() : max(0.0) { setSymbol("max_power"); setName(tr("Max Power")); setMetricUnits(tr("watts")); setImperialUnits(tr("watts")); setType(RideMetric::Peak); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const MainWindow *) { foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->watts >= max) max = point->watts; } setValue(max); } RideMetric *clone() const { return new MaxPower(*this); } }; static bool maxPowerAdded = RideMetricFactory::instance().addMetric(MaxPower()); ////////////////////////////////////////////////////////////////////////////// class MaxHr : public RideMetric { double max; public: MaxHr() : max(0.0) { setSymbol("max_heartrate"); setName(tr("Max Heartrate")); setMetricUnits(tr("bpm")); setImperialUnits(tr("bpm")); setType(RideMetric::Peak); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const MainWindow *) { foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->hr >= max) max = point->hr; } setValue(max); } RideMetric *clone() const { return new MaxHr(*this); } }; static bool maxHrAdded = RideMetricFactory::instance().addMetric(MaxHr()); ////////////////////////////////////////////////////////////////////////////// class MaxSpeed : public RideMetric { public: MaxSpeed() { setSymbol("max_speed"); setName(tr("Max Speed")); setMetricUnits(tr("kph")); setImperialUnits(tr("mph")); setType(RideMetric::Peak); setPrecision(1); setConversion(MILES_PER_KM); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const MainWindow *) { double max = 0.0; foreach (const RideFilePoint *point, ride->dataPoints()) if (point->kph > max) max = point->kph; setValue(max); } void aggregateWith(const RideMetric &other) { assert(symbol() == other.symbol()); const MaxSpeed &ms = dynamic_cast(other); setValue(ms.value(true) > value(true) ? ms.value(true) : value(true)); } RideMetric *clone() const { return new MaxSpeed(*this); } }; static bool maxSpeedAdded = RideMetricFactory::instance().addMetric(MaxSpeed()); ////////////////////////////////////////////////////////////////////////////// class MaxCadence : public RideMetric { public: MaxCadence() { setSymbol("max_cadence"); setName(tr("Max Cadence")); setMetricUnits(tr("rpm")); setImperialUnits(tr("rpm")); setType(RideMetric::Peak); setPrecision(1); setConversion(MILES_PER_KM); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const MainWindow *) { double max = 0.0; foreach (const RideFilePoint *point, ride->dataPoints()) if (point->cad > max) max = point->cad; setValue(max); } void aggregateWith(const RideMetric &other) { assert(symbol() == other.symbol()); const MaxCadence &mc = dynamic_cast(other); setValue(mc.value(true) > value(true) ? mc.value(true) : value(true)); } RideMetric *clone() const { return new MaxCadence(*this); } }; static bool maxCadenceAdded = RideMetricFactory::instance().addMetric(MaxCadence()); ////////////////////////////////////////////////////////////////////////////// class NinetyFivePercentHeartRate : public RideMetric { double hr; public: NinetyFivePercentHeartRate() : hr(0.0) { setSymbol("ninety_five_percent_hr"); setName(tr("95% Heartrate")); setMetricUnits(tr("bpm")); setImperialUnits(tr("bpm")); setType(RideMetric::Average); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const MainWindow *) { QVector hrs; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->hr >= 0.0) hrs.append(point->hr); } if (hrs.size() > 0) { std::sort(hrs.begin(), hrs.end()); hr = hrs[hrs.size() * 0.95]; } setValue(hr); } RideMetric *clone() const { return new NinetyFivePercentHeartRate(*this); } }; static bool ninetyFivePercentHeartRateAdded = RideMetricFactory::instance().addMetric(NinetyFivePercentHeartRate()); /////////////////////////////////////////////////////////////////////////////// class VAM : public RideMetric { public: VAM() { setSymbol("vam"); setName(tr("VAM")); setImperialUnits(""); setMetricUnits(""); setType(RideMetric::Average); } void compute(const RideFile *, const Zones *, int, const HrZones *, int, const QHash &deps, const MainWindow *) { ElevationGain *el = dynamic_cast(deps.value("elevation_gain")); WorkoutTime *wt = dynamic_cast(deps.value("workout_time")); setValue((el->value(true)*3600)/wt->value(true)); } RideMetric *clone() const { return new VAM(*this); } }; static bool addVam() { QVector deps; deps.append("elevation_gain"); deps.append("workout_time"); RideMetricFactory::instance().addMetric(VAM(), &deps); return true; } static bool vamAdded = addVam(); /////////////////////////////////////////////////////////////////////////////// class Gradient : public RideMetric { public: Gradient() { setSymbol("gradient"); setName(tr("Gradient")); setImperialUnits("%"); setMetricUnits("%"); setPrecision(1); setType(RideMetric::Average); } void compute(const RideFile *, const Zones *, int, const HrZones *, int, const QHash &deps, const MainWindow *) { ElevationGain *el = dynamic_cast(deps.value("elevation_gain")); TotalDistance *td = dynamic_cast(deps.value("total_distance")); if (td->value(true) && el->value(true)) { setValue(100.00 *(el->value(true) / (1000 *td->value(true)))); } else setValue(0.0); } RideMetric *clone() const { return new Gradient(*this); } }; static bool addGradient() { QVector deps; deps.append("elevation_gain"); deps.append("total_distance"); RideMetricFactory::instance().addMetric(Gradient(), &deps); return true; } static bool gradientAdded = addGradient(); /////////////////////////////////////////////////////////////////////////////// class MeanPowerVariance : public RideMetric { public: LTMOutliers *outliers; MeanPowerVariance() { outliers = NULL; setSymbol("meanpowervariance"); setName(tr("Average Power Variance")); setImperialUnits("watts change"); setMetricUnits("watts change"); setPrecision(2); setType(RideMetric::Average); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const MainWindow *) { // Less than 30s don't bother if (ride->dataPoints().count() < 30) setValue(0); else { QVector power; QVector secs; foreach (RideFilePoint *point, ride->dataPoints()) { power.append(point->watts); secs.append(point->secs); } if (outliers) delete outliers; outliers = new LTMOutliers(secs.data(), power.data(), power.count(), 30, false); setValue(outliers->getStdDeviation()); } } RideMetric *clone() const { return new MeanPowerVariance(*this); } }; static bool addMeanPowerVariance() { RideMetricFactory::instance().addMetric(MeanPowerVariance()); return true; } static bool meanPowerVarianceAdded = addMeanPowerVariance(); /////////////////////////////////////////////////////////////////////////////// class MaxPowerVariance : public RideMetric { public: MaxPowerVariance() { setSymbol("maxpowervariance"); setName(tr("Max Power Variance")); setImperialUnits("watts change"); setMetricUnits("watts change"); setPrecision(2); setType(RideMetric::Average); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &deps, const MainWindow *) { MeanPowerVariance *mean = dynamic_cast(deps.value("meanpowervariance")); if (ride->dataPoints().count() < 30) setValue(0); else setValue(mean->outliers->getYForRank(0)); } RideMetric *clone() const { return new MaxPowerVariance(*this); } }; static bool addMaxPowerVariance() { QVector deps; deps.append("meanpowervariance"); RideMetricFactory::instance().addMetric(MaxPowerVariance(), &deps); return true; } static bool maxPowerVarianceAdded = addMaxPowerVariance(); //////////////////////////////////////////////////////////////////////////////