/* * 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 "Athlete.h" #include "Context.h" #include "Settings.h" #include "RideItem.h" #include "LTMOutliers.h" #include "Units.h" #include "cmath" #include #include #include class RideCount : public RideMetric { Q_DECLARE_TR_FUNCTIONS(RideCount) public: RideCount() { setSymbol("ride_count"); setInternalName("Activities"); } void initialize() { setName(tr("Activities")); setMetricUnits(tr("")); setImperialUnits(tr("")); } void compute(const RideFile *, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { setValue(1); } RideMetric *clone() const { return new RideCount(*this); } }; static bool countAdded = RideMetricFactory::instance().addMetric(RideCount()); ////////////////////////////////////////////////////////////////////////////// class WorkoutTime : public RideMetric { Q_DECLARE_TR_FUNCTIONS(WorkoutTime) double seconds; public: WorkoutTime() : seconds(0.0) { setSymbol("workout_time"); setInternalName("Duration"); } bool isTime() const { return true; } void initialize() { setName(tr("Duration")); setMetricUnits(tr("seconds")); setImperialUnits(tr("seconds")); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (!ride->dataPoints().isEmpty()) { seconds = ride->dataPoints().back()->secs - ride->dataPoints().front()->secs + ride->recIntSecs(); } else { seconds = 0; } setValue(seconds); } RideMetric *clone() const { return new WorkoutTime(*this); } }; static bool workoutTimeAdded = RideMetricFactory::instance().addMetric(WorkoutTime()); ////////////////////////////////////////////////////////////////////////////// class TimeRiding : public RideMetric { Q_DECLARE_TR_FUNCTIONS(TimeRiding) double secsMovingOrPedaling; public: TimeRiding() : secsMovingOrPedaling(0.0) { setSymbol("time_riding"); setInternalName("Time Moving"); } bool isTime() const { return true; } void initialize() { setName(tr("Time Moving")); setMetricUnits(tr("seconds")); setImperialUnits(tr("seconds")); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { secsMovingOrPedaling = 0; if (ride->areDataPresent()->kph || ride->areDataPresent()->cad ) { 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 TimeCarrying : public RideMetric { Q_DECLARE_TR_FUNCTIONS(TimeCarrying) double secsCarrying; double prevalt; public: TimeCarrying() : secsCarrying(0.0) { setSymbol("time_carrying"); setInternalName("Time Carrying"); } bool isTime() const { return true; } void initialize() { setName(tr("Time Carrying (Est)")); setMetricUnits(tr("seconds")); setImperialUnits(tr("seconds")); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { secsCarrying = 0; if (ride->areDataPresent()->kph) { // hysteresis can be configured, we default to 3.0 double hysteresis = appsettings->value(NULL, GC_ELEVATION_HYSTERESIS).toDouble(); if (hysteresis <= 0.1) hysteresis = 3.00; bool first = true; foreach (const RideFilePoint *point, ride->dataPoints()) { // only consider pushing/carrying with elevation gain if (first) { first = false; prevalt = point->alt; } else if (point->alt > prevalt + hysteresis) { prevalt = point->alt; } else if (point->alt < prevalt - hysteresis) { prevalt = point->alt; } if ((point->kph > 0.0) && // we are moving (point->kph < 8.0) && // but slow (even slower than 8 kph) (point->alt > prevalt) && // gaining height (point->cad == 0.0) && // but no cadence (point->watts == 0.0)) // and no power secsCarrying += ride->recIntSecs(); } } setValue(secsCarrying); } RideMetric *clone() const { return new TimeCarrying(*this); } }; static bool timeCarryingAdded = RideMetricFactory::instance().addMetric(TimeCarrying()); ////////////////////////////////////////////////////////////////////////////// class ElevationGainCarrying : public RideMetric { Q_DECLARE_TR_FUNCTIONS(ElevationGain) double elegain; double prevalt; public: ElevationGainCarrying() : elegain(0.0), prevalt(0.0) { setSymbol("elevation_gain_carrying"); setInternalName("Elevation Gain Carrying"); } void initialize() { setName(tr("Elevation Gain Carrying (Est)")); 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 Context *) { // hysteresis can be configured, we default to 3.0 double hysteresis = appsettings->value(NULL, GC_ELEVATION_HYSTERESIS).toDouble(); if (hysteresis <= 0.1) hysteresis = 3.00; bool first = true; foreach (const RideFilePoint *point, ride->dataPoints()) { if (first) { first = false; prevalt = point->alt; } else if (point->alt > prevalt + hysteresis) { if ((point->kph > 0.0) && (point->kph < 8.0) && (point->watts == 0.0) && (point->cad == 0.0)) { elegain += point->alt - prevalt; }; prevalt = point->alt; } else if (point->alt < prevalt - hysteresis) { prevalt = point->alt; } } setValue(elegain); } RideMetric *clone() const { return new ElevationGainCarrying(*this); } }; static bool elevationGainCarryingAdded = RideMetricFactory::instance().addMetric(ElevationGainCarrying()); ////////////////////////////////////////////////////////////////////////////// class TotalDistance : public RideMetric { Q_DECLARE_TR_FUNCTIONS(TotalDistance) double km; public: TotalDistance() : km(0.0) { setSymbol("total_distance"); setInternalName("Distance"); } void initialize() { 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 Context *) { // 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. if (ride->dataPoints().count() > 1 && ride->areDataPresent()->km) { km = ride->dataPoints().back()->km - ride->dataPoints().front()->km; if (ride->areDataPresent()->kph) km += ride->dataPoints().front()->kph / 3600.0 * ride->recIntSecs(); } else { km = 0; } setValue(km); } RideMetric *clone() const { return new TotalDistance(*this); } }; static bool totalDistanceAdded = RideMetricFactory::instance().addMetric(TotalDistance()); class AthleteWeight : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AthleteWeight) double kg; public: AthleteWeight() : kg(0.0) { setSymbol("athlete_weight"); setInternalName("Athlete Weight"); } void initialize() { setName(tr("Athlete Weight")); setType(RideMetric::Average); setMetricUnits(tr("kg")); setImperialUnits(tr("lbs")); setPrecision(2); setConversion(LB_PER_KG); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *context) { // withings first double weight = context->athlete->getWithingsWeight(ride->startTime().date()); // from metadata if (!weight) weight = ride->getTag("Weight", "0.0").toDouble(); // global options if (!weight) weight = appsettings->cvalue(context->athlete->cyclist, GC_WEIGHT, "75.0").toString().toDouble(); // default to 75kg // No weight default is weird, we'll set to 80kg if (weight <= 0.00) weight = 80.00; setValue(weight); } RideMetric *clone() const { return new AthleteWeight(*this); } }; static bool athleteWeightAdded = RideMetricFactory::instance().addMetric(AthleteWeight()); ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// class ElevationGain : public RideMetric { Q_DECLARE_TR_FUNCTIONS(ElevationGain) double elegain; double prevalt; public: ElevationGain() : elegain(0.0), prevalt(0.0) { setSymbol("elevation_gain"); setInternalName("Elevation Gain"); } void initialize() { 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 Context *) { // hysteresis can be configured, we default to 3.0 double hysteresis = appsettings->value(NULL, GC_ELEVATION_HYSTERESIS).toDouble(); if (hysteresis <= 0.1) hysteresis = 3.00; 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 ElevationLoss : public RideMetric { Q_DECLARE_TR_FUNCTIONS(ElevationLoss) double eleLoss; double prevalt; public: ElevationLoss() : eleLoss(0.0), prevalt(0.0) { setSymbol("elevation_loss"); setInternalName("Elevation Loss"); } void initialize() { setName(tr("Elevation Loss")); 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 Context *) { // hysteresis can be configured, we default to 3.0 double hysteresis = appsettings->value(NULL, GC_ELEVATION_HYSTERESIS).toDouble(); if (hysteresis <= 0.1) hysteresis = 3.00; bool first = true; foreach (const RideFilePoint *point, ride->dataPoints()) { if (first) { first = false; prevalt = point->alt; } else if (point->alt < prevalt - hysteresis) { eleLoss += prevalt - point->alt; prevalt = point->alt; } else if (point->alt > prevalt + hysteresis) { prevalt = point->alt; } } setValue(eleLoss); } RideMetric *clone() const { return new ElevationLoss(*this); } }; static bool elevationLossAdded = RideMetricFactory::instance().addMetric(ElevationLoss()); ////////////////////////////////////////////////////////////////////////////// class TotalWork : public RideMetric { Q_DECLARE_TR_FUNCTIONS(TotalWork) double joules; public: TotalWork() : joules(0.0) { setSymbol("total_work"); setInternalName("Work"); } void initialize() { setName(tr("Work")); setMetricUnits(tr("kJ")); setImperialUnits(tr("kJ")); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { joules = 0; 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 { Q_DECLARE_TR_FUNCTIONS(AvgSpeed) double secsMoving; double km; public: AvgSpeed() : secsMoving(0.0), km(0.0) { setSymbol("average_speed"); setInternalName("Average Speed"); } void initialize() { 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 Context *) { assert(deps.contains("total_distance")); km = deps.value("total_distance")->value(true); if (ride->areDataPresent()->kph) { secsMoving = 0; bool withz = ride->isSwim(); // average with zeros for swims foreach (const RideFilePoint *point, ride->dataPoints()) if (withz || point->kph > 0.0) secsMoving += ride->recIntSecs(); setValue(secsMoving ? km / secsMoving * 3600.0 : 0.0); } else { // backup to duration if there is no speed channel assert(deps.contains("workout_time")); secsMoving = deps.value("workout_time")->value(true); 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" << "workout_time")); ////////////////////////////////////////////////////////////////////////////// class Pace : public RideMetric { Q_DECLARE_TR_FUNCTIONS(Pace) double pace; public: Pace() : pace(0.0) { setSymbol("pace"); setInternalName("Pace"); } // Pace ordering is reversed bool isLowerBetter() const { return true; } // Overrides to use Pace units setting QString units(bool) const { bool metricRunPace = appsettings->value(NULL, GC_PACE, true).toBool(); return RideMetric::units(metricRunPace); } double value(bool) const { bool metricRunPace = appsettings->value(NULL, GC_PACE, true).toBool(); return RideMetric::value(metricRunPace); } QString toString(bool metric) const { return time_to_string(value(metric)*60); } void initialize() { setName(tr("Pace")); setType(RideMetric::Average); setMetricUnits(tr("min/km")); setImperialUnits(tr("min/mile")); setPrecision(1); setConversion(KM_PER_MILE); } void compute(const RideFile *, const Zones *, int, const HrZones *, int, const QHash &deps, const Context *) { AvgSpeed *as = dynamic_cast(deps.value("average_speed")); // divide by zero or stupidly low pace if (as->value(true) > 0.00f) pace = 60.0f / as->value(true); else pace = 0; setValue(pace); setCount(as->count()); } bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim; } RideMetric *clone() const { return new Pace(*this); } }; static bool addPace() { QVector deps; deps.append("average_speed"); RideMetricFactory::instance().addMetric(Pace(), &deps); return true; } static bool paceAdded = addPace(); ////////////////////////////////////////////////////////////////////////////// class PaceSwim : public RideMetric { Q_DECLARE_TR_FUNCTIONS(PaceSwim) double pace; public: PaceSwim() : pace(0.0) { setSymbol("pace_swim"); setInternalName("Pace Swim"); } // Swim Pace ordering is reversed bool isLowerBetter() const { return true; } // Overrides to use Swim Pace units setting QString units(bool) const { bool metricRunPace = appsettings->value(NULL, GC_SWIMPACE, true).toBool(); return RideMetric::units(metricRunPace); } double value(bool) const { bool metricRunPace = appsettings->value(NULL, GC_SWIMPACE, true).toBool(); return RideMetric::value(metricRunPace); } QString toString(bool metric) const { return time_to_string(value(metric)*60); } void initialize() { setName(tr("Pace Swim")); setType(RideMetric::Average); setMetricUnits(tr("min/100m")); setImperialUnits(tr("min/100yd")); setPrecision(1); setConversion(METERS_PER_YARD); } void compute(const RideFile *, const Zones *, int, const HrZones *, int, const QHash &deps, const Context *) { AvgSpeed *as = dynamic_cast(deps.value("average_speed")); // divide by zero or stupidly low pace if (as->value(true) > 0.00f) pace = 6.0f / as->value(true); else pace = 0; setValue(pace); setCount(as->count()); } bool isRelevantForRide(const RideItem *ride) const { return ride->isSwim; } RideMetric *clone() const { return new PaceSwim(*this); } }; static bool addPaceSwim() { QVector deps; deps.append("average_speed"); RideMetricFactory::instance().addMetric(PaceSwim(), &deps); return true; } static bool paceSwimAdded = addPaceSwim(); ////////////////////////////////////////////////////////////////////////////// struct AvgPower : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgPower) double count, total; public: AvgPower() { setSymbol("average_power"); setInternalName("Average Power"); } void initialize() { 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 Context *) { 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 AvgSmO2 : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgSmO2) double count, total; public: AvgSmO2() { setSymbol("average_smo2"); setInternalName("Average SmO2"); } void initialize() { setName(tr("Average SmO2")); setMetricUnits(tr("%")); setImperialUnits(tr("%")); setType(RideMetric::Average); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { total = count = 0; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->smo2 >= 0.0) { total += point->smo2; ++count; } } setValue(count > 0 ? total / count : 0); setCount(count); } bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("O"); } RideMetric *clone() const { return new AvgSmO2(*this); } }; static bool avgSmO2Added = RideMetricFactory::instance().addMetric(AvgSmO2()); ////////////////////////////////////////////////////////////////////////////// struct AAvgPower : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AAvgPower) double count, total; public: AAvgPower() { setSymbol("average_apower"); setInternalName("Average aPower"); } void initialize() { setName(tr("Average aPower")); setMetricUnits(tr("watts")); setImperialUnits(tr("watts")); setType(RideMetric::Average); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { total = count = 0; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->apower >= 0.0) { total += point->apower; ++count; } } setValue(count > 0 ? total / count : 0); setCount(count); } RideMetric *clone() const { return new AAvgPower(*this); } }; static bool aavgPowerAdded = RideMetricFactory::instance().addMetric(AAvgPower()); ////////////////////////////////////////////////////////////////////////////// struct NonZeroPower : public RideMetric { Q_DECLARE_TR_FUNCTIONS(NonZeroPower) double count, total; public: NonZeroPower() { setSymbol("nonzero_power"); setInternalName("Nonzero Average Power"); } void initialize() { setName(tr("Nonzero 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 Context *) { 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 NonZeroPower(*this); } }; static bool nonZeroPowerAdded = RideMetricFactory::instance().addMetric(NonZeroPower()); ////////////////////////////////////////////////////////////////////////////// struct AvgHeartRate : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgHeartRate) double total, count; public: AvgHeartRate() { setSymbol("average_hr"); setInternalName("Average Heart Rate"); } void initialize() { 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 Context *) { 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); } bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("H"); } RideMetric *clone() const { return new AvgHeartRate(*this); } }; static bool avgHeartRateAdded = RideMetricFactory::instance().addMetric(AvgHeartRate()); /////////////////////////////////////////////////////////////////////////////// class HrPw : public RideMetric { Q_DECLARE_TR_FUNCTIONS(HrPw) public: HrPw() { setSymbol("hrpw"); setInternalName("HrPw Ratio"); } void initialize() { setName(tr("HrPw Ratio")); setImperialUnits(""); setMetricUnits(""); setPrecision(3); setType(RideMetric::Average); } void compute(const RideFile *, const Zones *, int, const HrZones *, int, const QHash &deps, const Context *) { AvgHeartRate *hr = dynamic_cast(deps.value("average_hr")); AvgPower *pw = dynamic_cast(deps.value("average_power")); if (hr->value(true) > 100 && pw->value(true) > 100) { // ignore silly rides with low values setValue(pw->value(true) / hr->value(true)); } else { setValue(0); } } RideMetric *clone() const { return new HrPw(*this); } }; static bool addHrPw() { QVector deps; deps.append("average_power"); deps.append("average_hr"); RideMetricFactory::instance().addMetric(HrPw(), &deps); return true; } static bool hrpwAdded = addHrPw(); ////////////////////////////////////////////////////////////////////// class WattsRPE : public RideMetric { Q_DECLARE_TR_FUNCTIONS(WattsRPE) public: WattsRPE() { setSymbol("wattsRPE"); setInternalName("Watts:RPE Ratio"); } void initialize() { setName(tr("Watts:RPE Ratio")); setImperialUnits(""); setMetricUnits(""); setPrecision(3); setType(RideMetric::Average); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &deps, const Context *) { double ratio = 0.0f; AvgPower *pw = dynamic_cast(deps.value("average_power")); double rpe = ride->getTag("RPE", "0").toDouble(); if (pw->value(true) > 100 && rpe > 0) { // ignore silly rides with low values ratio = pw->value(true) / rpe; } setValue(ratio); } RideMetric *clone() const { return new WattsRPE(*this); } }; static bool addWattsRPE() { QVector deps; deps.append("average_power"); RideMetricFactory::instance().addMetric(WattsRPE(), &deps); return true; } static bool wattsRPEAdded = addWattsRPE(); /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// class HrNp : public RideMetric { Q_DECLARE_TR_FUNCTIONS(HrNp) public: HrNp() { setSymbol("hrnp"); setInternalName("HrNp Ratio"); } void initialize() { setName(tr("HrNp Ratio")); setImperialUnits(""); setMetricUnits(""); setPrecision(3); setType(RideMetric::Average); } void compute(const RideFile *, const Zones *, int, const HrZones *, int, const QHash &deps, const Context *) { AvgHeartRate *hr = dynamic_cast(deps.value("average_hr")); RideMetric *pw = dynamic_cast(deps.value("coggan_np")); if (hr->value(true) > 100 && pw->value(true) > 100) { // ignore silly rides with low values setValue(pw->value(true) / hr->value(true)); } else { setValue(0); } } RideMetric *clone() const { return new HrNp(*this); } }; static bool addHrNp() { QVector deps; deps.append("coggan_np"); deps.append("average_hr"); RideMetricFactory::instance().addMetric(HrNp(), &deps); return true; } static bool hrnpAdded = addHrNp(); /////////////////////////////////////////////////////////////////////////////// struct AvgCadence : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgCadence) double total, count; public: AvgCadence() { setSymbol("average_cad"); setInternalName("Average Cadence"); } void initialize() { 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 Context *) { 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); } bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("C"); } RideMetric *clone() const { return new AvgCadence(*this); } }; static bool avgCadenceAdded = RideMetricFactory::instance().addMetric(AvgCadence()); ////////////////////////////////////////////////////////////////////////////// struct AvgTemp : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgTemp) double total, count; public: AvgTemp() { setSymbol("average_temp"); setInternalName("Average Temp"); } // we DO aggregate zero, its -255 we ignore ! bool aggregateZero() const { return true; } void initialize() { setName(tr("Average Temp")); setMetricUnits(tr("C")); setImperialUnits(tr("F")); setPrecision(1); setConversion(FAHRENHEIT_PER_CENTIGRADE); setConversionSum(FAHRENHEIT_ADD_CENTIGRADE); setType(RideMetric::Average); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->temp) { total = count = 0; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->temp != RideFile::NoTemp) { total += point->temp; ++count; } } setValue(count > 0 ? total / count : count); setCount(count); } else { setValue(RideFile::NoTemp); setCount(1); } } RideMetric *clone() const { return new AvgTemp(*this); } }; static bool avgTempAdded = RideMetricFactory::instance().addMetric(AvgTemp()); ////////////////////////////////////////////////////////////////////////////// class MaxPower : public RideMetric { Q_DECLARE_TR_FUNCTIONS(MaxPower) double max; public: MaxPower() : max(0.0) { setSymbol("max_power"); setInternalName("Max Power"); } void initialize() { 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 Context *) { 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 MaxSmO2 : public RideMetric { Q_DECLARE_TR_FUNCTIONS(MaxSmO2) double max; public: MaxSmO2() : max(0.0) { setSymbol("max_smo2"); setInternalName("Max SmO2"); } void initialize() { setName(tr("Max SmO2")); setMetricUnits(tr("%")); setImperialUnits(tr("%")); setType(RideMetric::Peak); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->smo2 >= max) max = point->smo2; } setValue(max); } bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("O"); } RideMetric *clone() const { return new MaxSmO2(*this); } }; static bool maxSmO2Added = RideMetricFactory::instance().addMetric(MaxSmO2()); ////////////////////////////////////////////////////////////////////////////// class MinSmO2 : public RideMetric { Q_DECLARE_TR_FUNCTIONS(MinSmO2) double min; public: MinSmO2() : min(0.0) { setSymbol("min_smo2"); setInternalName("Min SmO2"); } void initialize() { setName(tr("Min SmO2")); setMetricUnits(tr("%")); setImperialUnits(tr("%")); setType(RideMetric::Peak); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->smo2 > 0 && point->smo2 >= min) min = point->smo2; } setValue(min); } RideMetric *clone() const { return new MinSmO2(*this); } }; static bool minSmO2Added = RideMetricFactory::instance().addMetric(MinSmO2()); ////////////////////////////////////////////////////////////////////////////// class MaxHr : public RideMetric { Q_DECLARE_TR_FUNCTIONS(MaxHr) double max; public: MaxHr() : max(0.0) { setSymbol("max_heartrate"); setInternalName("Max Heartrate"); } void initialize() { 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 Context *) { foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->hr >= max) max = point->hr; } setValue(max); } bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("H"); } RideMetric *clone() const { return new MaxHr(*this); } }; static bool maxHrAdded = RideMetricFactory::instance().addMetric(MaxHr()); ////////////////////////////////////////////////////////////////////////////// class MaxSpeed : public RideMetric { Q_DECLARE_TR_FUNCTIONS(MaxSpeed) public: MaxSpeed() { setSymbol("max_speed"); setInternalName("Max Speed"); } void initialize() { 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 Context *) { double max = 0.0; if (ride->areDataPresent()->kph) { 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 { Q_DECLARE_TR_FUNCTIONS(MaxCadence) public: MaxCadence() { setSymbol("max_cadence"); setInternalName("Max Cadence"); } void initialize() { setName(tr("Max Cadence")); setMetricUnits(tr("rpm")); setImperialUnits(tr("rpm")); setType(RideMetric::Peak); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { 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)); } bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("C"); } RideMetric *clone() const { return new MaxCadence(*this); } }; static bool maxCadenceAdded = RideMetricFactory::instance().addMetric(MaxCadence()); ////////////////////////////////////////////////////////////////////////////// class MaxTemp : public RideMetric { Q_DECLARE_TR_FUNCTIONS(MaxTemp) public: MaxTemp() { setSymbol("max_temp"); setInternalName("Max Temp"); } void initialize() { setName(tr("Max Temp")); setMetricUnits(tr("C")); setImperialUnits(tr("F")); setType(RideMetric::Peak); setPrecision(1); setConversion(FAHRENHEIT_PER_CENTIGRADE); setConversionSum(FAHRENHEIT_ADD_CENTIGRADE); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->temp) { double max = 0.0; foreach (const RideFilePoint *point, ride->dataPoints()) if (point->temp != RideFile::NoTemp && point->temp > max) max = point->temp; setValue(max); } else { setValue(RideFile::NoTemp); } } void aggregateWith(const RideMetric &other) { assert(symbol() == other.symbol()); const MaxTemp &mc = dynamic_cast(other); setValue(mc.value(true) > value(true) ? mc.value(true) : value(true)); } RideMetric *clone() const { return new MaxTemp(*this); } }; static bool maxTempAdded = RideMetricFactory::instance().addMetric(MaxTemp()); ////////////////////////////////////////////////////////////////////////////// class NinetyFivePercentHeartRate : public RideMetric { Q_DECLARE_TR_FUNCTIONS(NinetyFivePercentHeartRate) double hr; public: NinetyFivePercentHeartRate() : hr(0.0) { setSymbol("ninety_five_percent_hr"); setInternalName("95% Heartrate"); } void initialize() { 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 Context *) { 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 { Q_DECLARE_TR_FUNCTIONS(VAM) public: VAM() { setSymbol("vam"); setInternalName("VAM"); } void initialize() { setName(tr("VAM")); setImperialUnits(""); setMetricUnits(""); setType(RideMetric::Average); } void compute(const RideFile *, const Zones *, int, const HrZones *, int, const QHash &deps, const Context *) { 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 EOA : public RideMetric { Q_DECLARE_TR_FUNCTIONS(EOA) public: EOA() { setSymbol("eoa"); setInternalName("EOA"); } void initialize() { setName(tr("Effect of Altitude")); setImperialUnits("%"); setMetricUnits("%"); setType(RideMetric::Average); } void compute(const RideFile *, const Zones *, int, const HrZones *, int, const QHash &deps, const Context *) { AAvgPower *aap = dynamic_cast(deps.value("average_apower")); AvgPower *ap = dynamic_cast(deps.value("average_power")); setValue(((aap->value(true)-ap->value(true))/aap->value(true)) * 100.00f); } RideMetric *clone() const { return new EOA(*this); } }; static bool addEOA() { QVector deps; deps.append("average_apower"); deps.append("average_power"); RideMetricFactory::instance().addMetric(EOA(), &deps); return true; } static bool eoaAdded = addEOA(); /////////////////////////////////////////////////////////////////////////////// class Gradient : public RideMetric { Q_DECLARE_TR_FUNCTIONS(Gradient) public: Gradient() { setSymbol("gradient"); setInternalName("Gradient"); } void initialize() { setName(tr("Gradient")); setImperialUnits("%"); setMetricUnits("%"); setPrecision(1); setType(RideMetric::Average); } void compute(const RideFile *, const Zones *, int, const HrZones *, int, const QHash &deps, const Context *) { 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 { Q_DECLARE_TR_FUNCTIONS(MeanPowerVariance) public: double topRank; MeanPowerVariance() { setSymbol("meanpowervariance"); setInternalName("Average Power Variance"); } void initialize() { 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 Context *) { // Less than 30s don't bother if (ride->dataPoints().count() < 30) { setValue(0); topRank=0.00; } else { QVector power; QVector secs; foreach (RideFilePoint *point, ride->dataPoints()) { power.append(point->watts); secs.append(point->secs); } LTMOutliers outliers(secs.data(), power.data(), power.count(), 30, false); setValue(outliers.getStdDeviation()); topRank = outliers.getYForRank(0); } } RideMetric *clone() const { return new MeanPowerVariance(*this); } }; static bool addMeanPowerVariance() { RideMetricFactory::instance().addMetric(MeanPowerVariance()); return true; } static bool meanPowerVarianceAdded = addMeanPowerVariance(); /////////////////////////////////////////////////////////////////////////////// class MaxPowerVariance : public RideMetric { Q_DECLARE_TR_FUNCTIONS(MaxPowerVariance) public: MaxPowerVariance() { setSymbol("maxpowervariance"); setInternalName("Max Power Variance"); } void initialize() { 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 Context *) { MeanPowerVariance *mean = dynamic_cast(deps.value("meanpowervariance")); if (ride->dataPoints().count() < 30) setValue(0); else setValue(mean->topRank); } 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(); ////////////////////////////////////////////////////////////////////////////// class AvgLTE : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgLTE) public: AvgLTE() { setSymbol("average_lte"); setInternalName("Average Left Torque Effectiveness"); } void initialize() { setName(tr("Average Left Torque Effectiveness")); setMetricUnits(tr("%")); setImperialUnits(tr("%")); setType(RideMetric::Average); setPrecision(1); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->lte) { double total = 0.0f; double samples = 0.0f; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->lte) { samples ++; total += point->lte; } } if (total > 0.0f && samples > 0.0f) setValue(total / samples); else setValue(0.0); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgLTE(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgRTE : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgRTE) public: AvgRTE() { setSymbol("average_rte"); setInternalName("Average Right Torque Effectiveness"); } void initialize() { setName(tr("Average Right Torque Effectiveness")); setMetricUnits(tr("%")); setImperialUnits(tr("%")); setType(RideMetric::Average); setPrecision(1); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->rte) { double total = 0.0f; double samples = 0.0f; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->rte) { samples ++; total += point->rte; } } if (total > 0.0f && samples > 0.0f) setValue(total / samples); else setValue(0.0); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgRTE(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgLPS : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgLPS) public: AvgLPS() { setSymbol("average_lps"); setInternalName("Average Left Pedal Smoothness"); } void initialize() { setName(tr("Average Left Pedal Smoothness")); setMetricUnits(tr("%")); setImperialUnits(tr("%")); setType(RideMetric::Average); setPrecision(1); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->lps) { double total = 0.0f; double samples = 0.0f; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->lps) { samples ++; total += point->lps; } } if (total > 0.0f && samples > 0.0f) setValue(total / samples); else setValue(0.0); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgLPS(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgRPS : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgRPS) public: AvgRPS() { setSymbol("average_rps"); setInternalName("Average Right Pedal Smoothness"); } void initialize() { setName(tr("Average Right Pedal Smoothness")); setMetricUnits(tr("%")); setImperialUnits(tr("%")); setType(RideMetric::Average); setPrecision(1); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->rps) { double total = 0.0f; double samples = 0.0f; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->rps) { samples ++; total += point->rps; } } if (total > 0.0f && samples > 0.0f) setValue(total / samples); else setValue(0.0); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgRPS(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgLPCO : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgLPCO) public: AvgLPCO() { setSymbol("average_lpco"); setInternalName("Average Left Pedal Center Offset"); } void initialize() { setName(tr("Average Left Pedal Center Offset")); setMetricUnits(tr("mm")); setImperialUnits(tr("in")); // inches would need more precision than 1 setType(RideMetric::Average); setPrecision(0); setImperialPrecision(2); setConversion(INCH_PER_MM); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->lpco) { double total = 0.0f; double secs = 0.0f; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->cad) { secs += ride->recIntSecs(); total += point->lpco; } } if (secs > 0.0f) setValue(total / secs); else setValue(0.0); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgLPCO(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgRPCO : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgRPCO) public: AvgRPCO() { setSymbol("average_rpco"); setInternalName("Average Right Pedal Center Offset"); } void initialize() { setName(tr("Average Right Pedal Center Offset")); setMetricUnits(tr("mm")); setImperialUnits(tr("in")); // inches would need more precision than 1 setType(RideMetric::Average); setPrecision(0); setImperialPrecision(2); setConversion(INCH_PER_MM); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->rpco) { double total = 0.0f; double secs = 0.0f; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->cad) { secs += ride->recIntSecs(); total += point->rpco; } } if (secs > 0.0f) setValue(total / secs); else setValue(0.0); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgRPCO(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgLPPB : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgLPPB) public: AvgLPPB() { setSymbol("average_lppb"); setInternalName("Average Left Power Phase Start"); } void initialize() { setName(tr("Average Left Power Phase Start")); setMetricUnits(tr("°")); setType(RideMetric::Average); setPrecision(0); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->lppb) { double total = 0.0f; double secs = 0.0f; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->lppe>0) { // use for average if we have an end secs += ride->recIntSecs(); total += point->lppb + (point->lppb>180?-360:0); } } if (secs > 0.0f) setValue(total / secs); else setValue(0.0); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgLPPB(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgRPPB : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgRTPP) public: AvgRPPB() { setSymbol("average_rppb"); setInternalName("Average Right Power Phase Start"); } void initialize() { setName(tr("Average Right Power Phase Start")); setMetricUnits(tr("°")); setType(RideMetric::Average); setPrecision(0); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->rppb) { double total = 0.0f; double secs = 0.0f; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->rppe>0) { // use for average if we have an end secs += ride->recIntSecs(); total += point->rppb + (point->rppb>180?-360:0); } } if (secs > 0.0f) setValue(total / secs); else setValue(0.0); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgRPPB(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgLPPE : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgLPPE) public: AvgLPPE() { setSymbol("average_lppe"); setInternalName("Average Left Power Phase End"); } void initialize() { setName(tr("Average Left Power Phase End")); setMetricUnits(tr("°")); setType(RideMetric::Average); setPrecision(0); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->lppe) { // end has to be > 0 double total = 0.0f; double secs = 0.0f; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->lppe > 0) { secs += ride->recIntSecs(); total += point->lppe; } } if (secs > 0.0f) setValue(total / secs); else setValue(0.0); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgLPPE(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgRPPE : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgRPPE) public: AvgRPPE() { setSymbol("average_rppe"); setInternalName("Average Right Power Phase End"); } void initialize() { setName(tr("Average Right Power Phase End")); setMetricUnits(tr("°")); setType(RideMetric::Average); setPrecision(0); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->rppe) { double total = 0.0f; double secs = 0.0f; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->rppe > 0) { // end has to be > 0 secs += ride->recIntSecs(); total += point->rppe; } } if (secs > 0.0f) setValue(total / secs); else setValue(0.0); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgRPPE(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgLPPPB : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgLPPPB) public: AvgLPPPB() { setSymbol("average_lpppb"); setInternalName("Average Left Peak Power Phase Start"); } void initialize() { setName(tr("Average Left Peak Power Phase Start")); setMetricUnits(tr("°")); setType(RideMetric::Average); setPrecision(0); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->lpppb) { double total = 0.0f; double secs = 0.0f; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->lpppe>0) { // use for average if we have an end secs += ride->recIntSecs(); total += point->lpppb + (point->lpppb>180?-360:0); } } if (secs > 0.0f) setValue(total / secs); else setValue(0.0); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgLPPPB(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgRPPPB : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgRPPPB) public: AvgRPPPB() { setSymbol("average_rpppb"); setInternalName("Average Right Peak Power Phase Start"); } void initialize() { setName(tr("Average Right Peak Power Phase Start")); setMetricUnits(tr("°")); setType(RideMetric::Average); setPrecision(0); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->rpppb) { double total = 0.0f; double secs = 0.0f; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->rpppe>0) { // use for average if we have an end secs += ride->recIntSecs(); total += point->rpppb + (point->rpppb>180?-360:0); } } if (secs > 0.0f) setValue(total / secs); else setValue(0.0); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgRPPPB(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgLPPPE : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgLPPPE) public: AvgLPPPE() { setSymbol("average_lpppe"); setInternalName("Average Left Peak Power Phase End"); } void initialize() { setName(tr("Average Left Peak Power Phase End")); setMetricUnits(tr("°")); setType(RideMetric::Average); setPrecision(0); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->lpppe) { double total = 0.0f; double secs = 0.0f; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->lpppe > 0) { // end has to be > 0 secs += ride->recIntSecs(); total += point->lpppe; } } if (secs > 0.0f) setValue(total / secs); else setValue(0.0); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgLPPPE(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgRPPPE : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgRPPPE) public: AvgRPPPE() { setSymbol("average_rpppe"); setInternalName("Average Right Peak Power Phase End"); } void initialize() { setName(tr("Average Right Peak Power Phase End")); setMetricUnits(tr("°")); setType(RideMetric::Average); setPrecision(0); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &, const Context *) { if (ride->areDataPresent()->rpppe) { double total = 0.0f; double secs = 0.0f; foreach (const RideFilePoint *point, ride->dataPoints()) { if (point->rpppe > 0) { // end has to be > 0 secs += ride->recIntSecs(); total += point->rpppe; } } if (secs > 0.0f) setValue(total / secs); else setValue(0.0); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgRPPPE(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgLPP : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgLPP) double average_lppb; double average_lppe; public: AvgLPP() { setSymbol("average_lpp"); setInternalName("Average Left Power Phase Length"); } void initialize() { setName(tr("Average Left Power Phase Length")); setMetricUnits(tr("°")); setType(RideMetric::Average); setPrecision(0); } void compute(const RideFile *, const Zones *, int, const HrZones *, int, const QHash &deps, const Context *) { average_lppb = deps.value("average_lppb")->value(true); average_lppe = deps.value("average_lppe")->value(true); if (average_lppe>0) { setValue(average_lppe-average_lppb); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgLPP(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgRPP : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgRPP) double average_rppb; double average_rppe; public: AvgRPP() { setSymbol("average_rpp"); setInternalName("Average Right Power Phase Length"); } void initialize() { setName(tr("Average Right Power Phase Length")); setMetricUnits(tr("°")); setType(RideMetric::Average); setPrecision(0); } void compute(const RideFile *, const Zones *, int, const HrZones *, int, const QHash &deps, const Context *) { average_rppb = deps.value("average_rppb")->value(true); average_rppe = deps.value("average_rppe")->value(true); if (average_rppe>0) { setValue(average_rppe-average_rppb); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgRPP(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgLPPP : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgLPPP) double average_lpppb; double average_lpppe; public: AvgLPPP() { setSymbol("average_lppp"); setInternalName("Average Peak Left Power Phase Length"); } void initialize() { setName(tr("Average Left Peak Power Phase Length")); setMetricUnits(tr("°")); setType(RideMetric::Average); setPrecision(0); } void compute(const RideFile *, const Zones *, int, const HrZones *, int, const QHash &deps, const Context *) { average_lpppb = deps.value("average_lpppb")->value(true); average_lpppe = deps.value("average_lpppe")->value(true); if (average_lpppe>0) { setValue(average_lpppe-average_lpppb); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgLPPP(*this); } }; ////////////////////////////////////////////////////////////////////////////// class AvgRPPP : public RideMetric { Q_DECLARE_TR_FUNCTIONS(AvgRPPP) double average_rpppb; double average_rpppe; public: AvgRPPP() { setSymbol("average_rppp"); setInternalName("Average Right Peak Power Phase Length"); } void initialize() { setName(tr("Average Right Peak Power Phase Length")); setMetricUnits(tr("°")); setType(RideMetric::Average); setPrecision(0); } void compute(const RideFile *, const Zones *, int, const HrZones *, int, const QHash &deps, const Context *) { average_rpppb = deps.value("average_rpppb")->value(true); average_rpppe = deps.value("average_rpppe")->value(true); if (average_rpppe>0) { setValue(average_rpppe-average_rpppb); } else { setValue(0.0); } } RideMetric *clone() const { return new AvgRPPP(*this); } }; static bool addLeftRight() { QVector deps; RideMetricFactory::instance().addMetric(AvgLTE(), &deps); RideMetricFactory::instance().addMetric(AvgRTE(), &deps); RideMetricFactory::instance().addMetric(AvgLPS(), &deps); RideMetricFactory::instance().addMetric(AvgRPS(), &deps); RideMetricFactory::instance().addMetric(AvgLPCO(), &deps); RideMetricFactory::instance().addMetric(AvgRPCO(), &deps); RideMetricFactory::instance().addMetric(AvgLPPB(), &deps); RideMetricFactory::instance().addMetric(AvgRPPB(), &deps); RideMetricFactory::instance().addMetric(AvgLPPE(), &deps); RideMetricFactory::instance().addMetric(AvgRPPE(), &deps); RideMetricFactory::instance().addMetric(AvgLPPPB(), &deps); RideMetricFactory::instance().addMetric(AvgRPPPB(), &deps); RideMetricFactory::instance().addMetric(AvgLPPPE(), &deps); RideMetricFactory::instance().addMetric(AvgRPPPE(), &deps); deps.append("average_lppb"); deps.append("average_lppe"); deps.append("average_rppb"); deps.append("average_rppe"); deps.append("average_lpppb"); deps.append("average_lpppe"); deps.append("average_rpppb"); deps.append("average_rpppe"); RideMetricFactory::instance().addMetric(AvgLPP(), &deps); RideMetricFactory::instance().addMetric(AvgRPP(), &deps); RideMetricFactory::instance().addMetric(AvgLPPP(), &deps); RideMetricFactory::instance().addMetric(AvgRPPP(), &deps); return true; } static bool leftRightAdded = addLeftRight(); ////////////////////////////////////////////////////////////////////////////// struct TotalCalories : public RideMetric { Q_DECLARE_TR_FUNCTIONS(TotalCalories) private: double average_hr, athlete_weight, duration, athlete_age; public: TotalCalories() { setSymbol("total_kcalories"); setInternalName("Calories"); } void initialize() { setName(tr("Calories (HR)")); setMetricUnits(tr("kcal")); setImperialUnits(tr("kcal")); setType(RideMetric::Total); } void compute(const RideFile *ride, const Zones *, int, const HrZones *, int, const QHash &deps, const Context *context) { average_hr = deps.value("average_hr")->value(true); athlete_weight = deps.value("athlete_weight")->value(true); duration = deps.value("time_riding")->value(true); // time_riding or workout_time ? athlete_age = ride->startTime().date().year() - appsettings->cvalue(context->athlete->cyclist, GC_DOB).toDate().year(); bool male = appsettings->cvalue(context->athlete->cyclist, GC_SEX).toInt() == 0; double kcalories = 0.0; if (duration>0 && average_hr>0 && athlete_weight>0 && athlete_age>0) { //Male: ((-55.0969 + (0.6309 x HR) + (0.1988 x W) + (0.2017 x A))/4.184) x 60 x T //Female: ((-20.4022 + (0.4472 x HR) - (0.1263 x W) + (0.074 x A))/4.184) x 60 x T if (male) kcalories = ((-55.0969 + (0.6309 * average_hr) + (0.1988 * athlete_weight) + (0.2017 * athlete_age))/4.184) * duration/60; else kcalories = ((-20.4022 + (0.4472 * average_hr) + (0.1263 * athlete_weight) + (0.074 * athlete_age))/4.184) * duration/60; } setValue(kcalories); } bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("H"); } RideMetric *clone() const { return new TotalCalories(*this); } }; static bool addTotalCalories() { QVector deps; deps.append("average_hr"); deps.append("time_riding"); deps.append("athlete_weight"); RideMetricFactory::instance().addMetric(TotalCalories(), &deps); return true; } static bool totalCaloriesAdded = addTotalCalories(); ///////////////////////////////////////////////////////////////////////////////