Files
GoldenCheetah/src/Metrics/BasicRideMetrics.cpp
Alejandro Martinez 3987833102 Allow Banister to use other performance metric besides Power Index (#3136)
* Allow Banister to use other performance metric besides Power Index
Performance metric can be selected from any Peak metric in LTM Charts
Datafilter now is banister(load_metric, perf_metric, ...)

* Travis-ci - R3.6 install update key
According to https://cloud.r-project.org/bin/linux/ubuntu/README
2019-08-04 18:01:09 -03:00

3841 lines
114 KiB
C++

/*
* 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 "IntervalItem.h"
#include "LTMOutliers.h"
#include "Units.h"
#include "Zones.h"
#include "cmath"
#include <assert.h>
#include <algorithm>
#include <QVector>
#include <QApplication>
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(""));
setDescription(tr("Activity Count"));
}
void compute(RideItem *, Specification, const QHash<QString,RideMetric*> &) {
setValue(1);
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new RideCount(*this); }
};
static bool countAdded =
RideMetricFactory::instance().addMetric(RideCount());
//////////////////////////////////////////////////////////////////////////////
class ToExhaustion : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(ToExhaustion)
public:
ToExhaustion()
{
setSymbol("ride_te");
setInternalName("To Exhaustion");
}
void initialize() {
setName(tr("To Exhaustion"));
setMetricUnits(tr(""));
setImperialUnits(tr(""));
setDescription(tr("Count of exhaustion points marked by the user in an activity"));
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &) {
int c=0;
if (item && item->ride()) {
foreach(RideFilePoint *rp, item->ride()->referencePoints()) {
if (rp->secs > 0) c++;
}
}
setValue(c);
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new ToExhaustion(*this); }
};
static bool teAdded =
RideMetricFactory::instance().addMetric(ToExhaustion());
class ElapsedTime : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(ElapsedTime)
public:
ElapsedTime()
{
setSymbol("elapsed_time");
setInternalName("Elapsed Time");
}
void initialize() {
setName(tr("Elapsed Time"));
setMetricUnits(tr("secs"));
setImperialUnits(tr(""));
setDescription(tr("Only useful for intervals, time the interval started"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
Q_UNUSED(item)
setValue(0);
if (spec.interval()) setValue(spec.interval()->start);
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new ElapsedTime(*this); }
};
static bool etAdded =
RideMetricFactory::instance().addMetric(ElapsedTime());
//////////////////////////////////////////////////////////////////////////////
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"));
setDescription(tr("Total Duration including pauses a.k.a. Elapsed Time"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
RideFileIterator it(item->ride(), spec);
// just subtract first timepoint from last timepoint
if (it.last() && it.first())
seconds = it.last()->secs - it.first()->secs + item->ride()->recIntSecs();
else seconds = RideFile::NA;
setValue(seconds);
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new WorkoutTime(*this); }
};
static bool workoutTimeAdded =
RideMetricFactory::instance().addMetric(WorkoutTime());
//////////////////////////////////////////////////////////////////////////////
class TimeRecording : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(TimeRecording)
double secsRecording;
public:
TimeRecording() : secsRecording(0.0)
{
setSymbol("time_recording");
setInternalName("Time Recording");
}
bool isTime() const { return true; }
void initialize() {
setName(tr("Time Recording"));
setMetricUnits(tr("seconds"));
setImperialUnits(tr("seconds"));
setDescription(tr("Time when device was recording, excludes gaps in recording due to pauses or missing samples"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
secsRecording = 0;
// loop through and count
for (RideFileIterator it(item->ride(), spec); it.hasNext(); it.next())
secsRecording += item->ride()->recIntSecs();
setValue(secsRecording);
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new TimeRecording(*this); }
};
static bool timeRecordingAdded =
RideMetricFactory::instance().addMetric(TimeRecording());
//////////////////////////////////////////////////////////////////////////////
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"));
setDescription(tr("Time with speed or cadence different from zero"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
secsMovingOrPedaling = 0;
// must have speed and cadence
if (item->ride()->areDataPresent()->kph || item->ride()->areDataPresent()->cad ) {
// loop through and count
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if ((point->kph > 0.0) || (point->cad > 0.0))
secsMovingOrPedaling += item->ride()->recIntSecs();
}
}
setValue(secsMovingOrPedaling);
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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), prevalt(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"));
setDescription(tr("Time with low speed and elevation gain but no power nor cadence"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
secsCarrying = 0;
if (item->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;
RideFileIterator it(item->ride(), spec);
bool first = true;
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
// 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 += item->ride()->recIntSecs();
}
}
setValue(secsCarrying);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("Elevation gained at low speed with no power nor cadence"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
// 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;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
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);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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(3);
setConversion(MILES_PER_KM);
setDescription(tr("Total Distance in km or miles"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
// 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 (item->ride()->areDataPresent()->km) {
RideFileIterator it(item->ride(), spec);
// just subtract first from last
if (it.last() && it.first())
km = it.last()->km - it.first()->km;
else km = RideFile::NA;
if (km != RideFile::NA && item->ride()->areDataPresent()->kph)
km += it.first()->kph / 3600.0 * item->ride()->recIntSecs();
} else {
km = 0;
}
setValue(km);
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new TotalDistance(*this); }
};
static bool totalDistanceAdded =
RideMetricFactory::instance().addMetric(TotalDistance());
// climb rating is essentially elev gain ^2 / distance
// a concept raised by Dan Conelly on his blog
class ClimbRating : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(ClimbRating)
double secsMoving;
double km;
public:
ClimbRating() : secsMoving(0.0), km(0.0)
{
setSymbol("climb_rating");
setInternalName("Climb Rating");
}
void initialize() {
setName(tr("Climb Rating"));
setMetricUnits(tr(""));
setImperialUnits(tr(""));
setType(RideMetric::Total);
setPrecision(0);
setDescription(tr("According to Dan Conelly: Elevation Gain ^2 / Distance / 1000, 100 is HARD"));
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &deps) {
double rating = 0.0f;
double distance = deps.value("total_distance")->value(true);
if (item->ride()->areDataPresent()->alt) {
double ele = deps.value("elevation_gain")->value(true);
// 100 is HARD !
if (ele >0 && distance >0) {
rating = (ele * ele) / distance;
rating /= 1000.0f;
}
}
setValue(rating);
setCount(1);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new ClimbRating(*this); }
};
static bool climbRatingAdded =
RideMetricFactory::instance().addMetric(
ClimbRating(), &(QVector<QString>() << "total_distance" << "elevation_gain"));
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);
setDescription(tr("Weight in kg or lbs: first from Athlete body measurements, then from Activity metadata and last from Athlete configuration with 75kg default"));
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &) {
// body measures first
double weight = item->getWeight();
// from metadata
if (!weight) weight = item->getText("Weight", "0.0").toDouble();
// global options
if (!weight) weight = appsettings->cvalue(item->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);
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new AthleteWeight(*this); }
};
static bool athleteWeightAdded =
RideMetricFactory::instance().addMetric(AthleteWeight());
//////////////////////////////////////////////////////////////////////////////
class AthleteFat : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AthleteFat)
double kg;
public:
AthleteFat() : kg(0.0)
{
setSymbol("athlete_fat");
setInternalName("Athlete Bodyfat");
}
void initialize() {
setName(tr("Athlete Bodyfat"));
setType(RideMetric::Average);
setMetricUnits(tr("kg"));
setImperialUnits(tr("lbs"));
setPrecision(2);
setConversion(LB_PER_KG);
setDescription(tr("Athlete bodyfat in kg or lbs from body measurements"));
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &) {
setValue(item->getWeight(BodyMeasure::FatKg));
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new AthleteFat(*this); }
};
static bool athleteFatAdded =
RideMetricFactory::instance().addMetric(AthleteFat());
//////////////////////////////////////////////////////////////////////////////
class AthleteBones : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AthleteBones)
double kg;
public:
AthleteBones() : kg(0.0)
{
setSymbol("athlete_bones");
setInternalName("Athlete Bones");
}
void initialize() {
setName(tr("Athlete Bones"));
setType(RideMetric::Average);
setMetricUnits(tr("kg"));
setImperialUnits(tr("lbs"));
setPrecision(2);
setConversion(LB_PER_KG);
setDescription(tr("Athlete bones in kg or lbs from body measurements"));
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &) {
setValue(item->getWeight(BodyMeasure::BonesKg));
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new AthleteBones(*this); }
};
static bool athleteBonesAdded =
RideMetricFactory::instance().addMetric(AthleteBones());
//////////////////////////////////////////////////////////////////////////////
class AthleteMuscles : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AthleteMuscles)
double kg;
public:
AthleteMuscles() : kg(0.0)
{
setSymbol("athlete_muscles");
setInternalName("Athlete Muscles");
}
void initialize() {
setName(tr("Athlete Muscles"));
setType(RideMetric::Average);
setMetricUnits(tr("kg"));
setImperialUnits(tr("lbs"));
setPrecision(2);
setConversion(LB_PER_KG);
setDescription(tr("Athlete muscles in kg or lbs from body measurements"));
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &) {
setValue(item->getWeight(BodyMeasure::MuscleKg));
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new AthleteMuscles(*this); }
};
static bool athleteMusclesAdded =
RideMetricFactory::instance().addMetric(AthleteMuscles());
//////////////////////////////////////////////////////////////////////////////
class AthleteLean : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AthleteLean)
double kg;
public:
AthleteLean() : kg(0.0)
{
setSymbol("athlete_lean");
setInternalName("Athlete Lean Weight");
}
void initialize() {
setName(tr("Athlete Lean Weight"));
setType(RideMetric::Average);
setMetricUnits(tr("kg"));
setImperialUnits(tr("lbs"));
setPrecision(2);
setConversion(LB_PER_KG);
setDescription(tr("Lean Weight in kg or lbs from body measurements"));
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &) {
setValue(item->getWeight(BodyMeasure::LeanKg));
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new AthleteLean(*this); }
};
static bool athleteLeanAdded =
RideMetricFactory::instance().addMetric(AthleteLean());
//////////////////////////////////////////////////////////////////////////////
class AthleteFatP : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AthleteFatP)
double kg;
public:
AthleteFatP() : kg(0.0)
{
setSymbol("athlete_fat_percent");
setInternalName("Athlete Bodyfat Percent");
}
void initialize() {
setName(tr("Athlete Bodyfat Percent"));
setType(RideMetric::Average);
setMetricUnits(tr("%"));
setImperialUnits(tr("%"));
setPrecision(1);
setDescription(tr("Bodyfat in Percent from body measurements"));
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &) {
setValue(item->getWeight(BodyMeasure::FatPercent));
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new AthleteFatP(*this); }
};
static bool athleteFatPAdded =
RideMetricFactory::instance().addMetric(AthleteFatP());
//////////////////////////////////////////////////////////////////////////////
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);
setDescription(tr("Elevation Gain in meters of feets"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
// 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;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
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);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("Elevation Loss in meters of feets"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
// 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;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
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);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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"));
setDescription(tr("Total Work in kJ computed from power data"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
joules = 0;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->watts >= 0.0)
joules += point->watts * item->ride()->recIntSecs();
}
setValue(joules/1000);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("P") || (!ride->isSwim && !ride->isRun); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("Average Speed in kph or mph, computed from distance over time when speed not zero"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &deps) {
assert(deps.contains("total_distance"));
km = deps.value("total_distance")->value(true);
if (item->ride()->areDataPresent()->kph) {
secsMoving = 0;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->kph > 0.0) secsMoving += item->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);
}
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new AvgSpeed(*this); }
};
static bool avgSpeedAdded =
RideMetricFactory::instance().addMetric(
AvgSpeed(), &(QVector<QString>() << "total_distance" << "workout_time"));
//////////////////////////////////////////////////////////////////////////////
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);
setDescription(tr("Average Power from all samples with power greater than or equal to zero"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (item->ride() == NULL || !item->ride()->areDataPresent()->watts || item->ride()->dataPoints().count() == 0) {
setValue(RideFile::NIL);
setCount(0);
return;
}
total = count = 0;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->watts >= 0.0) {
total += point->watts;
++count;
}
}
setValue(count > 0 ? total / count : 0);
setCount(count);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("P") || (!ride->isSwim && !ride->isRun); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("Average Muscle Oxygen Saturation, the percentage of hemoglobin that is carrying oxygen."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (item->ride() == NULL || !item->ride()->areDataPresent()->smo2 || item->ride()->dataPoints().count() == 0) {
setValue(RideFile::NIL);
setCount(0);
return;
}
total = count = 0;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->smo2 > 0.0f) { // SmO2 should always be > 0.0f
total += point->smo2;
++count;
}
}
setValue(count > 0 ? total / count : 0);
setCount(count);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("O"); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new AvgSmO2(*this); }
};
static bool avgSmO2Added =
RideMetricFactory::instance().addMetric(AvgSmO2());
struct AvgtHb : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgtHb)
double count, total;
public:
AvgtHb()
{
setSymbol("average_tHb");
setInternalName("Average tHb");
}
void initialize() {
setName(tr("Average tHb"));
setMetricUnits(tr("g/dL"));
setImperialUnits(tr("g/dL"));
setType(RideMetric::Average);
setPrecision(2);
setDescription(tr("Average total hemoglobin concentration. The total grams of hemoglobin per deciliter."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (item->ride() == NULL || !item->ride()->areDataPresent()->thb || item->ride()->dataPoints().count() == 0) {
setValue(RideFile::NIL);
setCount(0);
return;
}
total = count = 0.0f;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->thb > 0.0f) {
total += point->thb;
++count;
}
}
setValue(count > 0.0f ? total / count : 0.0f);
setCount(count);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("O"); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new AvgtHb(*this); }
};
static bool avgtHbAdded =
RideMetricFactory::instance().addMetric(AvgtHb());
//////////////////////////////////////////////////////////////////////////////
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);
setDescription(tr("Average altitude power. Recorded power adjusted to take into account the effect of altitude on vo2max and thus power output."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
total = count = 0;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->apower >= 0.0) {
total += point->apower;
++count;
}
}
setValue(count > 0 ? total / count : 0);
setCount(count);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("P") || (!ride->isSwim && !ride->isRun); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("Average Power without zero values, it gives inflated values when frecuent coasting is present"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (item->ride() == NULL || !item->ride()->areDataPresent()->watts || item->ride()->dataPoints().count() == 0) {
setValue(RideFile::NIL);
setCount(0);
return;
}
total = count = 0;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->watts > 0.0) {
total += point->watts;
++count;
}
}
setValue(count > 0 ? total / count : 0);
setCount(count);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("P") || (!ride->isSwim && !ride->isRun); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("Average Heart Rate computed for samples when hr is greater than zero"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (item->ride() == NULL || !item->ride()->areDataPresent()->hr || item->ride()->dataPoints().count() == 0) {
setValue(RideFile::NIL);
setCount(0);
return;
}
total = count = 0;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
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"); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new AvgHeartRate(*this); }
};
static bool avgHeartRateAdded =
RideMetricFactory::instance().addMetric(AvgHeartRate());
struct AvgCoreTemp : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgCoreTemp)
double total, count;
public:
AvgCoreTemp()
{
setSymbol("average_ct");
setInternalName("Average Core Temperature");
setPrecision(1);
}
void initialize() {
setName(tr("Average Core Temperature"));
setMetricUnits(tr("C"));
setImperialUnits(tr("C"));
setType(RideMetric::Average);
setDescription(tr("Average Core Temperature. The core body temperature estimate is based on HR data"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
total = count = 0;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->tcore > 0) {
total += point->tcore;
++count;
}
}
setValue(count > 0 ? total / count : 0);
setCount(count);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("H"); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new AvgCoreTemp(*this); }
};
static bool avgCTAdded =
RideMetricFactory::instance().addMetric(AvgCoreTemp());
///////////////////////////////////////////////////////////////////////////////
struct HeartBeats : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(HeartBeats)
double total;
public:
HeartBeats()
{
setSymbol("heartbeats");
setInternalName("Heartbeats");
}
void initialize() {
setName(tr("Heartbeats"));
setMetricUnits(tr("beats"));
setImperialUnits(tr("beats"));
setType(RideMetric::Total);
setDescription(tr("Total Heartbeats"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
total = 0;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
total += (point->hr / 60) * item->ride()->recIntSecs();
}
setValue(total);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("H"); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new HeartBeats(*this); }
};
static bool hbAdded =
RideMetricFactory::instance().addMetric(HeartBeats());
////////////////////////////////////////////////////////////////////////////////
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);
setDescription(tr("Power to Heart Rate Ratio in watts/bpm"));
}
void compute(RideItem *, Specification, const QHash<QString,RideMetric*> &deps) {
AvgHeartRate *hr = dynamic_cast<AvgHeartRate*>(deps.value("average_hr"));
AvgPower *pw = dynamic_cast<AvgPower*>(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(RideFile::NIL);
}
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("P") || (!ride->isSwim && !ride->isRun); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new HrPw(*this); }
};
static bool addHrPw()
{
QVector<QString> deps;
deps.append("average_power");
deps.append("average_hr");
RideMetricFactory::instance().addMetric(HrPw(), &deps);
return true;
}
static bool hrpwAdded = addHrPw();
////////////////////////////////////////////////////////////////////////////////
class Workbeat : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(Workbeats)
public:
Workbeat()
{
setSymbol("wb");
setInternalName("Workbeat stress");
}
void initialize() {
setName(tr("Workbeat stress"));
setImperialUnits("");
setMetricUnits("");
setPrecision(0);
setType(RideMetric::Total);
setDescription(tr("Work * Heartbeats / 100000"));
}
void compute(RideItem *, Specification, const QHash<QString,RideMetric*> &deps) {
TotalWork *work = dynamic_cast<TotalWork*>(deps.value("total_work"));
HeartBeats *hb = dynamic_cast<HeartBeats*>(deps.value("heartbeats"));
// if either zero we get zero
setValue((work->value() * hb->value()) / 100000.00f);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("P") || (!ride->isSwim && !ride->isRun); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new Workbeat(*this); }
};
static bool addWorkbeat()
{
QVector<QString> deps;
deps.append("total_work");
deps.append("heartbeats");
RideMetricFactory::instance().addMetric(Workbeat(), &deps);
return true;
}
static bool workbeatAdded = addWorkbeat();
//////////////////////////////////////////////////////////////////////
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);
setDescription(tr("Watts to RPE ratio"));
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &deps) {
double ratio = 0.0f;
AvgPower *pw = dynamic_cast<AvgPower*>(deps.value("average_power"));
double rpe = item->getText("RPE", "0").toDouble();
if (pw->value(true) > 100 && rpe > 0) { // ignore silly rides with low values
ratio = pw->value(true) / rpe;
}
setValue(ratio);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("P") || (!ride->isSwim && !ride->isRun); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new WattsRPE(*this); }
};
static bool addWattsRPE()
{
QVector<QString> deps;
deps.append("average_power");
RideMetricFactory::instance().addMetric(WattsRPE(), &deps);
return true;
}
static bool wattsRPEAdded = addWattsRPE();
///////////////////////////////////////////////////////////////////////////////
class APPercent : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(APPercent)
public:
APPercent()
{
setSymbol("ap_percent_max");
setInternalName("Power Percent of Max");
}
void initialize() {
setName(tr("Power Percent of Max"));
setImperialUnits("");
setMetricUnits("");
setPrecision(0);
setType(RideMetric::Average);
setDescription(tr("Power as percent of Pmax according to Power Zones"));
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &deps) {
double percent = 0.0f;
AvgPower *pw = dynamic_cast<AvgPower*>(deps.value("average_power"));
if (pw->value(true) > 0.0f && item->context->athlete->zones(item->isRun) && item->zoneRange >= 0) {
// get Pmax
double pmax = item->context->athlete->zones(item->isRun)->getPmax(item->zoneRange);
percent = pw->value(true)/pmax * 100;
}
setValue(percent);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("P") || (!ride->isSwim && !ride->isRun); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new APPercent(*this); }
};
static bool addAPPercent()
{
QVector<QString> deps;
deps.append("average_power");
RideMetricFactory::instance().addMetric(APPercent(), &deps);
return true;
}
static bool APPercentAdded = addAPPercent();
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
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);
setDescription(tr("Iso Power to Average Heart Rate ratio in watts/bpm"));
}
void compute(RideItem *, Specification, const QHash<QString,RideMetric*> &deps) {
AvgHeartRate *hr = dynamic_cast<AvgHeartRate*>(deps.value("average_hr"));
RideMetric *pw = dynamic_cast<RideMetric*>(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);
}
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("P") || (!ride->isSwim && !ride->isRun); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new HrNp(*this); }
};
static bool addHrNp()
{
QVector<QString> 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);
setDescription(tr("Average Cadence, computed when Cadence > 0"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
total = count = 0;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
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") && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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; }
// override to special case NA
QString toString(bool useMetricUnits) const {
if (value() == RideFile::NA) return "-";
return RideMetric::toString(useMetricUnits);
}
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);
setDescription(tr("Average Temp from activity data"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (item->ride() == NULL || !item->ride()->areDataPresent()->temp || item->ride()->dataPoints().count() == 0) {
setValue(RideFile::NA);
setCount(0);
return;
}
total = count = 0;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->temp != RideFile::NA) {
total += point->temp;
++count;
}
}
setValue(count > 0 ? (total / count) : count);
setCount(count);
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("Maximum Power"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->watts >= max)
max = point->watts;
}
setValue(max);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("P") || (!ride->isSwim && !ride->isRun); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("Maximum Muscle Oxygen Saturation, the percentage of hemoglobin that is carrying oxygen."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->smo2 >= max)
max = point->smo2;
}
setValue(max);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("O"); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new MaxSmO2(*this); }
};
static bool maxSmO2Added =
RideMetricFactory::instance().addMetric(MaxSmO2());
class MaxtHb : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(MaxtHb)
double max;
public:
MaxtHb() : max(0.0)
{
setSymbol("max_tHb");
setInternalName("Max tHb");
}
void initialize() {
setName(tr("Max tHb"));
setMetricUnits(tr("g/dL"));
setImperialUnits(tr("g/dL"));
setType(RideMetric::Peak);
setPrecision(2);
setDescription(tr("Maximum total hemoglobin concentration. The total grams of hemoglobin per deciliter."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->thb >= max)
max = point->thb;
}
setValue(max);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("O"); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new MaxtHb(*this); }
};
static bool maxtHbAdded =
RideMetricFactory::instance().addMetric(MaxtHb());
//////////////////////////////////////////////////////////////////////////////
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::Low);
setDescription(tr("Minimum Muscle Oxygen Saturation, the percentage of hemoglobin that is carrying oxygen."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
bool notset = true;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->smo2 >= 0.0f && (notset || point->smo2 < min)) {
min = point->smo2;
if (point->smo2 > 0.0f && notset)
notset = false;
}
}
setValue(min);
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new MinSmO2(*this); }
};
static bool minSmO2Added =
RideMetricFactory::instance().addMetric(MinSmO2());
class MintHb : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(MintHb)
double min;
public:
MintHb() : min(0.0)
{
setSymbol("min_tHb");
setInternalName("Min tHb");
}
void initialize() {
setName(tr("Min tHb"));
setMetricUnits(tr("g/dL"));
setImperialUnits(tr("g/dL"));
setType(RideMetric::Low);
setPrecision(2);
setDescription(tr("Minimum total hemoglobin concentration. The total grams of hemoglobin per deciliter."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
bool notset = true;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->thb > 0.0f && (notset || point->thb < min)) {
min = point->thb;
notset = false;
}
}
setValue(min);
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new MintHb(*this); }
};
static bool mintHb =
RideMetricFactory::instance().addMetric(MintHb());
//////////////////////////////////////////////////////////////////////////////
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);
setDescription(tr("Maximum Heart Rate."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->hr >= max)
max = point->hr;
}
setValue(max);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("H"); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new MaxHr(*this); }
};
static bool maxHrAdded =
RideMetricFactory::instance().addMetric(MaxHr());
//////////////////////////////////////////////////////////////////////////////
class MinHr : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(MinHr)
double min;
public:
MinHr() : min(0.0)
{
setSymbol("min_heartrate");
setInternalName("Min Heartrate");
}
void initialize() {
setName(tr("Min Heartrate"));
setMetricUnits(tr("bpm"));
setImperialUnits(tr("bpm"));
setType(RideMetric::Low);
setDescription(tr("Minimum Heart Rate."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
bool notset = true;
min = 0;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->hr > 0 && (notset || point->hr < min)) {
min = point->hr;
notset = false;
}
}
setValue(min);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("H"); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new MinHr(*this); }
};
static bool minHrAdded =
RideMetricFactory::instance().addMetric(MinHr());
//////////////////////////////////////////////////////////////////////////////
class MaxCT : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(MaxCT)
double max;
public:
MaxCT() : max(0.0)
{
setSymbol("max_ct");
setInternalName("Max Core Temperature");
setPrecision(1);
}
void initialize() {
setName(tr("Max Core Temperature"));
setMetricUnits(tr("C"));
setImperialUnits(tr("C"));
setType(RideMetric::Peak);
setDescription(tr("Maximum Core Temperature. The core body temperature estimate is based on HR data"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->tcore >= max)
max = point->tcore;
}
setValue(max);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("H"); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new MaxCT(*this); }
};
static bool maxCTAdded =
RideMetricFactory::instance().addMetric(MaxCT());
//////////////////////////////////////////////////////////////////////////////
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);
setDescription(tr("Maximum Speed"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double max = 0.0;
if (item->ride()->areDataPresent()->kph) {
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->kph > max) max = point->kph;
}
}
setValue(max);
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("Maximum Cadence"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double max = 0.0;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->cad > max) max = point->cad;
}
setValue(max);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("C") && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("Maximum Temperature"));
}
// override to special case NA
QString toString(bool useMetricUnits) const {
if (value() == RideFile::NA) return "-";
return RideMetric::toString(useMetricUnits);
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->temp) {
setValue(RideFile::NA);
setCount(0);
return;
}
double max = RideFile::NA;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->temp != RideFile::NA && point->temp > max) max = point->temp;
}
setValue(max);
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new MaxTemp(*this); }
};
static bool maxTempAdded =
RideMetricFactory::instance().addMetric(MaxTemp());
//////////////////////////////////////////////////////////////////////////////
class MinTemp : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(MinTemp)
public:
MinTemp()
{
setSymbol("min_temp");
setInternalName("Min Temp");
}
void initialize() {
setName(tr("Min Temp"));
setMetricUnits(tr("C"));
setImperialUnits(tr("F"));
setType(RideMetric::Low);
setPrecision(1);
setConversion(FAHRENHEIT_PER_CENTIGRADE);
setConversionSum(FAHRENHEIT_ADD_CENTIGRADE);
setDescription(tr("Minimum Temperature"));
}
// override to special case NA
QString toString(bool useMetricUnits) const {
if (value() == RideFile::NA) return "-";
return RideMetric::toString(useMetricUnits);
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->temp) {
setValue(RideFile::NA);
setCount(0);
return;
}
double min = 10000;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->temp != RideFile::NA && point->temp < min) min = point->temp;
}
setValue(min < 10000 ? min : (double)(RideFile::NA));
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new MinTemp(*this); }
};
static bool minTempAdded =
RideMetricFactory::instance().addMetric(MinTemp());
//////////////////////////////////////////////////////////////////////////////
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);
setDescription(tr("Heart Rate for which 95% of activity samples has lower HR values"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
QVector<double> hrs;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
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);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("H"); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("Velocita Ascensionale Media, average ascent speed in vertical meters per hour"));
}
void compute(RideItem *, Specification, const QHash<QString,RideMetric*> &deps) {
ElevationGain *el = dynamic_cast<ElevationGain*>(deps.value("elevation_gain"));
WorkoutTime *wt = dynamic_cast<WorkoutTime*>(deps.value("workout_time"));
setValue((el->value(true)*3600)/wt->value(true));
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new VAM(*this); }
};
static bool addVam()
{
QVector<QString> 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);
setDescription(tr("Relationship between altitude adjusted power and recorded power"));
}
void compute(RideItem *, Specification, const QHash<QString,RideMetric*> &deps) {
AAvgPower *aap = dynamic_cast<AAvgPower*>(deps.value("average_apower"));
AvgPower *ap = dynamic_cast<AvgPower*>(deps.value("average_power"));
setValue(((aap->value(true)-ap->value(true))/aap->value(true)) * 100.00f);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("P") || (!ride->isSwim && !ride->isRun); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new EOA(*this); }
};
static bool addEOA()
{
QVector<QString> 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);
setDescription(tr("Elevation Gain to Total Distance percent ratio"));
}
void compute(RideItem *, Specification, const QHash<QString,RideMetric*> &deps) {
ElevationGain *el = dynamic_cast<ElevationGain*>(deps.value("elevation_gain"));
TotalDistance *td = dynamic_cast<TotalDistance*>(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);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new Gradient(*this); }
};
static bool addGradient()
{
QVector<QString> 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);
setDescription(tr("Mean Power Deviation with respect to 30sec Moving Average"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
RideFileIterator it(item->ride(), spec);
// Less than 30s don't bother
if ((it.last()->secs - it.first()->secs) < 30) {
setValue(0);
topRank=0.00;
} else {
QVector<double> power;
QVector<double> secs;
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
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);
}
}
bool isRelevantForRide(const RideItem *ride) const {
return ride->present.contains("P") || (!ride->isSwim && !ride->isRun);
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("Maximum Power Deviation with respect to 30sec Moving Average"));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &deps) {
// no ride or no samples
if (spec.isEmpty(item->ride())) {
setValue(RideFile::NIL);
setCount(0);
return;
}
MeanPowerVariance *mean = dynamic_cast<MeanPowerVariance*>(deps.value("meanpowervariance"));
RideFileIterator it(item->ride(), spec);
// Less than 30s don't bother
if ((it.last()->secs - it.first()->secs) < 30)
setValue(0);
else
setValue(mean->topRank);
}
bool isRelevantForRide(const RideItem *ride) const {
return ride->present.contains("P") || (!ride->isSwim && !ride->isRun);
}
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new MaxPowerVariance(*this); }
};
static bool addMaxPowerVariance()
{
QVector<QString> 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);
setDescription(tr("It measures how much of the power delivered to the left pedal is pushing it forward, on average."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->watts || !item->ride()->areDataPresent()->lte) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double total = 0.0f;
double samples = 0.0f;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->lte && point->watts > 0.0f && point->cad && point->lrbalance > 0.0f && point->lrbalance < 100.0f) {
samples ++;
total += point->lte;
}
}
if (total > 0.0f && samples > 0.0f) setValue(total / samples);
else setValue(0.0);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("It measures how much of the power delivered to the right pedal is pushing it forward, on average."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->watts || !item->ride()->areDataPresent()->rte) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double total = 0.0f;
double samples = 0.0f;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->rte && point->watts > 0.0f && point->cad && point->lrbalance > 0.0f && point->lrbalance < 100.0f) {
samples ++;
total += point->rte;
}
}
if (total > 0.0f && samples > 0.0f) setValue(total / samples);
else setValue(0.0);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("It measures how smoothly power is delivered to the left pedal throughout the revolution, on average."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->watts || !item->ride()->areDataPresent()->lps) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double total = 0.0f;
double samples = 0.0f;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->lps && point->watts > 0.0f && point->cad && point->lrbalance > 0.0f && point->lrbalance < 100.0f) {
samples ++;
total += point->lps;
}
}
if (total > 0.0f && samples > 0.0f) setValue(total / samples);
else setValue(0.0);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("It measures how smoothly power is delivered to the right pedal throughout the revolution, on average."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->watts || !item->ride()->areDataPresent()->rps) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double total = 0.0f;
double samples = 0.0f;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->rps && point->watts > 0.0f && point->cad && point->lrbalance > 0.0f && point->lrbalance < 100.0f) {
samples ++;
total += point->rps;
}
}
if (total > 0.0f && samples > 0.0f) setValue(total / samples);
else setValue(0.0);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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(2);
setConversion(INCH_PER_MM);
setDescription(tr("Platform center offset is the location on the left pedal platform where you apply force, on average."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->lpco) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double total = 0.0f;
double secs = 0.0f;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->cad) {
secs += item->ride()->recIntSecs();
total += point->lpco;
}
}
if (secs > 0.0f) setValue(total / secs);
else setValue(0.0);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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(2);
setConversion(INCH_PER_MM);
setDescription(tr("Platform center offset is the location on the right pedal platform where you apply force, on average."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->rpco) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double total = 0.0f;
double secs = 0.0f;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->cad) {
secs += item->ride()->recIntSecs();
total += point->rpco;
}
}
if (secs > 0.0f) setValue(total / secs);
else setValue(0.0);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("It is the left pedal stroke angle where you start producing positive power, on average."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->lppb) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double total = 0.0f;
double secs = 0.0f;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->lppe>0) { // use for average if we have an end
secs += item->ride()->recIntSecs();
total += point->lppb + (point->lppb>180?-360:0);
}
}
if (secs > 0.0f) setValue(total / secs);
else setValue(0.0);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("It is the right pedal stroke angle where you start producing positive power, on average."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->rppb) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double total = 0.0f;
double secs = 0.0f;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->rppe>0) { // use for average if we have an end
secs += item->ride()->recIntSecs();
total += point->rppb + (point->rppb>180?-360:0);
}
}
if (secs > 0.0f) setValue(total / secs);
else setValue(0.0);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("It is the left pedal stroke angle where you end producing positive power, on average."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->lppe) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double total = 0.0f;
double secs = 0.0f;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->lppe > 0) {
secs += item->ride()->recIntSecs();
total += point->lppe;
}
}
if (secs > 0.0f) setValue(total / secs);
else setValue(0.0);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("It is the right pedal stroke angle where you end producing positive power, on average."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->rppe) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double total = 0.0f;
double secs = 0.0f;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->rppe > 0) { // end has to be > 0
secs += item->ride()->recIntSecs();
total += point->rppe;
}
}
if (secs > 0.0f) setValue(total / secs);
else setValue(0.0);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("It is the left pedal stroke angle where you start producing peak power, on average."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->lpppb) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double total = 0.0f;
double secs = 0.0f;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->lpppe>0) { // use for average if we have an end
secs += item->ride()->recIntSecs();
total += point->lpppb + (point->lpppb>180?-360:0);
}
}
if (secs > 0.0f) setValue(total / secs);
else setValue(0.0);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("It is the right pedal stroke angle where you start producing peak power, on average."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->rpppb) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double total = 0.0f;
double secs = 0.0f;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->rpppe>0) { // use for average if we have an end
secs += item->ride()->recIntSecs();
total += point->rpppb + (point->rpppb>180?-360:0);
}
}
if (secs > 0.0f) setValue(total / secs);
else setValue(0.0);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("It is the left pedal stroke angle where you end producing peak power, on average."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->lppe) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double total = 0.0f;
double secs = 0.0f;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->lpppe > 0) { // end has to be > 0
secs += item->ride()->recIntSecs();
total += point->lpppe;
}
}
if (secs > 0.0f) setValue(total / secs);
else setValue(0.0);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("It is the right pedal stroke angle where you end producing peak power, on average."));
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !item->ride()->areDataPresent()->rpppe) {
setValue(RideFile::NIL);
setCount(0);
return;
}
double total = 0.0f;
double secs = 0.0f;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->rpppe > 0) { // end has to be > 0
secs += item->ride()->recIntSecs();
total += point->rpppe;
}
}
if (secs > 0.0f) setValue(total / secs);
else setValue(0.0);
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("It is the left pedal stroke region length where you produce positive power, on average."));
}
void compute(RideItem *, Specification, const QHash<QString,RideMetric*> &deps) {
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);
}
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("It is the right pedal stroke region length where you produce positive power, on average."));
}
void compute(RideItem *, Specification, const QHash<QString,RideMetric*> &deps) {
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);
}
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("It is the left pedal stroke region length where you produce peak power, on average."));
}
void compute(RideItem *, Specification, const QHash<QString,RideMetric*> &deps) {
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);
}
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
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);
setDescription(tr("It is the right pedal stroke region length where you produce peak power, on average."));
}
void compute(RideItem *, Specification, const QHash<QString,RideMetric*> &deps) {
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);
}
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim && !ride->isRun; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new AvgRPPP(*this); }
};
static bool addLeftRight()
{
QVector<QString> 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);
setDescription(tr("Total Calories estimated from Time Moving, Heart Rate, Weight, Sex and Age"));
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &deps) {
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 = item->dateTime.date().year() - appsettings->cvalue(item->context->athlete->cyclist, GC_DOB).toDate().year();
bool male = appsettings->cvalue(item->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"); }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new TotalCalories(*this); }
};
static bool addTotalCalories() {
QVector<QString> deps;
deps.append("average_hr");
deps.append("time_riding");
deps.append("athlete_weight");
RideMetricFactory::instance().addMetric(TotalCalories(), &deps);
return true;
}
static bool totalCaloriesAdded = addTotalCalories();
///////////////////////////////////////////////////////////////////////////////
struct ActivityCRC : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(ActivityCRC)
private:
long int CRC;
public:
ActivityCRC()
{
setSymbol("activity_crc");
setInternalName("Checksum");
}
void initialize() {
setName(tr("Checksum"));
setMetricUnits(tr(""));
setImperialUnits(tr(""));
setType(RideMetric::Total);
setDescription(tr("A checksum for the activity, can be used to trigger cache refresh in R scripts."));
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &) {
setValue(item->crc + item->metacrc + item->dateTime.toMSecsSinceEpoch());
}
bool isRelevantForRide(const RideItem *) const { return true; }
MetricClass classification() const { return Undefined; }
MetricValidity validity() const { return Unknown; }
RideMetric *clone() const { return new ActivityCRC(*this); }
};
static bool addActivityCRC() {
QVector<QString> deps;
RideMetricFactory::instance().addMetric(ActivityCRC(), &deps);
return true;
}
static bool ActivityCRCAdded = addActivityCRC();
///////////////////////////////////////////////////////////////////////////////