mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-15 08:59:55 +00:00
* 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
3841 lines
114 KiB
C++
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();
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|