mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-15 08:59:55 +00:00
Distance/Duration is a better approximation for Avg Speed than 0.0 and Duration is better than Time Moving in GOVSS/SwimScore estimation for these cases.
2652 lines
74 KiB
C++
2652 lines
74 KiB
C++
/*
|
|
* Copyright (c) 2008 Sean C. Rhea (srhea@srhea.net)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "RideMetric.h"
|
|
#include "Athlete.h"
|
|
#include "Context.h"
|
|
#include "Settings.h"
|
|
#include "RideItem.h"
|
|
#include "LTMOutliers.h"
|
|
#include "Units.h"
|
|
#include "cmath"
|
|
#include <algorithm>
|
|
#include <QVector>
|
|
#include <QApplication>
|
|
|
|
class RideCount : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(RideCount)
|
|
public:
|
|
|
|
RideCount()
|
|
{
|
|
setSymbol("ride_count");
|
|
setInternalName("Activities");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Activities"));
|
|
setMetricUnits(tr(""));
|
|
setImperialUnits(tr(""));
|
|
}
|
|
|
|
void compute(const RideFile *, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
setValue(1);
|
|
}
|
|
RideMetric *clone() const { return new RideCount(*this); }
|
|
};
|
|
|
|
static bool countAdded =
|
|
RideMetricFactory::instance().addMetric(RideCount());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
class WorkoutTime : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(WorkoutTime)
|
|
double seconds;
|
|
|
|
public:
|
|
|
|
WorkoutTime() : seconds(0.0)
|
|
{
|
|
setSymbol("workout_time");
|
|
setInternalName("Duration");
|
|
}
|
|
bool isTime() const { return true; }
|
|
void initialize() {
|
|
setName(tr("Duration"));
|
|
setMetricUnits(tr("seconds"));
|
|
setImperialUnits(tr("seconds"));
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
if (!ride->dataPoints().isEmpty()) {
|
|
seconds = ride->dataPoints().back()->secs -
|
|
ride->dataPoints().front()->secs + ride->recIntSecs();
|
|
} else {
|
|
seconds = 0;
|
|
}
|
|
setValue(seconds);
|
|
|
|
}
|
|
RideMetric *clone() const { return new WorkoutTime(*this); }
|
|
};
|
|
|
|
static bool workoutTimeAdded =
|
|
RideMetricFactory::instance().addMetric(WorkoutTime());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class TimeRiding : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(TimeRiding)
|
|
double secsMovingOrPedaling;
|
|
|
|
public:
|
|
|
|
TimeRiding() : secsMovingOrPedaling(0.0)
|
|
{
|
|
setSymbol("time_riding");
|
|
setInternalName("Time Moving");
|
|
}
|
|
bool isTime() const { return true; }
|
|
void initialize() {
|
|
setName(tr("Time Moving"));
|
|
setMetricUnits(tr("seconds"));
|
|
setImperialUnits(tr("seconds"));
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
secsMovingOrPedaling = 0;
|
|
if (ride->areDataPresent()->kph || ride->areDataPresent()->cad ) {
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if ((point->kph > 0.0) || (point->cad > 0.0))
|
|
secsMovingOrPedaling += ride->recIntSecs();
|
|
}
|
|
}
|
|
setValue(secsMovingOrPedaling);
|
|
}
|
|
void override(const QMap<QString,QString> &map) {
|
|
if (map.contains("value"))
|
|
secsMovingOrPedaling = map.value("value").toDouble();
|
|
}
|
|
RideMetric *clone() const { return new TimeRiding(*this); }
|
|
};
|
|
|
|
static bool timeRidingAdded =
|
|
RideMetricFactory::instance().addMetric(TimeRiding());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class TimeCarrying : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(TimeCarrying)
|
|
double secsCarrying;
|
|
double prevalt;
|
|
|
|
public:
|
|
|
|
TimeCarrying() : secsCarrying(0.0)
|
|
{
|
|
setSymbol("time_carrying");
|
|
setInternalName("Time Carrying");
|
|
}
|
|
bool isTime() const { return true; }
|
|
void initialize() {
|
|
setName(tr("Time Carrying (Est)"));
|
|
setMetricUnits(tr("seconds"));
|
|
setImperialUnits(tr("seconds"));
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
secsCarrying = 0;
|
|
if (ride->areDataPresent()->kph) {
|
|
|
|
// hysteresis can be configured, we default to 3.0
|
|
double hysteresis = appsettings->value(NULL, GC_ELEVATION_HYSTERESIS).toDouble();
|
|
if (hysteresis <= 0.1) hysteresis = 3.00;
|
|
|
|
bool first = true;
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
// only consider pushing/carrying with elevation gain
|
|
if (first) {
|
|
first = false;
|
|
prevalt = point->alt;
|
|
}
|
|
else if (point->alt > prevalt + hysteresis) {
|
|
prevalt = point->alt;
|
|
}
|
|
else if (point->alt < prevalt - hysteresis) {
|
|
prevalt = point->alt;
|
|
}
|
|
|
|
if ((point->kph > 0.0) && // we are moving
|
|
(point->kph < 8.0) && // but slow (even slower than 8 kph)
|
|
(point->alt > prevalt) && // gaining height
|
|
(point->cad == 0.0) && // but no cadence
|
|
(point->watts == 0.0)) // and no power
|
|
|
|
secsCarrying += ride->recIntSecs();
|
|
}
|
|
}
|
|
setValue(secsCarrying);
|
|
|
|
}
|
|
RideMetric *clone() const { return new TimeCarrying(*this); }
|
|
};
|
|
|
|
static bool timeCarryingAdded =
|
|
RideMetricFactory::instance().addMetric(TimeCarrying());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class ElevationGainCarrying : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(ElevationGain)
|
|
double elegain;
|
|
double prevalt;
|
|
|
|
public:
|
|
|
|
ElevationGainCarrying() : elegain(0.0), prevalt(0.0)
|
|
{
|
|
setSymbol("elevation_gain_carrying");
|
|
setInternalName("Elevation Gain Carrying");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Elevation Gain Carrying (Est)"));
|
|
setType(RideMetric::Total);
|
|
setMetricUnits(tr("meters"));
|
|
setImperialUnits(tr("feet"));
|
|
setConversion(FEET_PER_METER);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
// hysteresis can be configured, we default to 3.0
|
|
double hysteresis = appsettings->value(NULL, GC_ELEVATION_HYSTERESIS).toDouble();
|
|
if (hysteresis <= 0.1) hysteresis = 3.00;
|
|
|
|
bool first = true;
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (first) {
|
|
first = false;
|
|
prevalt = point->alt;
|
|
}
|
|
else if (point->alt > prevalt + hysteresis) {
|
|
if ((point->kph > 0.0) &&
|
|
(point->kph < 8.0) &&
|
|
(point->watts == 0.0) &&
|
|
(point->cad == 0.0)) {
|
|
elegain += point->alt - prevalt;
|
|
};
|
|
prevalt = point->alt;
|
|
}
|
|
else if (point->alt < prevalt - hysteresis) {
|
|
prevalt = point->alt;
|
|
}
|
|
}
|
|
setValue(elegain);
|
|
}
|
|
RideMetric *clone() const { return new ElevationGainCarrying(*this); }
|
|
};
|
|
|
|
static bool elevationGainCarryingAdded =
|
|
RideMetricFactory::instance().addMetric(ElevationGainCarrying());
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class TotalDistance : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(TotalDistance)
|
|
double km;
|
|
|
|
public:
|
|
|
|
TotalDistance() : km(0.0)
|
|
{
|
|
setSymbol("total_distance");
|
|
setInternalName("Distance");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Distance"));
|
|
setType(RideMetric::Total);
|
|
setMetricUnits(tr("km"));
|
|
setImperialUnits(tr("miles"));
|
|
setPrecision(2);
|
|
setConversion(MILES_PER_KM);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
// Note: The 'km' in each sample is the distance travelled by the
|
|
// *end* of the sampling period. The last term in this equation
|
|
// accounts for the distance traveled *during* the first sample.
|
|
if (ride->dataPoints().count() > 1 && ride->areDataPresent()->km) {
|
|
|
|
km = ride->dataPoints().back()->km - ride->dataPoints().front()->km;
|
|
|
|
if (ride->areDataPresent()->kph)
|
|
km += ride->dataPoints().front()->kph / 3600.0 * ride->recIntSecs();
|
|
|
|
} else {
|
|
|
|
km = 0;
|
|
|
|
}
|
|
setValue(km);
|
|
}
|
|
|
|
RideMetric *clone() const { return new TotalDistance(*this); }
|
|
};
|
|
|
|
static bool totalDistanceAdded =
|
|
RideMetricFactory::instance().addMetric(TotalDistance());
|
|
|
|
class AthleteWeight : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(AthleteWeight)
|
|
double kg;
|
|
|
|
public:
|
|
|
|
AthleteWeight() : kg(0.0)
|
|
{
|
|
setSymbol("athlete_weight");
|
|
setInternalName("Athlete Weight");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Athlete Weight"));
|
|
setType(RideMetric::Average);
|
|
setMetricUnits(tr("kg"));
|
|
setImperialUnits(tr("lbs"));
|
|
setPrecision(2);
|
|
setConversion(LB_PER_KG);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *context) {
|
|
|
|
// withings first
|
|
double weight = context->athlete->getWithingsWeight(ride->startTime().date());
|
|
|
|
// from metadata
|
|
if (!weight) weight = ride->getTag("Weight", "0.0").toDouble();
|
|
|
|
// global options
|
|
if (!weight) weight = appsettings->cvalue(context->athlete->cyclist, GC_WEIGHT, "75.0").toString().toDouble(); // default to 75kg
|
|
|
|
// No weight default is weird, we'll set to 80kg
|
|
if (weight <= 0.00) weight = 80.00;
|
|
|
|
setValue(weight);
|
|
}
|
|
|
|
RideMetric *clone() const { return new AthleteWeight(*this); }
|
|
};
|
|
|
|
static bool athleteWeightAdded =
|
|
RideMetricFactory::instance().addMetric(AthleteWeight());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
class ElevationGain : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(ElevationGain)
|
|
double elegain;
|
|
double prevalt;
|
|
|
|
public:
|
|
|
|
ElevationGain() : elegain(0.0), prevalt(0.0)
|
|
{
|
|
setSymbol("elevation_gain");
|
|
setInternalName("Elevation Gain");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Elevation Gain"));
|
|
setType(RideMetric::Total);
|
|
setMetricUnits(tr("meters"));
|
|
setImperialUnits(tr("feet"));
|
|
setConversion(FEET_PER_METER);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
// hysteresis can be configured, we default to 3.0
|
|
double hysteresis = appsettings->value(NULL, GC_ELEVATION_HYSTERESIS).toDouble();
|
|
if (hysteresis <= 0.1) hysteresis = 3.00;
|
|
|
|
bool first = true;
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (first) {
|
|
first = false;
|
|
prevalt = point->alt;
|
|
}
|
|
else if (point->alt > prevalt + hysteresis) {
|
|
elegain += point->alt - prevalt;
|
|
prevalt = point->alt;
|
|
}
|
|
else if (point->alt < prevalt - hysteresis) {
|
|
prevalt = point->alt;
|
|
}
|
|
}
|
|
setValue(elegain);
|
|
}
|
|
RideMetric *clone() const { return new ElevationGain(*this); }
|
|
};
|
|
|
|
static bool elevationGainAdded =
|
|
RideMetricFactory::instance().addMetric(ElevationGain());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class ElevationLoss : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(ElevationLoss)
|
|
double eleLoss;
|
|
double prevalt;
|
|
|
|
public:
|
|
|
|
ElevationLoss() : eleLoss(0.0), prevalt(0.0)
|
|
{
|
|
setSymbol("elevation_loss");
|
|
setInternalName("Elevation Loss");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Elevation Loss"));
|
|
setType(RideMetric::Total);
|
|
setMetricUnits(tr("meters"));
|
|
setImperialUnits(tr("feet"));
|
|
setConversion(FEET_PER_METER);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
// hysteresis can be configured, we default to 3.0
|
|
double hysteresis = appsettings->value(NULL, GC_ELEVATION_HYSTERESIS).toDouble();
|
|
if (hysteresis <= 0.1) hysteresis = 3.00;
|
|
|
|
bool first = true;
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (first) {
|
|
first = false;
|
|
prevalt = point->alt;
|
|
}
|
|
else if (point->alt < prevalt - hysteresis) {
|
|
eleLoss += prevalt - point->alt;
|
|
prevalt = point->alt;
|
|
}
|
|
else if (point->alt > prevalt + hysteresis) {
|
|
prevalt = point->alt;
|
|
}
|
|
}
|
|
setValue(eleLoss);
|
|
}
|
|
RideMetric *clone() const { return new ElevationLoss(*this); }
|
|
};
|
|
|
|
static bool elevationLossAdded =
|
|
RideMetricFactory::instance().addMetric(ElevationLoss());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class TotalWork : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(TotalWork)
|
|
double joules;
|
|
|
|
public:
|
|
|
|
TotalWork() : joules(0.0)
|
|
{
|
|
setSymbol("total_work");
|
|
setInternalName("Work");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Work"));
|
|
setMetricUnits(tr("kJ"));
|
|
setImperialUnits(tr("kJ"));
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
joules = 0;
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (point->watts >= 0.0)
|
|
joules += point->watts * ride->recIntSecs();
|
|
}
|
|
setValue(joules/1000);
|
|
}
|
|
RideMetric *clone() const { return new TotalWork(*this); }
|
|
};
|
|
|
|
static bool totalWorkAdded =
|
|
RideMetricFactory::instance().addMetric(TotalWork());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgSpeed : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(AvgSpeed)
|
|
double secsMoving;
|
|
double km;
|
|
|
|
public:
|
|
|
|
AvgSpeed() : secsMoving(0.0), km(0.0)
|
|
{
|
|
setSymbol("average_speed");
|
|
setInternalName("Average Speed");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Speed"));
|
|
setMetricUnits(tr("kph"));
|
|
setImperialUnits(tr("mph"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(1);
|
|
setConversion(MILES_PER_KM);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &deps,
|
|
const Context *) {
|
|
assert(deps.contains("total_distance"));
|
|
km = deps.value("total_distance")->value(true);
|
|
|
|
if (ride->areDataPresent()->kph) {
|
|
|
|
secsMoving = 0;
|
|
bool withz = ride->isSwim(); // average with zeros for swims
|
|
foreach (const RideFilePoint *point, ride->dataPoints())
|
|
if (withz || point->kph > 0.0) secsMoving += ride->recIntSecs();
|
|
|
|
setValue(secsMoving ? km / secsMoving * 3600.0 : 0.0);
|
|
|
|
} else {
|
|
|
|
// backup to duration if there is no speed channel
|
|
assert(deps.contains("workout_time"));
|
|
secsMoving = deps.value("workout_time")->value(true);
|
|
setValue(secsMoving ? km / secsMoving * 3600.0 : 0.0);
|
|
|
|
}
|
|
}
|
|
|
|
void aggregateWith(const RideMetric &other) {
|
|
assert(symbol() == other.symbol());
|
|
const AvgSpeed &as = dynamic_cast<const AvgSpeed&>(other);
|
|
secsMoving += as.secsMoving;
|
|
km += as.km;
|
|
|
|
setValue(secsMoving ? km / secsMoving * 3600.0 : 0.0);
|
|
}
|
|
RideMetric *clone() const { return new AvgSpeed(*this); }
|
|
};
|
|
|
|
static bool avgSpeedAdded =
|
|
RideMetricFactory::instance().addMetric(
|
|
AvgSpeed(), &(QVector<QString>() << "total_distance" << "workout_time"));
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
class Pace : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(Pace)
|
|
double pace;
|
|
|
|
public:
|
|
|
|
Pace() : pace(0.0)
|
|
{
|
|
setSymbol("pace");
|
|
setInternalName("Pace");
|
|
}
|
|
// Pace ordering is reversed
|
|
bool isLowerBetter() const { return true; }
|
|
// Overrides to use Pace units setting
|
|
QString units(bool) const {
|
|
bool metricRunPace = appsettings->value(NULL, GC_PACE, true).toBool();
|
|
return RideMetric::units(metricRunPace);
|
|
}
|
|
double value(bool) const {
|
|
bool metricRunPace = appsettings->value(NULL, GC_PACE, true).toBool();
|
|
return RideMetric::value(metricRunPace);
|
|
}
|
|
QString toString(bool metric) const {
|
|
return time_to_string(value(metric)*60);
|
|
}
|
|
void initialize() {
|
|
setName(tr("Pace"));
|
|
setType(RideMetric::Average);
|
|
setMetricUnits(tr("min/km"));
|
|
setImperialUnits(tr("min/mile"));
|
|
setPrecision(1);
|
|
setConversion(KM_PER_MILE);
|
|
}
|
|
|
|
void compute(const RideFile *, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &deps,
|
|
const Context *) {
|
|
|
|
AvgSpeed *as = dynamic_cast<AvgSpeed*>(deps.value("average_speed"));
|
|
|
|
// divide by zero or stupidly low pace
|
|
if (as->value(true) > 0.00f) pace = 60.0f / as->value(true);
|
|
else pace = 0;
|
|
|
|
setValue(pace);
|
|
setCount(as->count());
|
|
}
|
|
|
|
bool isRelevantForRide(const RideItem *ride) const { return !ride->isSwim; }
|
|
|
|
RideMetric *clone() const { return new Pace(*this); }
|
|
};
|
|
|
|
static bool addPace()
|
|
{
|
|
QVector<QString> deps;
|
|
deps.append("average_speed");
|
|
RideMetricFactory::instance().addMetric(Pace(), &deps);
|
|
return true;
|
|
}
|
|
static bool paceAdded = addPace();
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
class PaceSwim : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(PaceSwim)
|
|
double pace;
|
|
|
|
public:
|
|
|
|
PaceSwim() : pace(0.0)
|
|
{
|
|
setSymbol("pace_swim");
|
|
setInternalName("Pace Swim");
|
|
}
|
|
// Swim Pace ordering is reversed
|
|
bool isLowerBetter() const { return true; }
|
|
// Overrides to use Swim Pace units setting
|
|
QString units(bool) const {
|
|
bool metricRunPace = appsettings->value(NULL, GC_SWIMPACE, true).toBool();
|
|
return RideMetric::units(metricRunPace);
|
|
}
|
|
double value(bool) const {
|
|
bool metricRunPace = appsettings->value(NULL, GC_SWIMPACE, true).toBool();
|
|
return RideMetric::value(metricRunPace);
|
|
}
|
|
QString toString(bool metric) const {
|
|
return time_to_string(value(metric)*60);
|
|
}
|
|
void initialize() {
|
|
setName(tr("Pace Swim"));
|
|
setType(RideMetric::Average);
|
|
setMetricUnits(tr("min/100m"));
|
|
setImperialUnits(tr("min/100yd"));
|
|
setPrecision(1);
|
|
setConversion(METERS_PER_YARD);
|
|
}
|
|
|
|
void compute(const RideFile *, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &deps,
|
|
const Context *) {
|
|
|
|
AvgSpeed *as = dynamic_cast<AvgSpeed*>(deps.value("average_speed"));
|
|
|
|
// divide by zero or stupidly low pace
|
|
if (as->value(true) > 0.00f) pace = 6.0f / as->value(true);
|
|
else pace = 0;
|
|
|
|
setValue(pace);
|
|
setCount(as->count());
|
|
}
|
|
|
|
bool isRelevantForRide(const RideItem *ride) const { return ride->isSwim; }
|
|
|
|
RideMetric *clone() const { return new PaceSwim(*this); }
|
|
};
|
|
|
|
static bool addPaceSwim()
|
|
{
|
|
QVector<QString> deps;
|
|
deps.append("average_speed");
|
|
RideMetricFactory::instance().addMetric(PaceSwim(), &deps);
|
|
return true;
|
|
}
|
|
static bool paceSwimAdded = addPaceSwim();
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct AvgPower : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(AvgPower)
|
|
|
|
double count, total;
|
|
|
|
public:
|
|
|
|
AvgPower()
|
|
{
|
|
setSymbol("average_power");
|
|
setInternalName("Average Power");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Power"));
|
|
setMetricUnits(tr("watts"));
|
|
setImperialUnits(tr("watts"));
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
total = count = 0;
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (point->watts >= 0.0) {
|
|
total += point->watts;
|
|
++count;
|
|
}
|
|
}
|
|
setValue(count > 0 ? total / count : 0);
|
|
setCount(count);
|
|
}
|
|
RideMetric *clone() const { return new AvgPower(*this); }
|
|
};
|
|
|
|
static bool avgPowerAdded =
|
|
RideMetricFactory::instance().addMetric(AvgPower());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct AvgSmO2 : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(AvgSmO2)
|
|
|
|
double count, total;
|
|
|
|
public:
|
|
|
|
AvgSmO2()
|
|
{
|
|
setSymbol("average_smo2");
|
|
setInternalName("Average SmO2");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average SmO2"));
|
|
setMetricUnits(tr("%"));
|
|
setImperialUnits(tr("%"));
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
total = count = 0;
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (point->smo2 >= 0.0) {
|
|
total += point->smo2;
|
|
++count;
|
|
}
|
|
}
|
|
setValue(count > 0 ? total / count : 0);
|
|
setCount(count);
|
|
}
|
|
|
|
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("O"); }
|
|
|
|
RideMetric *clone() const { return new AvgSmO2(*this); }
|
|
};
|
|
|
|
static bool avgSmO2Added =
|
|
RideMetricFactory::instance().addMetric(AvgSmO2());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct AAvgPower : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(AAvgPower)
|
|
|
|
double count, total;
|
|
|
|
public:
|
|
|
|
AAvgPower()
|
|
{
|
|
setSymbol("average_apower");
|
|
setInternalName("Average aPower");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average aPower"));
|
|
setMetricUnits(tr("watts"));
|
|
setImperialUnits(tr("watts"));
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
total = count = 0;
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (point->apower >= 0.0) {
|
|
total += point->apower;
|
|
++count;
|
|
}
|
|
}
|
|
setValue(count > 0 ? total / count : 0);
|
|
setCount(count);
|
|
}
|
|
RideMetric *clone() const { return new AAvgPower(*this); }
|
|
};
|
|
|
|
static bool aavgPowerAdded =
|
|
RideMetricFactory::instance().addMetric(AAvgPower());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct NonZeroPower : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(NonZeroPower)
|
|
|
|
double count, total;
|
|
|
|
public:
|
|
|
|
NonZeroPower()
|
|
{
|
|
setSymbol("nonzero_power");
|
|
setInternalName("Nonzero Average Power");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Nonzero Average Power"));
|
|
setMetricUnits(tr("watts"));
|
|
setImperialUnits(tr("watts"));
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
total = count = 0;
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (point->watts > 0.0) {
|
|
total += point->watts;
|
|
++count;
|
|
}
|
|
}
|
|
setValue(count > 0 ? total / count : 0);
|
|
setCount(count);
|
|
}
|
|
RideMetric *clone() const { return new NonZeroPower(*this); }
|
|
};
|
|
|
|
static bool nonZeroPowerAdded =
|
|
RideMetricFactory::instance().addMetric(NonZeroPower());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct AvgHeartRate : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(AvgHeartRate)
|
|
|
|
double total, count;
|
|
|
|
public:
|
|
|
|
AvgHeartRate()
|
|
{
|
|
setSymbol("average_hr");
|
|
setInternalName("Average Heart Rate");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Heart Rate"));
|
|
setMetricUnits(tr("bpm"));
|
|
setImperialUnits(tr("bpm"));
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
total = count = 0;
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (point->hr > 0) {
|
|
total += point->hr;
|
|
++count;
|
|
}
|
|
}
|
|
setValue(count > 0 ? total / count : 0);
|
|
setCount(count);
|
|
}
|
|
|
|
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("H"); }
|
|
|
|
RideMetric *clone() const { return new AvgHeartRate(*this); }
|
|
};
|
|
|
|
static bool avgHeartRateAdded =
|
|
RideMetricFactory::instance().addMetric(AvgHeartRate());
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class HrPw : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(HrPw)
|
|
|
|
public:
|
|
HrPw()
|
|
{
|
|
setSymbol("hrpw");
|
|
setInternalName("HrPw Ratio");
|
|
}
|
|
void initialize() {
|
|
setName(tr("HrPw Ratio"));
|
|
setImperialUnits("");
|
|
setMetricUnits("");
|
|
setPrecision(3);
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &deps,
|
|
const Context *) {
|
|
|
|
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(0);
|
|
}
|
|
}
|
|
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 WattsRPE : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(WattsRPE)
|
|
|
|
public:
|
|
WattsRPE()
|
|
{
|
|
setSymbol("wattsRPE");
|
|
setInternalName("Watts:RPE Ratio");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Watts:RPE Ratio"));
|
|
setImperialUnits("");
|
|
setMetricUnits("");
|
|
setPrecision(3);
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &deps,
|
|
const Context *) {
|
|
|
|
double ratio = 0.0f;
|
|
AvgPower *pw = dynamic_cast<AvgPower*>(deps.value("average_power"));
|
|
double rpe = ride->getTag("RPE", "0").toDouble();
|
|
|
|
if (pw->value(true) > 100 && rpe > 0) { // ignore silly rides with low values
|
|
ratio = pw->value(true) / rpe;
|
|
}
|
|
setValue(ratio);
|
|
}
|
|
RideMetric *clone() const { return new WattsRPE(*this); }
|
|
};
|
|
|
|
static bool addWattsRPE()
|
|
{
|
|
QVector<QString> deps;
|
|
deps.append("average_power");
|
|
RideMetricFactory::instance().addMetric(WattsRPE(), &deps);
|
|
return true;
|
|
}
|
|
|
|
static bool wattsRPEAdded = addWattsRPE();
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class HrNp : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(HrNp)
|
|
|
|
public:
|
|
HrNp()
|
|
{
|
|
setSymbol("hrnp");
|
|
setInternalName("HrNp Ratio");
|
|
}
|
|
void initialize() {
|
|
setName(tr("HrNp Ratio"));
|
|
setImperialUnits("");
|
|
setMetricUnits("");
|
|
setPrecision(3);
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &deps,
|
|
const Context *) {
|
|
|
|
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);
|
|
}
|
|
}
|
|
RideMetric *clone() const { return new HrNp(*this); }
|
|
};
|
|
|
|
static bool addHrNp()
|
|
{
|
|
QVector<QString> deps;
|
|
deps.append("coggan_np");
|
|
deps.append("average_hr");
|
|
RideMetricFactory::instance().addMetric(HrNp(), &deps);
|
|
return true;
|
|
}
|
|
|
|
static bool hrnpAdded = addHrNp();
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct AvgCadence : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(AvgCadence)
|
|
|
|
double total, count;
|
|
|
|
public:
|
|
|
|
AvgCadence()
|
|
{
|
|
setSymbol("average_cad");
|
|
setInternalName("Average Cadence");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Cadence"));
|
|
setMetricUnits(tr("rpm"));
|
|
setImperialUnits(tr("rpm"));
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
total = count = 0;
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (point->cad > 0) {
|
|
total += point->cad;
|
|
++count;
|
|
}
|
|
}
|
|
setValue(count > 0 ? total / count : count);
|
|
setCount(count);
|
|
}
|
|
|
|
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("C"); }
|
|
|
|
RideMetric *clone() const { return new AvgCadence(*this); }
|
|
};
|
|
|
|
static bool avgCadenceAdded =
|
|
RideMetricFactory::instance().addMetric(AvgCadence());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct AvgTemp : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(AvgTemp)
|
|
|
|
double total, count;
|
|
|
|
public:
|
|
|
|
AvgTemp()
|
|
{
|
|
setSymbol("average_temp");
|
|
setInternalName("Average Temp");
|
|
}
|
|
|
|
// we DO aggregate zero, its -255 we ignore !
|
|
bool aggregateZero() const { return true; }
|
|
|
|
void initialize() {
|
|
setName(tr("Average Temp"));
|
|
setMetricUnits(tr("C"));
|
|
setImperialUnits(tr("F"));
|
|
setPrecision(1);
|
|
setConversion(FAHRENHEIT_PER_CENTIGRADE);
|
|
setConversionSum(FAHRENHEIT_ADD_CENTIGRADE);
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->temp) {
|
|
total = count = 0;
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (point->temp != RideFile::NoTemp) {
|
|
total += point->temp;
|
|
++count;
|
|
}
|
|
}
|
|
setValue(count > 0 ? total / count : count);
|
|
setCount(count);
|
|
} else {
|
|
setValue(RideFile::NoTemp);
|
|
setCount(1);
|
|
}
|
|
}
|
|
RideMetric *clone() const { return new AvgTemp(*this); }
|
|
};
|
|
|
|
static bool avgTempAdded =
|
|
RideMetricFactory::instance().addMetric(AvgTemp());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class MaxPower : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(MaxPower)
|
|
double max;
|
|
public:
|
|
MaxPower() : max(0.0)
|
|
{
|
|
setSymbol("max_power");
|
|
setInternalName("Max Power");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Max Power"));
|
|
setMetricUnits(tr("watts"));
|
|
setImperialUnits(tr("watts"));
|
|
setType(RideMetric::Peak);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (point->watts >= max)
|
|
max = point->watts;
|
|
}
|
|
setValue(max);
|
|
}
|
|
RideMetric *clone() const { return new MaxPower(*this); }
|
|
};
|
|
|
|
static bool maxPowerAdded =
|
|
RideMetricFactory::instance().addMetric(MaxPower());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class MaxSmO2 : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(MaxSmO2)
|
|
double max;
|
|
public:
|
|
MaxSmO2() : max(0.0)
|
|
{
|
|
setSymbol("max_smo2");
|
|
setInternalName("Max SmO2");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Max SmO2"));
|
|
setMetricUnits(tr("%"));
|
|
setImperialUnits(tr("%"));
|
|
setType(RideMetric::Peak);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (point->smo2 >= max)
|
|
max = point->smo2;
|
|
}
|
|
setValue(max);
|
|
}
|
|
|
|
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("O"); }
|
|
|
|
RideMetric *clone() const { return new MaxSmO2(*this); }
|
|
};
|
|
|
|
static bool maxSmO2Added =
|
|
RideMetricFactory::instance().addMetric(MaxSmO2());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class MinSmO2 : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(MinSmO2)
|
|
double min;
|
|
public:
|
|
MinSmO2() : min(0.0)
|
|
{
|
|
setSymbol("min_smo2");
|
|
setInternalName("Min SmO2");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Min SmO2"));
|
|
setMetricUnits(tr("%"));
|
|
setImperialUnits(tr("%"));
|
|
setType(RideMetric::Peak);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (point->smo2 > 0 && point->smo2 >= min)
|
|
min = point->smo2;
|
|
}
|
|
setValue(min);
|
|
}
|
|
RideMetric *clone() const { return new MinSmO2(*this); }
|
|
};
|
|
|
|
static bool minSmO2Added =
|
|
RideMetricFactory::instance().addMetric(MinSmO2());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class MaxHr : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(MaxHr)
|
|
double max;
|
|
public:
|
|
MaxHr() : max(0.0)
|
|
{
|
|
setSymbol("max_heartrate");
|
|
setInternalName("Max Heartrate");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Max Heartrate"));
|
|
setMetricUnits(tr("bpm"));
|
|
setImperialUnits(tr("bpm"));
|
|
setType(RideMetric::Peak);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (point->hr >= max)
|
|
max = point->hr;
|
|
}
|
|
setValue(max);
|
|
}
|
|
|
|
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("H"); }
|
|
|
|
RideMetric *clone() const { return new MaxHr(*this); }
|
|
};
|
|
|
|
static bool maxHrAdded =
|
|
RideMetricFactory::instance().addMetric(MaxHr());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class MaxSpeed : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(MaxSpeed)
|
|
public:
|
|
|
|
MaxSpeed()
|
|
{
|
|
setSymbol("max_speed");
|
|
setInternalName("Max Speed");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Max Speed"));
|
|
setMetricUnits(tr("kph"));
|
|
setImperialUnits(tr("mph"));
|
|
setType(RideMetric::Peak);
|
|
setPrecision(1);
|
|
setConversion(MILES_PER_KM);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
double max = 0.0;
|
|
|
|
if (ride->areDataPresent()->kph) {
|
|
foreach (const RideFilePoint *point, ride->dataPoints())
|
|
if (point->kph > max) max = point->kph;
|
|
}
|
|
|
|
setValue(max);
|
|
}
|
|
|
|
void aggregateWith(const RideMetric &other) {
|
|
assert(symbol() == other.symbol());
|
|
const MaxSpeed &ms = dynamic_cast<const MaxSpeed&>(other);
|
|
|
|
setValue(ms.value(true) > value(true) ? ms.value(true) : value(true));
|
|
}
|
|
RideMetric *clone() const { return new MaxSpeed(*this); }
|
|
};
|
|
|
|
static bool maxSpeedAdded =
|
|
RideMetricFactory::instance().addMetric(MaxSpeed());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class MaxCadence : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(MaxCadence)
|
|
public:
|
|
|
|
MaxCadence()
|
|
{
|
|
setSymbol("max_cadence");
|
|
setInternalName("Max Cadence");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Max Cadence"));
|
|
setMetricUnits(tr("rpm"));
|
|
setImperialUnits(tr("rpm"));
|
|
setType(RideMetric::Peak);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
double max = 0.0;
|
|
foreach (const RideFilePoint *point, ride->dataPoints())
|
|
if (point->cad > max) max = point->cad;
|
|
|
|
setValue(max);
|
|
}
|
|
|
|
void aggregateWith(const RideMetric &other) {
|
|
assert(symbol() == other.symbol());
|
|
const MaxCadence &mc = dynamic_cast<const MaxCadence&>(other);
|
|
|
|
setValue(mc.value(true) > value(true) ? mc.value(true) : value(true));
|
|
}
|
|
|
|
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("C"); }
|
|
|
|
RideMetric *clone() const { return new MaxCadence(*this); }
|
|
};
|
|
|
|
static bool maxCadenceAdded =
|
|
RideMetricFactory::instance().addMetric(MaxCadence());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class MaxTemp : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(MaxTemp)
|
|
public:
|
|
|
|
MaxTemp()
|
|
{
|
|
setSymbol("max_temp");
|
|
setInternalName("Max Temp");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Max Temp"));
|
|
setMetricUnits(tr("C"));
|
|
setImperialUnits(tr("F"));
|
|
setType(RideMetric::Peak);
|
|
setPrecision(1);
|
|
setConversion(FAHRENHEIT_PER_CENTIGRADE);
|
|
setConversionSum(FAHRENHEIT_ADD_CENTIGRADE);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->temp) {
|
|
double max = 0.0;
|
|
foreach (const RideFilePoint *point, ride->dataPoints())
|
|
if (point->temp != RideFile::NoTemp && point->temp > max) max = point->temp;
|
|
|
|
setValue(max);
|
|
} else {
|
|
setValue(RideFile::NoTemp);
|
|
}
|
|
}
|
|
|
|
void aggregateWith(const RideMetric &other) {
|
|
assert(symbol() == other.symbol());
|
|
const MaxTemp &mc = dynamic_cast<const MaxTemp&>(other);
|
|
|
|
setValue(mc.value(true) > value(true) ? mc.value(true) : value(true));
|
|
}
|
|
RideMetric *clone() const { return new MaxTemp(*this); }
|
|
};
|
|
|
|
static bool maxTempAdded =
|
|
RideMetricFactory::instance().addMetric(MaxTemp());
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class NinetyFivePercentHeartRate : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(NinetyFivePercentHeartRate)
|
|
double hr;
|
|
public:
|
|
NinetyFivePercentHeartRate() : hr(0.0)
|
|
{
|
|
setSymbol("ninety_five_percent_hr");
|
|
setInternalName("95% Heartrate");
|
|
}
|
|
void initialize() {
|
|
setName(tr("95% Heartrate"));
|
|
setMetricUnits(tr("bpm"));
|
|
setImperialUnits(tr("bpm"));
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
QVector<double> hrs;
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (point->hr >= 0.0)
|
|
hrs.append(point->hr);
|
|
}
|
|
if (hrs.size() > 0) {
|
|
std::sort(hrs.begin(), hrs.end());
|
|
hr = hrs[hrs.size() * 0.95];
|
|
}
|
|
setValue(hr);
|
|
}
|
|
RideMetric *clone() const { return new NinetyFivePercentHeartRate(*this); }
|
|
};
|
|
|
|
static bool ninetyFivePercentHeartRateAdded =
|
|
RideMetricFactory::instance().addMetric(NinetyFivePercentHeartRate());
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class VAM : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(VAM)
|
|
|
|
public:
|
|
VAM()
|
|
{
|
|
setSymbol("vam");
|
|
setInternalName("VAM");
|
|
}
|
|
void initialize() {
|
|
setName(tr("VAM"));
|
|
setImperialUnits("");
|
|
setMetricUnits("");
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &deps,
|
|
const Context *) {
|
|
|
|
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));
|
|
}
|
|
RideMetric *clone() const { return new VAM(*this); }
|
|
};
|
|
|
|
static bool addVam()
|
|
{
|
|
QVector<QString> deps;
|
|
deps.append("elevation_gain");
|
|
deps.append("workout_time");
|
|
RideMetricFactory::instance().addMetric(VAM(), &deps);
|
|
return true;
|
|
}
|
|
|
|
static bool vamAdded = addVam();
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class EOA : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(EOA)
|
|
|
|
public:
|
|
EOA()
|
|
{
|
|
setSymbol("eoa");
|
|
setInternalName("EOA");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Effect of Altitude"));
|
|
setImperialUnits("%");
|
|
setMetricUnits("%");
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &deps,
|
|
const Context *) {
|
|
|
|
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);
|
|
}
|
|
RideMetric *clone() const { return new EOA(*this); }
|
|
};
|
|
|
|
static bool addEOA()
|
|
{
|
|
QVector<QString> deps;
|
|
deps.append("average_apower");
|
|
deps.append("average_power");
|
|
RideMetricFactory::instance().addMetric(EOA(), &deps);
|
|
return true;
|
|
}
|
|
|
|
static bool eoaAdded = addEOA();
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class Gradient : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(Gradient)
|
|
|
|
public:
|
|
Gradient()
|
|
{
|
|
setSymbol("gradient");
|
|
setInternalName("Gradient");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Gradient"));
|
|
setImperialUnits("%");
|
|
setMetricUnits("%");
|
|
setPrecision(1);
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &deps,
|
|
const Context *) {
|
|
|
|
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);
|
|
}
|
|
RideMetric *clone() const { return new Gradient(*this); }
|
|
};
|
|
|
|
static bool addGradient()
|
|
{
|
|
QVector<QString> deps;
|
|
deps.append("elevation_gain");
|
|
deps.append("total_distance");
|
|
RideMetricFactory::instance().addMetric(Gradient(), &deps);
|
|
return true;
|
|
}
|
|
|
|
static bool gradientAdded = addGradient();
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class MeanPowerVariance : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(MeanPowerVariance)
|
|
|
|
public:
|
|
double topRank;
|
|
|
|
MeanPowerVariance()
|
|
{
|
|
setSymbol("meanpowervariance");
|
|
setInternalName("Average Power Variance");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Power Variance"));
|
|
setImperialUnits("watts change");
|
|
setMetricUnits("watts change");
|
|
setPrecision(2);
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
// Less than 30s don't bother
|
|
if (ride->dataPoints().count() < 30) {
|
|
setValue(0);
|
|
topRank=0.00;
|
|
} else {
|
|
|
|
QVector<double> power;
|
|
QVector<double> secs;
|
|
foreach (RideFilePoint *point, ride->dataPoints()) {
|
|
power.append(point->watts);
|
|
secs.append(point->secs);
|
|
}
|
|
|
|
LTMOutliers outliers(secs.data(), power.data(), power.count(), 30, false);
|
|
setValue(outliers.getStdDeviation());
|
|
topRank = outliers.getYForRank(0);
|
|
}
|
|
}
|
|
RideMetric *clone() const { return new MeanPowerVariance(*this); }
|
|
};
|
|
|
|
static bool addMeanPowerVariance()
|
|
{
|
|
RideMetricFactory::instance().addMetric(MeanPowerVariance());
|
|
return true;
|
|
}
|
|
|
|
static bool meanPowerVarianceAdded = addMeanPowerVariance();
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class MaxPowerVariance : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(MaxPowerVariance)
|
|
|
|
public:
|
|
MaxPowerVariance()
|
|
{
|
|
setSymbol("maxpowervariance");
|
|
setInternalName("Max Power Variance");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Max Power Variance"));
|
|
setImperialUnits("watts change");
|
|
setMetricUnits("watts change");
|
|
setPrecision(2);
|
|
setType(RideMetric::Average);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &deps,
|
|
const Context *) {
|
|
|
|
MeanPowerVariance *mean = dynamic_cast<MeanPowerVariance*>(deps.value("meanpowervariance"));
|
|
if (ride->dataPoints().count() < 30)
|
|
setValue(0);
|
|
else
|
|
setValue(mean->topRank);
|
|
}
|
|
RideMetric *clone() const { return new MaxPowerVariance(*this); }
|
|
};
|
|
|
|
static bool addMaxPowerVariance()
|
|
{
|
|
QVector<QString> deps;
|
|
deps.append("meanpowervariance");
|
|
RideMetricFactory::instance().addMetric(MaxPowerVariance(), &deps);
|
|
return true;
|
|
}
|
|
|
|
static bool maxPowerVarianceAdded = addMaxPowerVariance();
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgLTE : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgLTE)
|
|
|
|
public:
|
|
|
|
AvgLTE()
|
|
{
|
|
setSymbol("average_lte");
|
|
setInternalName("Average Left Torque Effectiveness");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Left Torque Effectiveness"));
|
|
setMetricUnits(tr("%"));
|
|
setImperialUnits(tr("%"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(1);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->lte) {
|
|
|
|
double total = 0.0f;
|
|
double samples = 0.0f;
|
|
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
|
|
if (point->lte) {
|
|
samples ++;
|
|
total += point->lte;
|
|
}
|
|
}
|
|
|
|
if (total > 0.0f && samples > 0.0f) setValue(total / samples);
|
|
else setValue(0.0);
|
|
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgLTE(*this); }
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgRTE : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgRTE)
|
|
|
|
public:
|
|
|
|
AvgRTE()
|
|
{
|
|
setSymbol("average_rte");
|
|
setInternalName("Average Right Torque Effectiveness");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Right Torque Effectiveness"));
|
|
setMetricUnits(tr("%"));
|
|
setImperialUnits(tr("%"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(1);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->rte) {
|
|
|
|
double total = 0.0f;
|
|
double samples = 0.0f;
|
|
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
if (point->rte) {
|
|
samples ++;
|
|
total += point->rte;
|
|
}
|
|
}
|
|
|
|
if (total > 0.0f && samples > 0.0f) setValue(total / samples);
|
|
else setValue(0.0);
|
|
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgRTE(*this); }
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgLPS : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgLPS)
|
|
|
|
public:
|
|
|
|
AvgLPS()
|
|
{
|
|
setSymbol("average_lps");
|
|
setInternalName("Average Left Pedal Smoothness");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Left Pedal Smoothness"));
|
|
setMetricUnits(tr("%"));
|
|
setImperialUnits(tr("%"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(1);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->lps) {
|
|
|
|
double total = 0.0f;
|
|
double samples = 0.0f;
|
|
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
|
|
if (point->lps) {
|
|
samples ++;
|
|
total += point->lps;
|
|
}
|
|
}
|
|
|
|
if (total > 0.0f && samples > 0.0f) setValue(total / samples);
|
|
else setValue(0.0);
|
|
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgLPS(*this); }
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgRPS : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgRPS)
|
|
|
|
public:
|
|
|
|
AvgRPS()
|
|
{
|
|
setSymbol("average_rps");
|
|
setInternalName("Average Right Pedal Smoothness");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Right Pedal Smoothness"));
|
|
setMetricUnits(tr("%"));
|
|
setImperialUnits(tr("%"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(1);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->rps) {
|
|
|
|
double total = 0.0f;
|
|
double samples = 0.0f;
|
|
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
|
|
if (point->rps) {
|
|
samples ++;
|
|
total += point->rps;
|
|
}
|
|
}
|
|
|
|
if (total > 0.0f && samples > 0.0f) setValue(total / samples);
|
|
else setValue(0.0);
|
|
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgRPS(*this); }
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgLPCO : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgLPCO)
|
|
|
|
public:
|
|
|
|
AvgLPCO()
|
|
{
|
|
setSymbol("average_lpco");
|
|
setInternalName("Average Left Pedal Center Offset");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Left Pedal Center Offset"));
|
|
setMetricUnits(tr("mm"));
|
|
setImperialUnits(tr("in")); // inches would need more precision than 1
|
|
setType(RideMetric::Average);
|
|
setPrecision(0);
|
|
setImperialPrecision(2);
|
|
setConversion(INCH_PER_MM);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->lpco) {
|
|
|
|
double total = 0.0f;
|
|
double secs = 0.0f;
|
|
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
|
|
if (point->cad) {
|
|
secs += ride->recIntSecs();
|
|
total += point->lpco;
|
|
}
|
|
}
|
|
|
|
if (secs > 0.0f) setValue(total / secs);
|
|
else setValue(0.0);
|
|
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgLPCO(*this); }
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgRPCO : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgRPCO)
|
|
|
|
public:
|
|
|
|
AvgRPCO()
|
|
{
|
|
setSymbol("average_rpco");
|
|
setInternalName("Average Right Pedal Center Offset");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Right Pedal Center Offset"));
|
|
setMetricUnits(tr("mm"));
|
|
setImperialUnits(tr("in")); // inches would need more precision than 1
|
|
setType(RideMetric::Average);
|
|
setPrecision(0);
|
|
setImperialPrecision(2);
|
|
setConversion(INCH_PER_MM);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->rpco) {
|
|
|
|
double total = 0.0f;
|
|
double secs = 0.0f;
|
|
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
|
|
if (point->cad) {
|
|
secs += ride->recIntSecs();
|
|
total += point->rpco;
|
|
}
|
|
}
|
|
|
|
if (secs > 0.0f) setValue(total / secs);
|
|
else setValue(0.0);
|
|
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgRPCO(*this); }
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgLPPB : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgLPPB)
|
|
|
|
public:
|
|
|
|
AvgLPPB()
|
|
{
|
|
setSymbol("average_lppb");
|
|
setInternalName("Average Left Power Phase Start");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Left Power Phase Start"));
|
|
setMetricUnits(tr("°"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(0);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->lppb) {
|
|
|
|
double total = 0.0f;
|
|
double secs = 0.0f;
|
|
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
|
|
if (point->lppe>0) { // use for average if we have an end
|
|
secs += ride->recIntSecs();
|
|
total += point->lppb + (point->lppb>180?-360:0);
|
|
}
|
|
}
|
|
|
|
if (secs > 0.0f) setValue(total / secs);
|
|
else setValue(0.0);
|
|
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgLPPB(*this); }
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgRPPB : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgRTPP)
|
|
|
|
public:
|
|
|
|
AvgRPPB()
|
|
{
|
|
setSymbol("average_rppb");
|
|
setInternalName("Average Right Power Phase Start");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Right Power Phase Start"));
|
|
setMetricUnits(tr("°"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(0);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->rppb) {
|
|
|
|
double total = 0.0f;
|
|
double secs = 0.0f;
|
|
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
|
|
if (point->rppe>0) { // use for average if we have an end
|
|
secs += ride->recIntSecs();
|
|
total += point->rppb + (point->rppb>180?-360:0);
|
|
}
|
|
}
|
|
|
|
if (secs > 0.0f) setValue(total / secs);
|
|
else setValue(0.0);
|
|
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgRPPB(*this); }
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgLPPE : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgLPPE)
|
|
|
|
public:
|
|
|
|
AvgLPPE()
|
|
{
|
|
setSymbol("average_lppe");
|
|
setInternalName("Average Left Power Phase End");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Left Power Phase End"));
|
|
setMetricUnits(tr("°"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(0);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->lppe) { // end has to be > 0
|
|
|
|
double total = 0.0f;
|
|
double secs = 0.0f;
|
|
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
|
|
if (point->lppe > 0) {
|
|
secs += ride->recIntSecs();
|
|
total += point->lppe;
|
|
}
|
|
}
|
|
|
|
if (secs > 0.0f) setValue(total / secs);
|
|
else setValue(0.0);
|
|
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgLPPE(*this); }
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgRPPE : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgRPPE)
|
|
|
|
public:
|
|
|
|
AvgRPPE()
|
|
{
|
|
setSymbol("average_rppe");
|
|
setInternalName("Average Right Power Phase End");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Right Power Phase End"));
|
|
setMetricUnits(tr("°"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(0);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->rppe) {
|
|
|
|
double total = 0.0f;
|
|
double secs = 0.0f;
|
|
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
|
|
if (point->rppe > 0) { // end has to be > 0
|
|
secs += ride->recIntSecs();
|
|
total += point->rppe;
|
|
}
|
|
}
|
|
|
|
if (secs > 0.0f) setValue(total / secs);
|
|
else setValue(0.0);
|
|
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgRPPE(*this); }
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgLPPPB : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgLPPPB)
|
|
|
|
public:
|
|
|
|
AvgLPPPB()
|
|
{
|
|
setSymbol("average_lpppb");
|
|
setInternalName("Average Left Peak Power Phase Start");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Left Peak Power Phase Start"));
|
|
setMetricUnits(tr("°"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(0);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->lpppb) {
|
|
|
|
double total = 0.0f;
|
|
double secs = 0.0f;
|
|
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
|
|
if (point->lpppe>0) { // use for average if we have an end
|
|
secs += ride->recIntSecs();
|
|
total += point->lpppb + (point->lpppb>180?-360:0);
|
|
}
|
|
}
|
|
|
|
if (secs > 0.0f) setValue(total / secs);
|
|
else setValue(0.0);
|
|
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgLPPPB(*this); }
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgRPPPB : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgRPPPB)
|
|
|
|
public:
|
|
|
|
AvgRPPPB()
|
|
{
|
|
setSymbol("average_rpppb");
|
|
setInternalName("Average Right Peak Power Phase Start");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Right Peak Power Phase Start"));
|
|
setMetricUnits(tr("°"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(0);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->rpppb) {
|
|
|
|
double total = 0.0f;
|
|
double secs = 0.0f;
|
|
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
|
|
if (point->rpppe>0) { // use for average if we have an end
|
|
secs += ride->recIntSecs();
|
|
total += point->rpppb + (point->rpppb>180?-360:0);
|
|
}
|
|
}
|
|
|
|
if (secs > 0.0f) setValue(total / secs);
|
|
else setValue(0.0);
|
|
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgRPPPB(*this); }
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgLPPPE : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgLPPPE)
|
|
|
|
public:
|
|
|
|
AvgLPPPE()
|
|
{
|
|
setSymbol("average_lpppe");
|
|
setInternalName("Average Left Peak Power Phase End");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Left Peak Power Phase End"));
|
|
setMetricUnits(tr("°"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(0);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->lpppe) {
|
|
|
|
double total = 0.0f;
|
|
double secs = 0.0f;
|
|
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
|
|
if (point->lpppe > 0) { // end has to be > 0
|
|
secs += ride->recIntSecs();
|
|
total += point->lpppe;
|
|
}
|
|
}
|
|
|
|
if (secs > 0.0f) setValue(total / secs);
|
|
else setValue(0.0);
|
|
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgLPPPE(*this); }
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgRPPPE : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgRPPPE)
|
|
|
|
public:
|
|
|
|
AvgRPPPE()
|
|
{
|
|
setSymbol("average_rpppe");
|
|
setInternalName("Average Right Peak Power Phase End");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Right Peak Power Phase End"));
|
|
setMetricUnits(tr("°"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(0);
|
|
}
|
|
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &,
|
|
const Context *) {
|
|
|
|
if (ride->areDataPresent()->rpppe) {
|
|
|
|
double total = 0.0f;
|
|
double secs = 0.0f;
|
|
|
|
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
|
|
|
if (point->rpppe > 0) { // end has to be > 0
|
|
secs += ride->recIntSecs();
|
|
total += point->rpppe;
|
|
}
|
|
}
|
|
|
|
if (secs > 0.0f) setValue(total / secs);
|
|
else setValue(0.0);
|
|
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgRPPPE(*this); }
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgLPP : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgLPP)
|
|
double average_lppb;
|
|
double average_lppe;
|
|
|
|
public:
|
|
|
|
AvgLPP()
|
|
{
|
|
setSymbol("average_lpp");
|
|
setInternalName("Average Left Power Phase Length");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Left Power Phase Length"));
|
|
setMetricUnits(tr("°"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(0);
|
|
}
|
|
|
|
void compute(const RideFile *, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &deps,
|
|
const Context *) {
|
|
|
|
average_lppb = deps.value("average_lppb")->value(true);
|
|
average_lppe = deps.value("average_lppe")->value(true);
|
|
|
|
if (average_lppe>0) {
|
|
|
|
setValue(average_lppe-average_lppb);
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgLPP(*this); }
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgRPP : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgRPP)
|
|
double average_rppb;
|
|
double average_rppe;
|
|
|
|
public:
|
|
|
|
AvgRPP()
|
|
{
|
|
setSymbol("average_rpp");
|
|
setInternalName("Average Right Power Phase Length");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Right Power Phase Length"));
|
|
setMetricUnits(tr("°"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(0);
|
|
}
|
|
|
|
void compute(const RideFile *, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &deps,
|
|
const Context *) {
|
|
|
|
average_rppb = deps.value("average_rppb")->value(true);
|
|
average_rppe = deps.value("average_rppe")->value(true);
|
|
|
|
if (average_rppe>0) {
|
|
|
|
setValue(average_rppe-average_rppb);
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgRPP(*this); }
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgLPPP : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgLPPP)
|
|
double average_lpppb;
|
|
double average_lpppe;
|
|
|
|
public:
|
|
|
|
AvgLPPP()
|
|
{
|
|
setSymbol("average_lppp");
|
|
setInternalName("Average Peak Left Power Phase Length");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Left Peak Power Phase Length"));
|
|
setMetricUnits(tr("°"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(0);
|
|
}
|
|
|
|
void compute(const RideFile *, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &deps,
|
|
const Context *) {
|
|
|
|
average_lpppb = deps.value("average_lpppb")->value(true);
|
|
average_lpppe = deps.value("average_lpppe")->value(true);
|
|
|
|
if (average_lpppe>0) {
|
|
|
|
setValue(average_lpppe-average_lpppb);
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgLPPP(*this); }
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
class AvgRPPP : public RideMetric {
|
|
|
|
Q_DECLARE_TR_FUNCTIONS(AvgRPPP)
|
|
double average_rpppb;
|
|
double average_rpppe;
|
|
|
|
public:
|
|
|
|
AvgRPPP()
|
|
{
|
|
setSymbol("average_rppp");
|
|
setInternalName("Average Right Peak Power Phase Length");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Average Right Peak Power Phase Length"));
|
|
setMetricUnits(tr("°"));
|
|
setType(RideMetric::Average);
|
|
setPrecision(0);
|
|
}
|
|
|
|
void compute(const RideFile *, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &deps,
|
|
const Context *) {
|
|
|
|
average_rpppb = deps.value("average_rpppb")->value(true);
|
|
average_rpppe = deps.value("average_rpppe")->value(true);
|
|
|
|
if (average_rpppe>0) {
|
|
|
|
setValue(average_rpppe-average_rpppb);
|
|
} else {
|
|
|
|
setValue(0.0);
|
|
}
|
|
|
|
}
|
|
|
|
RideMetric *clone() const { return new AvgRPPP(*this); }
|
|
};
|
|
|
|
static bool addLeftRight()
|
|
{
|
|
QVector<QString> deps;
|
|
RideMetricFactory::instance().addMetric(AvgLTE(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgRTE(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgLPS(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgRPS(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgLPCO(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgRPCO(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgLPPB(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgRPPB(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgLPPE(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgRPPE(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgLPPPB(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgRPPPB(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgLPPPE(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgRPPPE(), &deps);
|
|
|
|
deps.append("average_lppb");
|
|
deps.append("average_lppe");
|
|
deps.append("average_rppb");
|
|
deps.append("average_rppe");
|
|
deps.append("average_lpppb");
|
|
deps.append("average_lpppe");
|
|
deps.append("average_rpppb");
|
|
deps.append("average_rpppe");
|
|
|
|
RideMetricFactory::instance().addMetric(AvgLPP(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgRPP(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgLPPP(), &deps);
|
|
RideMetricFactory::instance().addMetric(AvgRPPP(), &deps);
|
|
return true;
|
|
}
|
|
static bool leftRightAdded = addLeftRight();
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct TotalCalories : public RideMetric {
|
|
Q_DECLARE_TR_FUNCTIONS(TotalCalories)
|
|
|
|
private:
|
|
double average_hr, athlete_weight, duration, athlete_age;
|
|
|
|
public:
|
|
|
|
TotalCalories()
|
|
{
|
|
setSymbol("total_kcalories");
|
|
setInternalName("Calories");
|
|
}
|
|
void initialize() {
|
|
setName(tr("Calories (HR)"));
|
|
setMetricUnits(tr("kcal"));
|
|
setImperialUnits(tr("kcal"));
|
|
setType(RideMetric::Total);
|
|
}
|
|
void compute(const RideFile *ride, const Zones *, int,
|
|
const HrZones *, int,
|
|
const QHash<QString,RideMetric*> &deps,
|
|
const Context *context) {
|
|
|
|
average_hr = deps.value("average_hr")->value(true);
|
|
athlete_weight = deps.value("athlete_weight")->value(true);
|
|
duration = deps.value("time_riding")->value(true); // time_riding or workout_time ?
|
|
|
|
athlete_age = ride->startTime().date().year() - appsettings->cvalue(context->athlete->cyclist, GC_DOB).toDate().year();
|
|
bool male = appsettings->cvalue(context->athlete->cyclist, GC_SEX).toInt() == 0;
|
|
|
|
double kcalories = 0.0;
|
|
|
|
if (duration>0 && average_hr>0 && athlete_weight>0 && athlete_age>0) {
|
|
//Male: ((-55.0969 + (0.6309 x HR) + (0.1988 x W) + (0.2017 x A))/4.184) x 60 x T
|
|
//Female: ((-20.4022 + (0.4472 x HR) - (0.1263 x W) + (0.074 x A))/4.184) x 60 x T
|
|
if (male)
|
|
kcalories = ((-55.0969 + (0.6309 * average_hr) + (0.1988 * athlete_weight) + (0.2017 * athlete_age))/4.184) * duration/60;
|
|
else
|
|
kcalories = ((-20.4022 + (0.4472 * average_hr) + (0.1263 * athlete_weight) + (0.074 * athlete_age))/4.184) * duration/60;
|
|
|
|
}
|
|
setValue(kcalories);
|
|
}
|
|
|
|
bool isRelevantForRide(const RideItem *ride) const { return ride->present.contains("H"); }
|
|
|
|
RideMetric *clone() const { return new TotalCalories(*this); }
|
|
};
|
|
|
|
static bool addTotalCalories() {
|
|
QVector<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();
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|