Files
GoldenCheetah/src/BasicRideMetrics.cpp
Mark Liversedge d743fdd3ec No Assert in RideMetric.h
.. gets rid of an iritating compiler warning when
   compiling with VS2015 and MSVC

.. is good because user metrics aren't fixed so the
   assert assumption is wrong now

.. means we have to add assert.h to all the source
   files that had it from including RideMetric.h
   and want to use it.
2015-12-24 15:26:29 +00:00

3607 lines
98 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 "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(""));
}
void compute(RideItem *, Specification, const QHash<QString,RideMetric*> &) {
setValue(1);
}
RideMetric *clone() const { return new RideCount(*this); }
};
static bool countAdded =
RideMetricFactory::instance().addMetric(RideCount());
//////////////////////////////////////////////////////////////////////////////
class WorkoutTime : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(WorkoutTime)
double seconds;
public:
WorkoutTime() : seconds(0.0)
{
setSymbol("workout_time");
setInternalName("Duration");
}
bool isTime() const { return true; }
void initialize() {
setName(tr("Duration"));
setMetricUnits(tr("seconds"));
setImperialUnits(tr("seconds"));
}
void compute(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);
}
RideMetric *clone() const { return new WorkoutTime(*this); }
};
static bool workoutTimeAdded =
RideMetricFactory::instance().addMetric(WorkoutTime());
//////////////////////////////////////////////////////////////////////////////
class TimeRiding : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(TimeRiding)
double secsMovingOrPedaling;
public:
TimeRiding() : secsMovingOrPedaling(0.0)
{
setSymbol("time_riding");
setInternalName("Time Moving");
}
bool isTime() const { return true; }
void initialize() {
setName(tr("Time Moving"));
setMetricUnits(tr("seconds"));
setImperialUnits(tr("seconds"));
}
void compute(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);
}
RideMetric *clone() const { return new TimeRiding(*this); }
};
static bool timeRidingAdded =
RideMetricFactory::instance().addMetric(TimeRiding());
//////////////////////////////////////////////////////////////////////////////
class TimeCarrying : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(TimeCarrying)
double secsCarrying;
double prevalt;
public:
TimeCarrying() : secsCarrying(0.0)
{
setSymbol("time_carrying");
setInternalName("Time Carrying");
}
bool isTime() const { return true; }
void initialize() {
setName(tr("Time Carrying (Est)"));
setMetricUnits(tr("seconds"));
setImperialUnits(tr("seconds"));
}
void compute(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; }
RideMetric *clone() const { return new TimeCarrying(*this); }
};
static bool timeCarryingAdded =
RideMetricFactory::instance().addMetric(TimeCarrying());
//////////////////////////////////////////////////////////////////////////////
class ElevationGainCarrying : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(ElevationGain)
double elegain;
double prevalt;
public:
ElevationGainCarrying() : elegain(0.0), prevalt(0.0)
{
setSymbol("elevation_gain_carrying");
setInternalName("Elevation Gain Carrying");
}
void initialize() {
setName(tr("Elevation Gain Carrying (Est)"));
setType(RideMetric::Total);
setMetricUnits(tr("meters"));
setImperialUnits(tr("feet"));
setConversion(FEET_PER_METER);
}
void compute(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; }
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);
}
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);
}
RideMetric *clone() const { return new TotalDistance(*this); }
};
static bool totalDistanceAdded =
RideMetricFactory::instance().addMetric(TotalDistance());
// DistanceSwim is TotalDistance in swim units, relevant for swims in yards //
class DistanceSwim : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(DistanceSwim)
double mts;
public:
DistanceSwim() : mts(0.0)
{
setSymbol("distance_swim");
setInternalName("Distance Swim");
}
// Overrides to use Swim Pace units setting
QString units(bool) const {
bool metricSwPace = appsettings->value(NULL, GC_SWIMPACE, true).toBool();
return RideMetric::units(metricSwPace);
}
double value(bool) const {
bool metricSwPace = appsettings->value(NULL, GC_SWIMPACE, true).toBool();
return RideMetric::value(metricSwPace);
}
void initialize() {
setName(tr("Distance Swim"));
setType(RideMetric::Total);
setMetricUnits(tr("m"));
setImperialUnits(tr("yd"));
setPrecision(0);
setConversion(1.0/METERS_PER_YARD);
}
void compute(RideItem *, Specification, const QHash<QString,RideMetric*> &deps) {
TotalDistance *distance = dynamic_cast<TotalDistance*>(deps.value("total_distance"));
// convert to meters
mts = distance->value(true) * 1000.0;
setValue(mts);
setCount(distance->count());
}
bool isRelevantForRide(const RideItem *ride) const { return ride->isSwim; }
RideMetric *clone() const { return new DistanceSwim(*this); }
};
static bool addDistanceSwim()
{
QVector<QString> deps;
deps.append("total_distance");
RideMetricFactory::instance().addMetric(DistanceSwim(), &deps);
return true;
}
static bool distanceSwimAdded = addDistanceSwim();
// 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);
}
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; }
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);
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &) {
// withings first
double weight = item->context->athlete->getWithingsWeight(item->dateTime.date());
// 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);
}
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);
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &) {
WithingsReading here;
// withings first
item->context->athlete->getWithings(item->dateTime.date(), here);
setValue(here.fatkg);
}
RideMetric *clone() const { return new AthleteFat(*this); }
};
static bool athleteFatAdded =
RideMetricFactory::instance().addMetric(AthleteFat());
//////////////////////////////////////////////////////////////////////////////
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);
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &) {
WithingsReading here;
// withings first
item->context->athlete->getWithings(item->dateTime.date(), here);
setValue(here.leankg);
}
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);
}
void compute(RideItem *item, Specification, const QHash<QString,RideMetric*> &) {
WithingsReading here;
// withings first
item->context->athlete->getWithings(item->dateTime.date(), here);
setValue(here.fatpercent);
}
RideMetric *clone() const { return new AthleteFatP(*this); }
};
static bool athleteFatPAdded =
RideMetricFactory::instance().addMetric(AthleteFatP());
// case WITHINGS_LEANKG : return withings.leankg;
//////////////////////////////////////////////////////////////////////////////
class ElevationGain : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(ElevationGain)
double elegain;
double prevalt;
public:
ElevationGain() : elegain(0.0), prevalt(0.0)
{
setSymbol("elevation_gain");
setInternalName("Elevation Gain");
}
void initialize() {
setName(tr("Elevation Gain"));
setType(RideMetric::Total);
setMetricUnits(tr("meters"));
setImperialUnits(tr("feet"));
setConversion(FEET_PER_METER);
}
void compute(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; }
RideMetric *clone() const { return new ElevationGain(*this); }
};
static bool elevationGainAdded =
RideMetricFactory::instance().addMetric(ElevationGain());
//////////////////////////////////////////////////////////////////////////////
class ElevationLoss : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(ElevationLoss)
double eleLoss;
double prevalt;
public:
ElevationLoss() : eleLoss(0.0), prevalt(0.0)
{
setSymbol("elevation_loss");
setInternalName("Elevation Loss");
}
void initialize() {
setName(tr("Elevation Loss"));
setType(RideMetric::Total);
setMetricUnits(tr("meters"));
setImperialUnits(tr("feet"));
setConversion(FEET_PER_METER);
}
void compute(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; }
RideMetric *clone() const { return new ElevationLoss(*this); }
};
static bool elevationLossAdded =
RideMetricFactory::instance().addMetric(ElevationLoss());
//////////////////////////////////////////////////////////////////////////////
class TotalWork : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(TotalWork)
double joules;
public:
TotalWork() : joules(0.0)
{
setSymbol("total_work");
setInternalName("Work");
}
void initialize() {
setName(tr("Work"));
setMetricUnits(tr("kJ"));
setImperialUnits(tr("kJ"));
}
void compute(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); }
RideMetric *clone() const { return new TotalWork(*this); }
};
static bool totalWorkAdded =
RideMetricFactory::instance().addMetric(TotalWork());
//////////////////////////////////////////////////////////////////////////////
class AvgSpeed : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgSpeed)
double secsMoving;
double km;
public:
AvgSpeed() : secsMoving(0.0), km(0.0)
{
setSymbol("average_speed");
setInternalName("Average Speed");
}
void initialize() {
setName(tr("Average Speed"));
setMetricUnits(tr("kph"));
setImperialUnits(tr("mph"));
setType(RideMetric::Average);
setPrecision(1);
setConversion(MILES_PER_KM);
}
void compute(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;
}
assert(deps.contains("total_distance"));
km = deps.value("total_distance")->value(true);
if (item->ride()->areDataPresent()->kph) {
secsMoving = 0;
bool withz = item->isSwim; // average with zeros for swims
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (withz || 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);
}
}
RideMetric *clone() const { return new AvgSpeed(*this); }
};
static bool avgSpeedAdded =
RideMetricFactory::instance().addMetric(
AvgSpeed(), &(QVector<QString>() << "total_distance" << "workout_time"));
//////////////////////////////////////////////////////////////////////////////
class Pace : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(Pace)
double pace;
public:
Pace() : pace(0.0)
{
setSymbol("pace");
setInternalName("Pace");
}
// Pace ordering is reversed
bool isLowerBetter() const { return true; }
// Overrides to use Pace units setting
QString units(bool) const {
bool metricRunPace = appsettings->value(NULL, GC_PACE, true).toBool();
return RideMetric::units(metricRunPace);
}
double value(bool) const {
bool metricRunPace = appsettings->value(NULL, GC_PACE, true).toBool();
return RideMetric::value(metricRunPace);
}
QString toString(bool metric) const {
return time_to_string(value(metric)*60);
}
void initialize() {
setName(tr("Pace"));
setType(RideMetric::Average);
setMetricUnits(tr("min/km"));
setImperialUnits(tr("min/mile"));
setPrecision(1);
setConversion(KM_PER_MILE);
}
void compute(RideItem *, Specification, const QHash<QString,RideMetric*> &deps) {
AvgSpeed *as = dynamic_cast<AvgSpeed*>(deps.value("average_speed"));
// divide by zero or stupidly low pace
if (as->value(true) > 0.00f) pace = 60.0f / as->value(true);
else pace = 0;
setValue(pace);
setCount(as->count());
}
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim; }
RideMetric *clone() const { return new Pace(*this); }
};
static bool addPace()
{
QVector<QString> deps;
deps.append("average_speed");
RideMetricFactory::instance().addMetric(Pace(), &deps);
return true;
}
static bool paceAdded = addPace();
//////////////////////////////////////////////////////////////////////////////
class PaceSwim : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(PaceSwim)
double pace;
public:
PaceSwim() : pace(0.0)
{
setSymbol("pace_swim");
setInternalName("Pace Swim");
}
// Swim Pace ordering is reversed
bool isLowerBetter() const { return true; }
// Overrides to use Swim Pace units setting
QString units(bool) const {
bool metricRunPace = appsettings->value(NULL, GC_SWIMPACE, true).toBool();
return RideMetric::units(metricRunPace);
}
double value(bool) const {
bool metricRunPace = appsettings->value(NULL, GC_SWIMPACE, true).toBool();
return RideMetric::value(metricRunPace);
}
QString toString(bool metric) const {
return time_to_string(value(metric)*60);
}
void initialize() {
setName(tr("Pace Swim"));
setType(RideMetric::Average);
setMetricUnits(tr("min/100m"));
setImperialUnits(tr("min/100yd"));
setPrecision(1);
setConversion(METERS_PER_YARD);
}
void compute(RideItem *, Specification, const QHash<QString,RideMetric*> &deps) {
AvgSpeed *as = dynamic_cast<AvgSpeed*>(deps.value("average_speed"));
// divide by zero or stupidly low pace
if (as->value(true) > 0.00f) pace = 6.0f / as->value(true);
else pace = 0;
setValue(pace);
setCount(as->count());
}
bool isRelevantForRide(const RideItem *ride) const { return ride->isSwim; }
RideMetric *clone() const { return new PaceSwim(*this); }
};
static bool addPaceSwim()
{
QVector<QString> deps;
deps.append("average_speed");
RideMetricFactory::instance().addMetric(PaceSwim(), &deps);
return true;
}
static bool paceSwimAdded = addPaceSwim();
//////////////////////////////////////////////////////////////////////////////
struct AvgPower : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgPower)
double count, total;
public:
AvgPower()
{
setSymbol("average_power");
setInternalName("Average Power");
}
void initialize() {
setName(tr("Average Power"));
setMetricUnits(tr("watts"));
setImperialUnits(tr("watts"));
setType(RideMetric::Average);
}
void compute(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); }
RideMetric *clone() const { return new AvgPower(*this); }
};
static bool avgPowerAdded =
RideMetricFactory::instance().addMetric(AvgPower());
//////////////////////////////////////////////////////////////////////////////
struct AvgSmO2 : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgSmO2)
double count, total;
public:
AvgSmO2()
{
setSymbol("average_smo2");
setInternalName("Average SmO2");
}
void initialize() {
setName(tr("Average SmO2"));
setMetricUnits(tr("%"));
setImperialUnits(tr("%"));
setType(RideMetric::Average);
}
void compute(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.0) {
total += point->smo2;
++count;
}
}
setValue(count > 0 ? total / count : 0);
setCount(count);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("O"); }
RideMetric *clone() const { return new AvgSmO2(*this); }
};
static bool avgSmO2Added =
RideMetricFactory::instance().addMetric(AvgSmO2());
struct 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);
}
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;
RideFileIterator it(item->ride(), spec);
while (it.hasNext()) {
struct RideFilePoint *point = it.next();
if (point->thb >= 0.0) {
total += point->thb;
++count;
}
}
setValue(count > 0 ? total / count : 0);
setCount(count);
}
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("O"); }
RideMetric *clone() const { return new 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);
}
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); }
RideMetric *clone() const { return new AAvgPower(*this); }
};
static bool aavgPowerAdded =
RideMetricFactory::instance().addMetric(AAvgPower());
//////////////////////////////////////////////////////////////////////////////
struct NonZeroPower : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(NonZeroPower)
double count, total;
public:
NonZeroPower()
{
setSymbol("nonzero_power");
setInternalName("Nonzero Average Power");
}
void initialize() {
setName(tr("Nonzero Average Power"));
setMetricUnits(tr("watts"));
setImperialUnits(tr("watts"));
setType(RideMetric::Average);
}
void compute(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); }
RideMetric *clone() const { return new NonZeroPower(*this); }
};
static bool nonZeroPowerAdded =
RideMetricFactory::instance().addMetric(NonZeroPower());
//////////////////////////////////////////////////////////////////////////////
struct AvgHeartRate : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgHeartRate)
double total, count;
public:
AvgHeartRate()
{
setSymbol("average_hr");
setInternalName("Average Heart Rate");
}
void initialize() {
setName(tr("Average Heart Rate"));
setMetricUnits(tr("bpm"));
setImperialUnits(tr("bpm"));
setType(RideMetric::Average);
}
void compute(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"); }
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);
}
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"); }
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);
}
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"); }
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);
}
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); }
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);
}
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); }
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);
}
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); }
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);
}
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); }
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);
}
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); }
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);
}
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"); }
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);
}
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);
}
RideMetric *clone() const { return new AvgTemp(*this); }
};
static bool avgTempAdded =
RideMetricFactory::instance().addMetric(AvgTemp());
//////////////////////////////////////////////////////////////////////////////
class MaxPower : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(MaxPower)
double max;
public:
MaxPower() : max(0.0)
{
setSymbol("max_power");
setInternalName("Max Power");
}
void initialize() {
setName(tr("Max Power"));
setMetricUnits(tr("watts"));
setImperialUnits(tr("watts"));
setType(RideMetric::Peak);
}
void compute(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); }
RideMetric *clone() const { return new MaxPower(*this); }
};
static bool maxPowerAdded =
RideMetricFactory::instance().addMetric(MaxPower());
//////////////////////////////////////////////////////////////////////////////
class MaxSmO2 : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(MaxSmO2)
double max;
public:
MaxSmO2() : max(0.0)
{
setSymbol("max_smo2");
setInternalName("Max SmO2");
}
void initialize() {
setName(tr("Max SmO2"));
setMetricUnits(tr("%"));
setImperialUnits(tr("%"));
setType(RideMetric::Peak);
}
void compute(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"); }
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);
}
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"); }
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::Peak);
}
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 > 0 && point->smo2 >= min)
min = point->smo2;
}
setValue(min);
}
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);
}
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 > 0 && point->thb >= min)
min = point->thb;
}
setValue(min);
}
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);
}
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"); }
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::Peak);
}
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"); }
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);
}
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"); }
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);
}
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);
}
RideMetric *clone() const { return new MaxSpeed(*this); }
};
static bool maxSpeedAdded =
RideMetricFactory::instance().addMetric(MaxSpeed());
//////////////////////////////////////////////////////////////////////////////
class MaxCadence : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(MaxCadence)
public:
MaxCadence()
{
setSymbol("max_cadence");
setInternalName("Max Cadence");
}
void initialize() {
setName(tr("Max Cadence"));
setMetricUnits(tr("rpm"));
setImperialUnits(tr("rpm"));
setType(RideMetric::Peak);
}
void compute(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"); }
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);
}
// 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);
}
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::Peak);
setPrecision(1);
setConversion(FAHRENHEIT_PER_CENTIGRADE);
setConversionSum(FAHRENHEIT_ADD_CENTIGRADE);
}
// 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));
}
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);
}
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"); }
RideMetric *clone() const { return new NinetyFivePercentHeartRate(*this); }
};
static bool ninetyFivePercentHeartRateAdded =
RideMetricFactory::instance().addMetric(NinetyFivePercentHeartRate());
///////////////////////////////////////////////////////////////////////////////
class VAM : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(VAM)
public:
VAM()
{
setSymbol("vam");
setInternalName("VAM");
}
void initialize() {
setName(tr("VAM"));
setImperialUnits("");
setMetricUnits("");
setType(RideMetric::Average);
}
void compute(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; }
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);
}
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); }
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);
}
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; }
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);
}
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);
}
RideMetric *clone() const { return new MeanPowerVariance(*this); }
};
static bool addMeanPowerVariance()
{
RideMetricFactory::instance().addMetric(MeanPowerVariance());
return true;
}
static bool meanPowerVarianceAdded = addMeanPowerVariance();
///////////////////////////////////////////////////////////////////////////////
class MaxPowerVariance : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(MaxPowerVariance)
public:
MaxPowerVariance()
{
setSymbol("maxpowervariance");
setInternalName("Max Power Variance");
}
void initialize() {
setName(tr("Max Power Variance"));
setImperialUnits("watts change");
setMetricUnits("watts change");
setPrecision(2);
setType(RideMetric::Average);
}
void compute(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);
}
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);
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !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) {
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; }
RideMetric *clone() const { return new AvgLTE(*this); }
};
//////////////////////////////////////////////////////////////////////////////
class AvgRTE : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgRTE)
public:
AvgRTE()
{
setSymbol("average_rte");
setInternalName("Average Right Torque Effectiveness");
}
void initialize() {
setName(tr("Average Right Torque Effectiveness"));
setMetricUnits(tr("%"));
setImperialUnits(tr("%"));
setType(RideMetric::Average);
setPrecision(1);
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !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) {
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; }
RideMetric *clone() const { return new AvgRTE(*this); }
};
//////////////////////////////////////////////////////////////////////////////
class AvgLPS : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgLPS)
public:
AvgLPS()
{
setSymbol("average_lps");
setInternalName("Average Left Pedal Smoothness");
}
void initialize() {
setName(tr("Average Left Pedal Smoothness"));
setMetricUnits(tr("%"));
setImperialUnits(tr("%"));
setType(RideMetric::Average);
setPrecision(1);
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !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) {
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; }
RideMetric *clone() const { return new AvgLPS(*this); }
};
//////////////////////////////////////////////////////////////////////////////
class AvgRPS : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgRPS)
public:
AvgRPS()
{
setSymbol("average_rps");
setInternalName("Average Right Pedal Smoothness");
}
void initialize() {
setName(tr("Average Right Pedal Smoothness"));
setMetricUnits(tr("%"));
setImperialUnits(tr("%"));
setType(RideMetric::Average);
setPrecision(1);
}
void compute(RideItem *item, Specification spec, const QHash<QString,RideMetric*> &) {
// no ride or no samples
if (spec.isEmpty(item->ride()) || !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) {
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; }
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);
}
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; }
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);
}
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; }
RideMetric *clone() const { return new AvgRPCO(*this); }
};
//////////////////////////////////////////////////////////////////////////////
class AvgLPPB : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgLPPB)
public:
AvgLPPB()
{
setSymbol("average_lppb");
setInternalName("Average Left Power Phase Start");
}
void initialize() {
setName(tr("Average Left Power Phase Start"));
setMetricUnits(tr("°"));
setType(RideMetric::Average);
setPrecision(0);
}
void compute(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; }
RideMetric *clone() const { return new AvgLPPB(*this); }
};
//////////////////////////////////////////////////////////////////////////////
class AvgRPPB : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgRTPP)
public:
AvgRPPB()
{
setSymbol("average_rppb");
setInternalName("Average Right Power Phase Start");
}
void initialize() {
setName(tr("Average Right Power Phase Start"));
setMetricUnits(tr("°"));
setType(RideMetric::Average);
setPrecision(0);
}
void compute(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; }
RideMetric *clone() const { return new AvgRPPB(*this); }
};
//////////////////////////////////////////////////////////////////////////////
class AvgLPPE : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgLPPE)
public:
AvgLPPE()
{
setSymbol("average_lppe");
setInternalName("Average Left Power Phase End");
}
void initialize() {
setName(tr("Average Left Power Phase End"));
setMetricUnits(tr("°"));
setType(RideMetric::Average);
setPrecision(0);
}
void compute(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; }
RideMetric *clone() const { return new AvgLPPE(*this); }
};
//////////////////////////////////////////////////////////////////////////////
class AvgRPPE : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgRPPE)
public:
AvgRPPE()
{
setSymbol("average_rppe");
setInternalName("Average Right Power Phase End");
}
void initialize() {
setName(tr("Average Right Power Phase End"));
setMetricUnits(tr("°"));
setType(RideMetric::Average);
setPrecision(0);
}
void compute(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; }
RideMetric *clone() const { return new AvgRPPE(*this); }
};
//////////////////////////////////////////////////////////////////////////////
class AvgLPPPB : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgLPPPB)
public:
AvgLPPPB()
{
setSymbol("average_lpppb");
setInternalName("Average Left Peak Power Phase Start");
}
void initialize() {
setName(tr("Average Left Peak Power Phase Start"));
setMetricUnits(tr("°"));
setType(RideMetric::Average);
setPrecision(0);
}
void compute(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; }
RideMetric *clone() const { return new AvgLPPPB(*this); }
};
//////////////////////////////////////////////////////////////////////////////
class AvgRPPPB : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgRPPPB)
public:
AvgRPPPB()
{
setSymbol("average_rpppb");
setInternalName("Average Right Peak Power Phase Start");
}
void initialize() {
setName(tr("Average Right Peak Power Phase Start"));
setMetricUnits(tr("°"));
setType(RideMetric::Average);
setPrecision(0);
}
void compute(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; }
RideMetric *clone() const { return new AvgRPPPB(*this); }
};
//////////////////////////////////////////////////////////////////////////////
class AvgLPPPE : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgLPPPE)
public:
AvgLPPPE()
{
setSymbol("average_lpppe");
setInternalName("Average Left Peak Power Phase End");
}
void initialize() {
setName(tr("Average Left Peak Power Phase End"));
setMetricUnits(tr("°"));
setType(RideMetric::Average);
setPrecision(0);
}
void compute(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; }
RideMetric *clone() const { return new AvgLPPPE(*this); }
};
//////////////////////////////////////////////////////////////////////////////
class AvgRPPPE : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgRPPPE)
public:
AvgRPPPE()
{
setSymbol("average_rpppe");
setInternalName("Average Right Peak Power Phase End");
}
void initialize() {
setName(tr("Average Right Peak Power Phase End"));
setMetricUnits(tr("°"));
setType(RideMetric::Average);
setPrecision(0);
}
void compute(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; }
RideMetric *clone() const { return new AvgRPPPE(*this); }
};
//////////////////////////////////////////////////////////////////////////////
class AvgLPP : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgLPP)
double average_lppb;
double average_lppe;
public:
AvgLPP()
{
setSymbol("average_lpp");
setInternalName("Average Left Power Phase Length");
}
void initialize() {
setName(tr("Average Left Power Phase Length"));
setMetricUnits(tr("°"));
setType(RideMetric::Average);
setPrecision(0);
}
void compute(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; }
RideMetric *clone() const { return new AvgLPP(*this); }
};
//////////////////////////////////////////////////////////////////////////////
class AvgRPP : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgRPP)
double average_rppb;
double average_rppe;
public:
AvgRPP()
{
setSymbol("average_rpp");
setInternalName("Average Right Power Phase Length");
}
void initialize() {
setName(tr("Average Right Power Phase Length"));
setMetricUnits(tr("°"));
setType(RideMetric::Average);
setPrecision(0);
}
void compute(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; }
RideMetric *clone() const { return new AvgRPP(*this); }
};
//////////////////////////////////////////////////////////////////////////////
class AvgLPPP : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgLPPP)
double average_lpppb;
double average_lpppe;
public:
AvgLPPP()
{
setSymbol("average_lppp");
setInternalName("Average Peak Left Power Phase Length");
}
void initialize() {
setName(tr("Average Left Peak Power Phase Length"));
setMetricUnits(tr("°"));
setType(RideMetric::Average);
setPrecision(0);
}
void compute(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; }
RideMetric *clone() const { return new AvgLPPP(*this); }
};
//////////////////////////////////////////////////////////////////////////////
class AvgRPPP : public RideMetric {
Q_DECLARE_TR_FUNCTIONS(AvgRPPP)
double average_rpppb;
double average_rpppe;
public:
AvgRPPP()
{
setSymbol("average_rppp");
setInternalName("Average Right Peak Power Phase Length");
}
void initialize() {
setName(tr("Average Right Peak Power Phase Length"));
setMetricUnits(tr("°"));
setType(RideMetric::Average);
setPrecision(0);
}
void compute(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; }
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);
}
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"); }
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();
///////////////////////////////////////////////////////////////////////////////