diff --git a/src/FileIO/FitRideFile.cpp b/src/FileIO/FitRideFile.cpp index 1e1f6682e..dabd2b673 100644 --- a/src/FileIO/FitRideFile.cpp +++ b/src/FileIO/FitRideFile.cpp @@ -584,12 +584,24 @@ struct FitFileReaderState QString getNameForExtraNative(int native_num) { switch (native_num) { + case 40: // STANCE_TIME_PERCENT + return "STANCETIMEPERCENT"; // Stance Time Percent + + case 42: // ACTIVITY_TYPE + return "ACTIVITYTYPE"; // Activity Type + case 47: // COMBINED_PEDAL_SMOOTHNES return "COMBINEDSMOOTHNESS"; //Combined Pedal Smoothness case 81: // BATTERY_SOC return "BATTERYSOC"; + case 83: // VERTICAL_RATIO + return "VERTICALRATIO"; // Vertical Ratio + + case 85: // STEP_LENGTH + return "STEPLENGTH"; // Step Length + default: return QString("FIELD_%1").arg(native_num); } @@ -598,6 +610,13 @@ struct FitFileReaderState float getScaleForExtraNative(int native_num) { switch (native_num) { + case 40: // STANCE_TIME_PERCENT + case 83: // VERTICAL_RATIO + return 100.0; + + case 85: // STEP_LENGTH + return 10.0; + case 47: // COMBINED_PEDAL_SMOOTHNES case 81: // BATTERY_SOC return 2.0; @@ -1625,8 +1644,9 @@ struct FitFileReaderState rvert = value / 100.0f; break; - //case 40: // GROUND CONTACT TIME PERCENT - //break; + case 40: // GROUND CONTACT TIME PERCENT + native_num = -1; + break; case 41: // GROUND CONTACT TIME if (!native_profile && field.deve_idx>-1) @@ -1635,10 +1655,9 @@ struct FitFileReaderState rcontact = value / 10.0f; break; - //case 42: // ACTIVITY_TYPE - // // TODO We should know/test value for run - // run = true; - // break; + case 42: // ACTIVITY_TYPE + native_num = -1; + break; case 43: // LEFT_TORQUE_EFFECTIVENESS leftTorqueEff = value / 2.0; @@ -1703,9 +1722,15 @@ struct FitFileReaderState rightTopPeakPowerPhase = round(valueList.at(0) * 360.0/256); rightBottomPeakPowerPhase = round(valueList.at(1) * 360.0/256); break; + case 83: // VERTICAL_RATIO + native_num = -1; + break; case 84: // Left right balance lrbalance = value/100.0; break; + case 85: // STEP_LENGTH + native_num = -1; + break; case 87: // ??? break; diff --git a/src/Metrics/LeftRightBalance.cpp b/src/Metrics/LeftRightBalance.cpp index da503943c..6488cc0a1 100644 --- a/src/Metrics/LeftRightBalance.cpp +++ b/src/Metrics/LeftRightBalance.cpp @@ -43,7 +43,7 @@ class LeftRightBalance : public RideMetric { setImperialUnits(tr("%")); setType(RideMetric::Average); setPrecision(1); - setDescription(tr("Left/Right Balance shows the proportion of power coming from each pedal.")); + setDescription(tr("Left/Right Balance shows the proportion of power coming from each pedal for rides and the proportion of Ground Contact Time from each leg for runs.")); } void compute(RideItem *item, Specification spec, const QHash &) { @@ -60,7 +60,7 @@ class LeftRightBalance : public RideMetric { RideFileIterator it(item->ride(), spec); while (it.hasNext()) { struct RideFilePoint *point = it.next(); - if (point->cad && point->lrbalance > 0.0f) { + if ((point->cad || point->rcad) && point->lrbalance > 0.0f) { total += point->lrbalance; ++count; } diff --git a/src/Metrics/RideMetric.cpp b/src/Metrics/RideMetric.cpp index 14c3c7faf..24da3bd06 100644 --- a/src/Metrics/RideMetric.cpp +++ b/src/Metrics/RideMetric.cpp @@ -157,7 +157,8 @@ // 147 06 May 2018 Ale Martinez Added PeakHr metrics and HrZone // 148 27 Jul 2018 Ale Martinez Changed Hrv Measures to retrun 0 when no record for the date // 149 04 Jan 2019 Mark Liversedge PowerIndex metric to score performance tests/intervals/rides vs typical athlete PD curve -int DBSchemaVersion = 149; +// 150 28 Mar 2019 Ale Martinez Additional Running Dynamics metrics +int DBSchemaVersion = 150; RideMetricFactory *RideMetricFactory::_instance; QVector RideMetricFactory::noDeps; diff --git a/src/Metrics/RunMetrics.cpp b/src/Metrics/RunMetrics.cpp index 844bfc587..7a2e5a81b 100644 --- a/src/Metrics/RunMetrics.cpp +++ b/src/Metrics/RunMetrics.cpp @@ -21,6 +21,7 @@ #include "Context.h" #include "Settings.h" #include "RideItem.h" +#include "RideFile.h" #include "LTMOutliers.h" #include "Units.h" #include "Zones.h" @@ -236,7 +237,7 @@ struct AvgRunVerticalOscillation : public RideMetric { while (it.hasNext()) { struct RideFilePoint *point = it.next(); - if (point->rcontact > 0) { + if (point->rvert > 0) { total += point->rvert; ++count; } @@ -441,3 +442,183 @@ static bool avgStrideLengthAdded = RideMetricFactory::instance().addMetric(AvgStrideLength()); ////////////////////////////////////////////////////////////////////////////// + +struct AvgRunVerticalRatio : public RideMetric { + Q_DECLARE_TR_FUNCTIONS(AvgRunVerticalRatio) + + double total, count; + + public: + + AvgRunVerticalRatio() + { + setSymbol("average_run_vert_ratio"); + setInternalName("Average Vertical Ratio"); + } + + void initialize() { + setName(tr("Average Vertical Ratio")); + setMetricUnits(tr("%")); + setImperialUnits(tr("%")); + setPrecision(1); + setType(RideMetric::Average); + setDescription(tr("Average Vertical Ratio (%): Vertical Oscillation / Step Length")); + } + + void compute(RideItem *item, Specification spec, const QHash &) { + + // no ride or no samples + if (spec.isEmpty(item->ride())) { + setValue(RideFile::NIL); + setCount(0); + return; + } + + total = count = 0; + + int idx = 0; + RideFileIterator it(item->ride(), spec); + while (it.hasNext()) { + struct RideFilePoint *point = it.next(); + + double vert_ratio = item->ride()->xdataValue(point, idx, "EXTRA", "VERTICALRATIO", RideFile::SPARSE); + if (point->rcad > 0 && vert_ratio > 0) { + total += vert_ratio; + ++count; + } + } + setValue(count > 0 ? total / count : count); + setCount(count); + } + + bool isRelevantForRide(const RideItem *ride) const { return (ride->present.contains("R") && ride->isRun); } + + MetricClass classification() const { return Undefined; } + MetricValidity validity() const { return Unknown; } + RideMetric *clone() const { return new AvgRunVerticalRatio(*this); } +}; + +static bool avgRunVerticalRatio = + RideMetricFactory::instance().addMetric(AvgRunVerticalRatio()); + +////////////////////////////////////////////////////////////////////////////// + +struct AvgRunStanceTimePercent : public RideMetric { + Q_DECLARE_TR_FUNCTIONS(AvgRunStanceTimePercent) + + double total, count; + + public: + + AvgRunStanceTimePercent() + { + setSymbol("average_run_stance_time_percent"); + setInternalName("Average Stance Time Percent"); + } + + void initialize() { + setName(tr("Average Stance Time Percent")); + setMetricUnits(tr("%")); + setImperialUnits(tr("%")); + setPrecision(1); + setType(RideMetric::Average); + setDescription(tr("Average Stance Time Percent (%): Ground Contact Time / Step Time")); + } + + void compute(RideItem *item, Specification spec, const QHash &) { + + // no ride or no samples + if (spec.isEmpty(item->ride())) { + setValue(RideFile::NIL); + setCount(0); + return; + } + + total = count = 0; + + int idx = 0; + RideFileIterator it(item->ride(), spec); + while (it.hasNext()) { + struct RideFilePoint *point = it.next(); + + double stance_time_pct = item->ride()->xdataValue(point, idx, "EXTRA", "STANCETIMEPERCENT", RideFile::SPARSE); + if (point->rcad > 0 && stance_time_pct > 0) { + total += stance_time_pct; + ++count; + } + } + setValue(count > 0 ? total / count : count); + setCount(count); + } + + bool isRelevantForRide(const RideItem *ride) const { return (ride->present.contains("R") && ride->isRun); } + + MetricClass classification() const { return Undefined; } + MetricValidity validity() const { return Unknown; } + RideMetric *clone() const { return new AvgRunStanceTimePercent(*this); } +}; + +static bool avgRunStanceTimePercent = + RideMetricFactory::instance().addMetric(AvgRunStanceTimePercent()); + +////////////////////////////////////////////////////////////////////////////// + +struct AvgStepLength : public RideMetric { + Q_DECLARE_TR_FUNCTIONS(AvgStepLength) + + double total, count; + + public: + + AvgStepLength() + { + setSymbol("average_step_length"); + setInternalName("Average Step Length"); + } + + void initialize() { + setName(tr("Average Step Length")); + setMetricUnits(tr("mm")); + setImperialUnits(tr("mm")); + setPrecision(0); + setType(RideMetric::Average); + setDescription(tr("Average Step Length: average single step (L to R / R to L) length in mm")); + } + + void compute(RideItem *item, Specification spec, const QHash &) { + + // no ride or no samples + if (spec.isEmpty(item->ride())) { + setValue(RideFile::NIL); + setCount(0); + return; + } + + total = count = 0; + + int idx = 0; + RideFileIterator it(item->ride(), spec); + while (it.hasNext()) { + struct RideFilePoint *point = it.next(); + + double step_length = item->ride()->xdataValue(point, idx, "EXTRA", "STEPLENGTH", RideFile::SPARSE); + if (point->rcad > 0 && step_length > 0) { + total += step_length; + ++count; + } + } + setValue(count > 0 ? total / count : count); + setCount(count); + } + + bool isRelevantForRide(const RideItem *ride) const { return (ride->present.contains("S") && ride->present.contains("C") && ride->isRun); } + + MetricClass classification() const { return Undefined; } + MetricValidity validity() const { return Unknown; } + RideMetric *clone() const { return new AvgStepLength(*this); } +}; + +static bool avgStepLengthAdded = + RideMetricFactory::instance().addMetric(AvgStepLength()); + +//////////////////////////////////////////////////////////////////////////////