mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-14 00:28:42 +00:00
306 lines
15 KiB
C++
306 lines
15 KiB
C++
/*
|
|
* Copyright (c) 2010 Mark Liversedge (liversedge@gmail.com)
|
|
*
|
|
* 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 "DataProcessor.h"
|
|
#include "Settings.h"
|
|
#include "Units.h"
|
|
#include "HelpWhatsThis.h"
|
|
#include <algorithm>
|
|
#include <QVector>
|
|
|
|
// Config widget used by the Preferences/Options config panes
|
|
class FixGaps;
|
|
class FixGapsConfig : public DataProcessorConfig
|
|
{
|
|
Q_DECLARE_TR_FUNCTIONS(FixGapsConfig)
|
|
|
|
friend class ::FixGaps;
|
|
protected:
|
|
QHBoxLayout *layout;
|
|
QLabel *toleranceLabel, *beerandburritoLabel;
|
|
QDoubleSpinBox *tolerance,
|
|
*beerandburrito;
|
|
|
|
public:
|
|
FixGapsConfig(QWidget *parent) : DataProcessorConfig(parent) {
|
|
|
|
HelpWhatsThis *help = new HelpWhatsThis(parent);
|
|
parent->setWhatsThis(help->getWhatsThisText(HelpWhatsThis::MenuBar_Edit_FixGapsInRecording));
|
|
|
|
layout = new QHBoxLayout(this);
|
|
|
|
layout->setContentsMargins(0,0,0,0);
|
|
setContentsMargins(0,0,0,0);
|
|
|
|
toleranceLabel = new QLabel(tr("Tolerance"));
|
|
beerandburritoLabel = new QLabel(tr("Stop"));
|
|
|
|
tolerance = new QDoubleSpinBox();
|
|
tolerance->setMaximum(99.99);
|
|
tolerance->setMinimum(0);
|
|
tolerance->setSingleStep(0.1);
|
|
|
|
beerandburrito = new QDoubleSpinBox();
|
|
beerandburrito->setMaximum(99999.99);
|
|
beerandburrito->setMinimum(0);
|
|
beerandburrito->setSingleStep(0.1);
|
|
|
|
layout->addWidget(toleranceLabel);
|
|
layout->addWidget(tolerance);
|
|
layout->addWidget(beerandburritoLabel);
|
|
layout->addWidget(beerandburrito);
|
|
layout->addStretch();
|
|
}
|
|
|
|
//~FixGapsConfig() {} // deliberately not declared since Qt will delete
|
|
// the widget and its children when the config pane is deleted
|
|
|
|
QString explain() {
|
|
return(QString(tr("Many devices, especially wireless devices, will "
|
|
"drop connections to the bike computer. This leads "
|
|
"to lost samples in the resulting data, or so-called "
|
|
"drops in recording.\n\n"
|
|
"In order to calculate peak powers and averages, it "
|
|
"is very helpful to remove these gaps, and either "
|
|
"smooth the data where it is missing or just "
|
|
"replace with zero value samples\n\n"
|
|
"This function performs this task, taking two "
|
|
"parameters;\n\n"
|
|
"tolerance - this defines the minimum size of a "
|
|
"recording gap (in seconds) that will be processed. "
|
|
"any gap shorter than this will not be affected.\n\n"
|
|
"stop - this defines the maximum size of "
|
|
"gap (in seconds) that will have a smoothing algorithm "
|
|
"applied. Where a gap is shorter than this value it will "
|
|
"be filled with values interpolated from the values "
|
|
"recorded before and after the gap. If it is longer "
|
|
"than this value, it will be filled with zero values.")));
|
|
}
|
|
|
|
void readConfig() {
|
|
double tol = appsettings->value(NULL, GC_DPFG_TOLERANCE, "1.0").toDouble();
|
|
double stop = appsettings->value(NULL, GC_DPFG_STOP, "1.0").toDouble();
|
|
tolerance->setValue(tol);
|
|
beerandburrito->setValue(stop);
|
|
}
|
|
|
|
void saveConfig() {
|
|
appsettings->setValue(GC_DPFG_TOLERANCE, tolerance->value());
|
|
appsettings->setValue(GC_DPFG_STOP, beerandburrito->value());
|
|
}
|
|
};
|
|
|
|
|
|
// RideFile Dataprocessor -- used to handle gaps in recording
|
|
// by inserting interpolated/zero samples
|
|
// to ensure dataPoints are contiguous in time
|
|
//
|
|
class FixGaps : public DataProcessor {
|
|
Q_DECLARE_TR_FUNCTIONS(FixGaps)
|
|
|
|
public:
|
|
FixGaps() {}
|
|
~FixGaps() {}
|
|
|
|
// the processor
|
|
bool postProcess(RideFile *, DataProcessorConfig* config);
|
|
|
|
// the config widget
|
|
DataProcessorConfig* processorConfig(QWidget *parent) {
|
|
return new FixGapsConfig(parent);
|
|
}
|
|
|
|
// Localized Name
|
|
QString name() {
|
|
return (tr("Fix Gaps in Recording"));
|
|
}
|
|
};
|
|
|
|
static bool fixGapsAdded = DataProcessorFactory::instance().registerProcessor(QString("Fix Gaps in Recording"), new FixGaps());
|
|
|
|
bool
|
|
FixGaps::postProcess(RideFile *ride, DataProcessorConfig *config=0)
|
|
{
|
|
// get settings
|
|
double tolerance, stop;
|
|
if (config == NULL) { // being called automatically
|
|
tolerance = appsettings->value(NULL, GC_DPFG_TOLERANCE, "1.0").toDouble();
|
|
stop = appsettings->value(NULL, GC_DPFG_STOP, "1.0").toDouble();
|
|
} else { // being called manually
|
|
tolerance = ((FixGapsConfig*)(config))->tolerance->value();
|
|
stop = ((FixGapsConfig*)(config))->beerandburrito->value();
|
|
}
|
|
|
|
// if the number of duration / number of samples
|
|
// equals the recording interval then we don't need
|
|
// to post-process for gaps
|
|
|
|
// Additionally, If there are less than 2 dataPoints then there
|
|
// is no way of post processing anyway (e.g. manual workouts)
|
|
if (ride->dataPoints().count() < 2) return false;
|
|
|
|
// OK, so there are probably some gaps, lets post process them
|
|
RideFilePoint *last = NULL;
|
|
int dropouts = 0;
|
|
double dropouttime = 0.0;
|
|
|
|
// put it all in a LUW
|
|
ride->command->startLUW("Fix Gaps in Recording");
|
|
|
|
for (int position = 0; position < ride->dataPoints().count(); position++) {
|
|
RideFilePoint *point = ride->dataPoints()[position];
|
|
|
|
if (last == NULL) last = point;
|
|
else {
|
|
double gap = point->secs - last->secs - ride->recIntSecs();
|
|
|
|
// if we have gps and we moved, then this isn't a stop
|
|
bool stationary = ((last->lat || last->lon) && (point->lat || point->lon)) // gps is present
|
|
&& last->lat == point->lat && last->lon == point->lon;
|
|
|
|
// moved for less than 30 seconds ... interpolate
|
|
if (!stationary && gap > tolerance && gap < stop) {
|
|
|
|
// what's needed?
|
|
dropouts++;
|
|
dropouttime += gap;
|
|
|
|
int count = gap/ride->recIntSecs();
|
|
double hrdelta = (point->hr - last->hr) / (double) count;
|
|
double pwrdelta = (point->watts - last->watts) / (double) count;
|
|
double kphdelta = (point->kph - last->kph) / (double) count;
|
|
double kmdelta = (point->km - last->km) / (double) count;
|
|
double caddelta = (point->cad - last->cad) / (double) count;
|
|
double altdelta = (point->alt - last->alt) / (double) count;
|
|
double nmdelta = (point->nm - last->nm) / (double) count;
|
|
double londelta = (point->lon - last->lon) / (double) count;
|
|
double latdelta = (point->lat - last->lat) / (double) count;
|
|
double hwdelta = (point->headwind - last->headwind) / (double) count;
|
|
double slopedelta = (point->slope - last->slope) / (double) count;
|
|
double temperaturedelta = (point->temp - last->temp) / (double) count;
|
|
double lrbalancedelta = (point->lrbalance - last->lrbalance) / (double) count;
|
|
double ltedelta = (point->lte - last->lte) / (double) count;
|
|
double rtedelta = (point->rte - last->rte) / (double) count;
|
|
double lpsdelta = (point->lps - last->lps) / (double) count;
|
|
double rpsdelta = (point->rps - last->rps) / (double) count;
|
|
double lpcodelta = (point->lpco - last->lpco) / (double) count;
|
|
double rpcodelta = (point->rpco - last->rpco) / (double) count;
|
|
double lppbdelta = (point->lppb - last->lppb) / (double) count;
|
|
double rppbdelta = (point->rppb - last->rppb) / (double) count;
|
|
double lppedelta = (point->lppe - last->lppe) / (double) count;
|
|
double rppedelta = (point->rppe - last->rppe) / (double) count;
|
|
double lpppbdelta = (point->lpppb - last->lpppb) / (double) count;
|
|
double rpppbdelta = (point->rpppb - last->rpppb) / (double) count;
|
|
double lpppedelta = (point->lpppe - last->lpppe) / (double) count;
|
|
double rpppedelta = (point->rpppe - last->rpppe) / (double) count;
|
|
double smo2delta = (point->smo2 - last->smo2) / (double) count;
|
|
double thbdelta = (point->thb - last->thb) / (double) count;
|
|
double rcontactdelta = (point->rcontact - last->rcontact) / (double) count;
|
|
double rcaddelta = (point->rcad - last->rcad) / (double) count;
|
|
double rvertdelta = (point->rvert - last->rvert) / (double) count;
|
|
|
|
|
|
// add the points
|
|
for(int i=0; i<count; i++) {
|
|
RideFilePoint *add = new RideFilePoint(last->secs+((i+1)*ride->recIntSecs()),
|
|
last->cad+((i+1)*caddelta),
|
|
last->hr + ((i+1)*hrdelta),
|
|
last->km + ((i+1)*kmdelta),
|
|
last->kph + ((i+1)*kphdelta),
|
|
last->nm + ((i+1)*nmdelta),
|
|
last->watts + ((i+1)*pwrdelta),
|
|
last->alt + ((i+1)*altdelta),
|
|
last->lon + ((i+1)*londelta),
|
|
last->lat + ((i+1)*latdelta),
|
|
last->headwind + ((i+1)*hwdelta),
|
|
last->slope + ((i+1)*slopedelta),
|
|
last->temp + ((i+1)*temperaturedelta),
|
|
last->lrbalance + ((i+1)*lrbalancedelta),
|
|
last->lte + ((i+1)*ltedelta),
|
|
last->rte + ((i+1)*rtedelta),
|
|
last->lps + ((i+1)*lpsdelta),
|
|
last->rps + ((i+1)*rpsdelta),
|
|
last->lpco + ((i+1)*lpcodelta),
|
|
last->rpco + ((i+1)*rpcodelta),
|
|
last->lppb + ((i+1)*lppbdelta),
|
|
last->rppb + ((i+1)*rppbdelta),
|
|
last->lppe + ((i+1)*lppedelta),
|
|
last->rppe + ((i+1)*rppedelta),
|
|
last->lpppb + ((i+1)*lpppbdelta),
|
|
last->rpppb + ((i+1)*rpppbdelta),
|
|
last->lpppe + ((i+1)*lpppedelta),
|
|
last->rpppe + ((i+1)*rpppedelta),
|
|
last->smo2 + ((i+1)*smo2delta),
|
|
last->thb + ((i+1)*thbdelta),
|
|
last->rvert + ((i+1)*rvertdelta),
|
|
last->rcad + ((i+1)*rcaddelta),
|
|
last->rcontact + ((i+1)*rcontactdelta),
|
|
last->interval);
|
|
|
|
ride->command->insertPoint(position++, add);
|
|
}
|
|
|
|
// stationary or greater than 30 seconds... fill with zeroes
|
|
} else if (gap > stop) {
|
|
|
|
dropouts++;
|
|
dropouttime += gap;
|
|
|
|
int count = gap/ride->recIntSecs();
|
|
double kmdelta = (point->km - last->km) / (double) count;
|
|
|
|
// add zero value points
|
|
for(int i=0; i<count; i++) {
|
|
RideFilePoint *add = new RideFilePoint(last->secs+((i+1)*ride->recIntSecs()),
|
|
0,
|
|
0,
|
|
last->km + ((i+1)*kmdelta),
|
|
0,
|
|
0,
|
|
0,
|
|
last->alt,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0.0, 0.0, 0.0, 0.0, //pedal torque / smoothness
|
|
0.0, 0.0, // pedal platform offset
|
|
0.0, 0.0, 0.0, 0.0, //pedal power phase
|
|
0.0, 0.0, 0.0, 0.0, //pedal peak power phase
|
|
0.0, 0.0, // smO2 / thb
|
|
0.0, 0.0, 0.0, // running dynamics
|
|
last->interval);
|
|
ride->command->insertPoint(position++, add);
|
|
}
|
|
}
|
|
}
|
|
last = point;
|
|
}
|
|
|
|
// end the Logical unit of work here
|
|
ride->command->endLUW();
|
|
|
|
ride->setTag("Dropouts", QString("%1").arg(dropouts));
|
|
ride->setTag("Dropout Time", QString("%1").arg(dropouttime));
|
|
|
|
if (dropouts) return true;
|
|
else return false;
|
|
}
|