mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 16:18:42 +00:00
Find intervals by time/distance
Find interval dialog will now allow you to add intervals by time or distance (e.g. add interval for every 10 minutes or every kilometer). Since there were three menu options all doing similar things, they have now been consolidated into a single dialog.
This commit is contained in:
662
src/AddIntervalDialog.cpp
Normal file
662
src/AddIntervalDialog.cpp
Normal file
@@ -0,0 +1,662 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Damien Grauser (Damien.Grauser@pev-geneve.ch)
|
||||
*
|
||||
* 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 "AddIntervalDialog.h"
|
||||
#include "MainWindow.h"
|
||||
#include "RideFile.h"
|
||||
#include <QMap>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
AddIntervalDialog::AddIntervalDialog(MainWindow *mainWindow) :
|
||||
mainWindow(mainWindow)
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setWindowTitle("Add Intervals");
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout(this);
|
||||
|
||||
intervalMethodWidget = new QWidget();
|
||||
QHBoxLayout *intervalMethodLayout = new QHBoxLayout;
|
||||
QLabel *intervalMethodLabel = new QLabel(tr("Method: "), this);
|
||||
intervalMethodLayout->addWidget(intervalMethodLabel);
|
||||
QButtonGroup *methodButtonGroup = new QButtonGroup(this);
|
||||
methodFirst = new QRadioButton(tr("First"));
|
||||
methodFirst->setChecked(true);
|
||||
methodButtonGroup->addButton(methodFirst);
|
||||
intervalMethodLayout->addWidget(methodFirst);
|
||||
methodBestPower = new QRadioButton(tr("Peak Power"));
|
||||
methodButtonGroup->addButton(methodBestPower);
|
||||
intervalMethodLayout->addWidget(methodBestPower);
|
||||
//mainLayout->addLayout(intervalMethodLayout);
|
||||
intervalMethodWidget->setLayout(intervalMethodLayout);
|
||||
mainLayout->addWidget(intervalMethodWidget);
|
||||
|
||||
intervalPeakPowerWidget = new QWidget();
|
||||
intervalPeakPowerTypeLayout = new QHBoxLayout;
|
||||
QLabel *intervalPeakPowerTypeLabel = new QLabel(tr("Type: "), this);
|
||||
intervalPeakPowerTypeLayout->addWidget(intervalPeakPowerTypeLabel);
|
||||
QButtonGroup *peakPowerTypeButtonGroup = new QButtonGroup(this);
|
||||
peakPowerStandard = new QRadioButton(tr("Standard"));
|
||||
peakPowerStandard->setChecked(true);
|
||||
peakPowerTypeButtonGroup->addButton(peakPowerStandard);
|
||||
intervalPeakPowerTypeLayout->addWidget(peakPowerStandard);
|
||||
peakPowerCustom = new QRadioButton(tr("Custom"));
|
||||
peakPowerTypeButtonGroup->addButton(peakPowerCustom);
|
||||
intervalPeakPowerTypeLayout->addWidget(peakPowerCustom);
|
||||
//mainLayout->addLayout(intervalPeakPowerTypeLayout);
|
||||
intervalPeakPowerWidget->setLayout(intervalPeakPowerTypeLayout);
|
||||
intervalPeakPowerWidget->hide();
|
||||
mainLayout->addWidget(intervalPeakPowerWidget);
|
||||
|
||||
intervalTypeWidget = new QWidget();
|
||||
QHBoxLayout *intervalTypeLayout = new QHBoxLayout;
|
||||
QLabel *intervalTypeLabel = new QLabel(tr("Interval length: "), this);
|
||||
intervalTypeLayout->addWidget(intervalTypeLabel);
|
||||
QButtonGroup *typeButtonGroup = new QButtonGroup(this);
|
||||
typeTime = new QRadioButton(tr("By time"));
|
||||
typeTime->setChecked(true);
|
||||
typeButtonGroup->addButton(typeTime);
|
||||
intervalTypeLayout->addWidget(typeTime);
|
||||
typeDistance = new QRadioButton(tr("By distance"));
|
||||
typeButtonGroup->addButton(typeDistance);
|
||||
intervalTypeLayout->addWidget(typeDistance);
|
||||
//mainLayout->addLayout(intervalTypeLayout);
|
||||
intervalTypeWidget->setLayout(intervalTypeLayout);
|
||||
mainLayout->addWidget(intervalTypeWidget);
|
||||
|
||||
intervalTimeWidget = new QWidget();
|
||||
QHBoxLayout *intervalTimeLayout = new QHBoxLayout;
|
||||
QLabel *intervalTimeLabel = new QLabel(tr("Time: "), this);
|
||||
intervalTimeLayout->addWidget(intervalTimeLabel);
|
||||
hrsSpinBox = new QDoubleSpinBox(this);
|
||||
hrsSpinBox->setDecimals(0);
|
||||
hrsSpinBox->setMinimum(0.0);
|
||||
hrsSpinBox->setSuffix(" hrs");
|
||||
hrsSpinBox->setSingleStep(1.0);
|
||||
hrsSpinBox->setAlignment(Qt::AlignRight);
|
||||
intervalTimeLayout->addWidget(hrsSpinBox);
|
||||
minsSpinBox = new QDoubleSpinBox(this);
|
||||
minsSpinBox->setDecimals(0);
|
||||
minsSpinBox->setRange(0.0, 59.0);
|
||||
minsSpinBox->setSuffix(" mins");
|
||||
minsSpinBox->setSingleStep(1.0);
|
||||
minsSpinBox->setWrapping(true);
|
||||
minsSpinBox->setAlignment(Qt::AlignRight);
|
||||
minsSpinBox->setValue(1.0);
|
||||
intervalTimeLayout->addWidget(minsSpinBox);
|
||||
secsSpinBox = new QDoubleSpinBox(this);
|
||||
secsSpinBox->setDecimals(0);
|
||||
secsSpinBox->setRange(0.0, 59.0);
|
||||
secsSpinBox->setSuffix(" secs");
|
||||
secsSpinBox->setSingleStep(1.0);
|
||||
secsSpinBox->setWrapping(true);
|
||||
secsSpinBox->setAlignment(Qt::AlignRight);
|
||||
intervalTimeLayout->addWidget(secsSpinBox);
|
||||
//mainLayout->addLayout(intervalLengthLayout);
|
||||
intervalTimeWidget->setLayout(intervalTimeLayout);
|
||||
mainLayout->addWidget(intervalTimeWidget);
|
||||
|
||||
intervalDistanceWidget = new QWidget();
|
||||
QHBoxLayout *intervalDistanceLayout = new QHBoxLayout;
|
||||
QLabel *intervalDistanceLabel = new QLabel(tr("Distance: "), this);
|
||||
intervalDistanceLayout->addWidget(intervalDistanceLabel);
|
||||
kmsSpinBox = new QDoubleSpinBox(this);
|
||||
kmsSpinBox->setDecimals(0);
|
||||
kmsSpinBox->setRange(0.0, 999);
|
||||
kmsSpinBox->setValue(5.0);
|
||||
kmsSpinBox->setSuffix(" km");
|
||||
kmsSpinBox->setSingleStep(1.0);
|
||||
kmsSpinBox->setAlignment(Qt::AlignRight);
|
||||
intervalDistanceLayout->addWidget(kmsSpinBox);
|
||||
msSpinBox = new QDoubleSpinBox(this);
|
||||
msSpinBox->setDecimals(0);
|
||||
msSpinBox->setRange(0.0, 999);
|
||||
msSpinBox->setSuffix(" m");
|
||||
msSpinBox->setSingleStep(1.0);
|
||||
msSpinBox->setAlignment(Qt::AlignRight);
|
||||
intervalDistanceLayout->addWidget(msSpinBox);
|
||||
//mainLayout->addLayout(intervalDistanceLayout);
|
||||
intervalDistanceWidget->setLayout(intervalDistanceLayout);
|
||||
intervalDistanceWidget->hide();
|
||||
mainLayout->addWidget(intervalDistanceWidget);
|
||||
|
||||
intervalCountWidget = new QWidget();
|
||||
QHBoxLayout *intervalCountLayout = new QHBoxLayout;
|
||||
QLabel *intervalCountLabel = new QLabel(tr("How many to find: "), this);
|
||||
intervalCountLayout->addWidget(intervalCountLabel);
|
||||
countSpinBox = new QDoubleSpinBox(this);
|
||||
countSpinBox->setDecimals(0);
|
||||
countSpinBox->setMinimum(1.0);
|
||||
countSpinBox->setValue(5.0); // lets default to the top 5 powers
|
||||
countSpinBox->setSingleStep(1.0);
|
||||
countSpinBox->setAlignment(Qt::AlignRight);
|
||||
intervalCountLayout->addWidget(countSpinBox);
|
||||
//mainLayout->addLayout(intervalCountLayout);
|
||||
intervalCountWidget->setLayout(intervalCountLayout);
|
||||
mainLayout->addWidget(intervalCountWidget);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
QLabel *resultsLabel = new QLabel(tr("Results:"), this);
|
||||
mainLayout->addWidget(resultsLabel);
|
||||
|
||||
// user can select from the results to add
|
||||
// to the ride intervals
|
||||
resultsTable = new QTableWidget;
|
||||
mainLayout->addWidget(resultsTable);
|
||||
resultsTable->setColumnCount(5);
|
||||
resultsTable->setColumnHidden(3, true); // has start time in secs
|
||||
resultsTable->setColumnHidden(4, true); // has stop time in secs
|
||||
resultsTable->horizontalHeader()->hide();
|
||||
// resultsTable->verticalHeader()->hide();
|
||||
resultsTable->setShowGrid(false);
|
||||
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
||||
createButton = new QPushButton(tr("&Create Intervals"), this);
|
||||
buttonLayout->addWidget(createButton);
|
||||
doneButton = new QPushButton(tr("&Done"), this);
|
||||
buttonLayout->addWidget(doneButton);
|
||||
addButton = new QPushButton(tr("&Add to Intervals"));
|
||||
buttonLayout->addWidget(addButton);
|
||||
mainLayout->addLayout(buttonLayout);
|
||||
|
||||
connect(methodFirst, SIGNAL(clicked()), this, SLOT(methodFirstClicked()));
|
||||
connect(methodBestPower, SIGNAL(clicked()), this, SLOT(methodBestPowerClicked()));
|
||||
connect(peakPowerStandard, SIGNAL(clicked()), this, SLOT(peakPowerStandardClicked()));
|
||||
connect(peakPowerCustom, SIGNAL(clicked()), this, SLOT(peakPowerCustomClicked()));
|
||||
connect(typeTime, SIGNAL(clicked()), this, SLOT(typeTimeClicked()));
|
||||
connect(typeDistance, SIGNAL(clicked()), this, SLOT(typeDistanceClicked()));
|
||||
|
||||
|
||||
|
||||
|
||||
connect(createButton, SIGNAL(clicked()), this, SLOT(createClicked()));
|
||||
connect(doneButton, SIGNAL(clicked()), this, SLOT(doneClicked()));
|
||||
connect(addButton, SIGNAL(clicked()), this, SLOT(addClicked()));
|
||||
}
|
||||
|
||||
void
|
||||
AddIntervalDialog::methodFirstClicked()
|
||||
{
|
||||
intervalPeakPowerWidget->hide();
|
||||
intervalTypeWidget->show();
|
||||
if (typeDistance->isChecked())
|
||||
typeDistanceClicked();
|
||||
else
|
||||
typeTimeClicked();
|
||||
intervalCountWidget->show();
|
||||
}
|
||||
|
||||
void
|
||||
AddIntervalDialog::methodBestPowerClicked()
|
||||
{
|
||||
intervalPeakPowerWidget->show();
|
||||
if (peakPowerCustom->isChecked())
|
||||
peakPowerCustomClicked();
|
||||
else
|
||||
peakPowerStandardClicked();
|
||||
}
|
||||
|
||||
void
|
||||
AddIntervalDialog::peakPowerStandardClicked()
|
||||
{
|
||||
intervalTypeWidget->hide();
|
||||
intervalTimeWidget->hide();
|
||||
intervalDistanceWidget->hide();
|
||||
intervalCountWidget->hide();
|
||||
}
|
||||
|
||||
void
|
||||
AddIntervalDialog::peakPowerCustomClicked()
|
||||
{
|
||||
intervalTypeWidget->show();
|
||||
if (typeDistance->isChecked())
|
||||
typeDistanceClicked();
|
||||
else
|
||||
typeTimeClicked();
|
||||
intervalCountWidget->show();
|
||||
}
|
||||
|
||||
void
|
||||
AddIntervalDialog::typeTimeClicked()
|
||||
{
|
||||
intervalDistanceWidget->hide();
|
||||
intervalTimeWidget->show();
|
||||
}
|
||||
|
||||
void
|
||||
AddIntervalDialog::typeDistanceClicked()
|
||||
{
|
||||
intervalDistanceWidget->show();
|
||||
intervalTimeWidget->hide();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// little helper function
|
||||
static void
|
||||
clearResultsTable(QTableWidget *resultsTable)
|
||||
{
|
||||
// zap the 3 main cols and two hidden ones
|
||||
for (int i=0; i<resultsTable->rowCount(); i++) {
|
||||
for (int j=0; j<resultsTable->columnCount(); j++)
|
||||
delete resultsTable->takeItem(i,j);
|
||||
}
|
||||
}
|
||||
|
||||
static double
|
||||
intervalDuration(const RideFilePoint *start, const RideFilePoint *stop, const RideFile *ride)
|
||||
{
|
||||
return stop->secs - start->secs + ride->recIntSecs();
|
||||
}
|
||||
|
||||
static double
|
||||
intervalDistance(const RideFilePoint *start, const RideFilePoint *stop, const RideFile *ride)
|
||||
{
|
||||
return 1000*(stop->km - start->km);// + (ride->recIntSecs()*stop->kph/3600));
|
||||
}
|
||||
|
||||
static bool
|
||||
intervalsOverlap(const AddIntervalDialog::AddedInterval &a,
|
||||
const AddIntervalDialog::AddedInterval &b)
|
||||
{
|
||||
if ((a.start <= b.start) && (a.stop > b.start))
|
||||
return true;
|
||||
if ((b.start <= a.start) && (b.stop > a.start))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct CompareBests {
|
||||
// Sort by decreasing power and increasing start time.
|
||||
bool operator()(const AddIntervalDialog::AddedInterval &a,
|
||||
const AddIntervalDialog::AddedInterval &b) const {
|
||||
if (a.avg > b.avg)
|
||||
return true;
|
||||
if (b.avg > a.avg)
|
||||
return false;
|
||||
return a.start < b.start;
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
AddIntervalDialog::createClicked()
|
||||
{
|
||||
const RideFile *ride = mainWindow->currentRide();
|
||||
if (!ride) {
|
||||
QMessageBox::critical(this, tr("Select Ride"), tr("No ride selected!"));
|
||||
return;
|
||||
}
|
||||
|
||||
int maxIntervals = (int) countSpinBox->value();
|
||||
|
||||
double windowSizeSecs = (hrsSpinBox->value() * 3600.0
|
||||
+ minsSpinBox->value() * 60.0
|
||||
+ secsSpinBox->value());
|
||||
|
||||
double windowSizeMeters = (kmsSpinBox->value() * 1000.0
|
||||
+ msSpinBox->value());
|
||||
|
||||
if (windowSizeSecs == 0.0) {
|
||||
QMessageBox::critical(this, tr("Bad Interval Length"),
|
||||
tr("Interval length must be greater than zero!"));
|
||||
return;
|
||||
}
|
||||
|
||||
bool byTime = typeTime->isChecked();
|
||||
|
||||
QList<AddedInterval> results;
|
||||
if (methodBestPower->isChecked()) {
|
||||
if (peakPowerStandard->isChecked())
|
||||
findPeakPowerStandard(ride, results);
|
||||
else
|
||||
findBests(byTime, ride, (byTime?windowSizeSecs:windowSizeMeters), maxIntervals, results, "");
|
||||
}
|
||||
else
|
||||
findFirsts(byTime, ride, (byTime?windowSizeSecs:windowSizeMeters), maxIntervals, results);
|
||||
|
||||
// clear the table
|
||||
clearResultsTable(resultsTable);
|
||||
|
||||
// populate the table
|
||||
resultsTable->setRowCount(results.size());
|
||||
int row = 0;
|
||||
foreach (const AddedInterval &interval, results) {
|
||||
|
||||
double secs = interval.start;
|
||||
double mins = floor(secs / 60);
|
||||
secs = secs - mins * 60.0;
|
||||
double hrs = floor(mins / 60);
|
||||
mins = mins - hrs * 60.0;
|
||||
|
||||
// check box
|
||||
QCheckBox *c = new QCheckBox;
|
||||
c->setCheckState(Qt::Checked);
|
||||
resultsTable->setCellWidget(row, 0, c);
|
||||
|
||||
// start time
|
||||
QString start = "%1:%2:%3";
|
||||
start = start.arg(hrs, 0, 'f', 0);
|
||||
start = start.arg(mins, 2, 'f', 0, QLatin1Char('0'));
|
||||
start = start.arg(round(secs), 2, 'f', 0, QLatin1Char('0'));
|
||||
|
||||
QTableWidgetItem *t = new QTableWidgetItem;
|
||||
t->setText(start);
|
||||
t->setFlags(t->flags() & (~Qt::ItemIsEditable));
|
||||
resultsTable->setItem(row, 1, t);
|
||||
|
||||
QTableWidgetItem *n = new QTableWidgetItem;
|
||||
n->setText(interval.name);
|
||||
n->setFlags(n->flags() | (Qt::ItemIsEditable));
|
||||
resultsTable->setItem(row, 2, n);
|
||||
|
||||
// hidden columns - start, stop
|
||||
QString strt = QString("%1").arg(interval.start); // can't use secs as it gets modified
|
||||
QTableWidgetItem *st = new QTableWidgetItem;
|
||||
st->setText(strt);
|
||||
resultsTable->setItem(row, 3, st);
|
||||
|
||||
QString stp = QString("%1").arg(interval.stop); // was interval.start+x
|
||||
QTableWidgetItem *sp = new QTableWidgetItem;
|
||||
sp->setText(stp);
|
||||
resultsTable->setItem(row, 4, sp);
|
||||
|
||||
row++;
|
||||
}
|
||||
resultsTable->resizeColumnToContents(0);
|
||||
resultsTable->resizeColumnToContents(1);
|
||||
resultsTable->setColumnWidth(2,200);
|
||||
}
|
||||
|
||||
void
|
||||
AddIntervalDialog::findFirsts(bool typeTime, const RideFile *ride, double windowSize,
|
||||
int maxIntervals, QList<AddedInterval> &results)
|
||||
{
|
||||
QList<AddedInterval> bests;
|
||||
|
||||
double secsDelta = ride->recIntSecs();
|
||||
double totalWatts = 0.0;
|
||||
QList<const RideFilePoint*> window;
|
||||
|
||||
// ride is shorter than the window size!
|
||||
if (typeTime)
|
||||
if (windowSize > ride->dataPoints().last()->secs + secsDelta) return;
|
||||
else
|
||||
if (windowSize > ride->dataPoints().last()->km*1000) return;
|
||||
|
||||
double rest = 0;
|
||||
// We're looking for intervals with durations in [windowSizeSecs, windowSizeSecs + secsDelta).
|
||||
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
||||
|
||||
// Add points until interval duration is >= windowSizeSecs.
|
||||
totalWatts += point->watts;
|
||||
window.append(point);
|
||||
double duration = intervalDuration(window.first(), window.last(), ride);
|
||||
double distance = intervalDistance(window.first(), window.last(), ride);
|
||||
|
||||
if ((typeTime && duration >= (windowSize - rest)) ||
|
||||
(!typeTime && distance >= (windowSize - rest))) {
|
||||
double start = window.first()->secs;
|
||||
double stop = start + duration - (typeTime?0:ride->recIntSecs()); // correction for distance
|
||||
double avg = totalWatts * secsDelta / duration;
|
||||
bests.append(AddedInterval(start, stop, avg));
|
||||
rest = (typeTime?duration-windowSize+rest:distance-windowSize+rest);
|
||||
// Discard points
|
||||
window.clear();
|
||||
totalWatts = 0;
|
||||
|
||||
// For distance the last point of an interval is also the first point of the next
|
||||
if (!typeTime) {
|
||||
totalWatts += point->watts;
|
||||
window.append(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (!bests.empty() && (results.size() < maxIntervals)) {
|
||||
AddedInterval candidate = bests.takeFirst();
|
||||
|
||||
QString name = "#%1 %2%3 (%4w)";
|
||||
name = name.arg(results.count()+1);
|
||||
|
||||
if (typeTime) {
|
||||
// best n mins
|
||||
if (windowSize < 60) {
|
||||
// whole seconds
|
||||
name = name.arg(windowSize);
|
||||
name = name.arg("sec");
|
||||
} else if (windowSize >= 60 && !(((int)windowSize)%60)) {
|
||||
// whole minutes
|
||||
name = name.arg(windowSize/60);
|
||||
name = name.arg("min");
|
||||
} else {
|
||||
double secs = windowSize;
|
||||
double mins = ((int) secs) / 60;
|
||||
secs = secs - mins * 60.0;
|
||||
double hrs = ((int) mins) / 60;
|
||||
mins = mins - hrs * 60.0;
|
||||
QString tm = "%1:%2:%3";
|
||||
tm = tm.arg(hrs, 0, 'f', 0);
|
||||
tm = tm.arg(mins, 2, 'f', 0, QLatin1Char('0'));
|
||||
tm = tm.arg(secs, 2, 'f', 0, QLatin1Char('0'));
|
||||
|
||||
// mins and secs
|
||||
name = name.arg(tm);
|
||||
name = name.arg("");
|
||||
}
|
||||
} else {
|
||||
// best n mins
|
||||
if (windowSize < 1000) {
|
||||
// whole seconds
|
||||
name = name.arg(windowSize);
|
||||
name = name.arg("m");
|
||||
} else {
|
||||
double dist = windowSize;
|
||||
double kms = ((int) dist) / 1000;
|
||||
dist = dist - kms * 1000.0;
|
||||
double ms = dist;
|
||||
|
||||
QString tm = "%1,%2";
|
||||
tm = tm.arg(kms);
|
||||
tm = tm.arg(ms);
|
||||
|
||||
// km and m
|
||||
name = name.arg(tm);
|
||||
name = name.arg("km");
|
||||
}
|
||||
}
|
||||
name = name.arg(round(candidate.avg));
|
||||
candidate.name = name;
|
||||
|
||||
results.append(candidate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
AddIntervalDialog::findPeakPowerStandard(const RideFile *ride, QList<AddedInterval> &results)
|
||||
{
|
||||
findBests(true, ride, 5, 1, results, "Peak 5s");
|
||||
findBests(true, ride, 10, 1, results, "Peak 10s");
|
||||
findBests(true, ride, 20, 1, results, "Peak 20s");
|
||||
findBests(true, ride, 30, 1, results, "Peak 30s");
|
||||
findBests(true, ride, 60, 1, results, "Peak 1min");
|
||||
findBests(true, ride, 120, 1, results, "Peak 2min");
|
||||
findBests(true, ride, 300, 1, results, "Peak 10min");
|
||||
findBests(true, ride, 600, 1, results, "Peak 10min");
|
||||
findBests(true, ride, 1200, 1, results, "Peak 20min");
|
||||
findBests(true, ride, 1800, 1, results, "Peak 30min");
|
||||
findBests(true, ride, 3600, 1, results, "Peak 60min");
|
||||
}
|
||||
|
||||
void
|
||||
AddIntervalDialog::findBests(bool typeTime, const RideFile *ride, double windowSize,
|
||||
int maxIntervals, QList<AddedInterval> &results, QString prefix)
|
||||
{
|
||||
QList<AddedInterval> bests;
|
||||
QList<AddedInterval> _results;
|
||||
|
||||
double secsDelta = ride->recIntSecs();
|
||||
double totalWatts = 0.0;
|
||||
QList<const RideFilePoint*> window;
|
||||
|
||||
// ride is shorter than the window size!
|
||||
if (typeTime)
|
||||
if (windowSize > ride->dataPoints().last()->secs + secsDelta) return;
|
||||
else
|
||||
if (windowSize > ride->dataPoints().last()->km*1000) return;
|
||||
|
||||
// We're looking for intervals with durations in [windowSizeSecs, windowSizeSecs + secsDelta).
|
||||
foreach (const RideFilePoint *point, ride->dataPoints()) {
|
||||
// Discard points until interval duration is < windowSizeSecs + secsDelta.
|
||||
while ((typeTime && !window.empty() && intervalDuration(window.first(), point, ride) >= windowSize + secsDelta) ||
|
||||
(!typeTime && window.length()>1 && intervalDistance(window.at(1), point, ride) >= windowSize)) {
|
||||
totalWatts -= window.first()->watts;
|
||||
window.takeFirst();
|
||||
}
|
||||
// Add points until interval duration or distance is >= windowSize.
|
||||
totalWatts += point->watts;
|
||||
window.append(point);
|
||||
double duration = intervalDuration(window.first(), window.last(), ride);
|
||||
double distance = intervalDistance(window.first(), window.last(), ride);
|
||||
|
||||
if ((typeTime && duration >= windowSize) ||
|
||||
(!typeTime && distance >= windowSize)) {
|
||||
double start = window.first()->secs;
|
||||
double stop = window.last()->secs; //start + duration;
|
||||
double avg = totalWatts * secsDelta / duration;
|
||||
bests.append(AddedInterval(start, stop, avg));
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(bests.begin(), bests.end(), CompareBests());
|
||||
|
||||
while (!bests.empty() && (_results.size() < maxIntervals)) {
|
||||
AddedInterval candidate = bests.takeFirst();
|
||||
bool overlaps = false;
|
||||
foreach (const AddedInterval &existing, _results) {
|
||||
if (intervalsOverlap(candidate, existing)) {
|
||||
overlaps = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!overlaps) {
|
||||
QString name = prefix;
|
||||
if (prefix == "") {
|
||||
name = "Best %2%3 #%1";
|
||||
name = name.arg(_results.count()+1);
|
||||
if (typeTime) {
|
||||
// best n mins
|
||||
if (windowSize < 60) {
|
||||
// whole seconds
|
||||
name = name.arg(windowSize);
|
||||
name = name.arg("sec");
|
||||
} else if (windowSize >= 60 && !(((int)windowSize)%60)) {
|
||||
// whole minutes
|
||||
name = name.arg(windowSize/60);
|
||||
name = name.arg("min");
|
||||
} else {
|
||||
double secs = windowSize;
|
||||
double mins = ((int) secs) / 60;
|
||||
secs = secs - mins * 60.0;
|
||||
double hrs = ((int) mins) / 60;
|
||||
mins = mins - hrs * 60.0;
|
||||
QString tm = "%1:%2:%3";
|
||||
tm = tm.arg(hrs, 0, 'f', 0);
|
||||
tm = tm.arg(mins, 2, 'f', 0, QLatin1Char('0'));
|
||||
tm = tm.arg(secs, 2, 'f', 0, QLatin1Char('0'));
|
||||
|
||||
// mins and secs
|
||||
name = name.arg(tm);
|
||||
name = name.arg("");
|
||||
}
|
||||
} else {
|
||||
// best n mins
|
||||
if (windowSize < 1000) {
|
||||
// whole seconds
|
||||
name = name.arg(windowSize);
|
||||
name = name.arg("m");
|
||||
} else {
|
||||
double dist = windowSize;
|
||||
double kms = ((int) dist) / 1000;
|
||||
dist = dist - kms * 1000.0;
|
||||
double ms = dist;
|
||||
|
||||
QString tm = "%1,%2";
|
||||
tm = tm.arg(kms);
|
||||
tm = tm.arg(ms);
|
||||
|
||||
// km and m
|
||||
name = name.arg(tm);
|
||||
name = name.arg("km");
|
||||
}
|
||||
}
|
||||
}
|
||||
name += " (%4w)";
|
||||
name = name.arg(round(candidate.avg));
|
||||
candidate.name = name;
|
||||
name = "";
|
||||
_results.append(candidate);
|
||||
}
|
||||
}
|
||||
results.append(_results);
|
||||
}
|
||||
|
||||
void
|
||||
AddIntervalDialog::doneClicked()
|
||||
{
|
||||
clearResultsTable(resultsTable); // clear out that table!
|
||||
done(0);
|
||||
}
|
||||
|
||||
void
|
||||
AddIntervalDialog::addClicked()
|
||||
{
|
||||
// run through the table row by row
|
||||
// and when the checkbox is shown
|
||||
// get name from column 2
|
||||
// get start in secs as a string from column 3
|
||||
// get stop in secs as a string from column 4
|
||||
for (int i=0; i<resultsTable->rowCount(); i++) {
|
||||
|
||||
// is it checked?
|
||||
QCheckBox *c = (QCheckBox *)resultsTable->cellWidget(i,0);
|
||||
if (c->isChecked()) {
|
||||
double start = resultsTable->item(i,3)->text().toDouble();
|
||||
double stop = resultsTable->item(i,4)->text().toDouble();
|
||||
QString name = resultsTable->item(i,2)->text();
|
||||
const RideFile *ride = mainWindow->currentRide();
|
||||
|
||||
QTreeWidgetItem *allIntervals = mainWindow->mutableIntervalItems();
|
||||
QTreeWidgetItem *last =
|
||||
new IntervalItem(ride, name, start, stop,
|
||||
ride->timeToDistance(start),
|
||||
ride->timeToDistance(stop),
|
||||
allIntervals->childCount()+1);
|
||||
last->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
|
||||
// add
|
||||
allIntervals->addChild(last);
|
||||
}
|
||||
}
|
||||
mainWindow->updateRideFileIntervals();
|
||||
}
|
||||
78
src/AddIntervalDialog.h
Normal file
78
src/AddIntervalDialog.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Damien Grauser (Damien.Grauser@pev-geneve.ch)
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef _GC_AddIntervalDialog_h
|
||||
#define _GC_AddIntervalDialog_h 1
|
||||
#include "GoldenCheetah.h"
|
||||
|
||||
#include <QtGui>
|
||||
|
||||
class MainWindow;
|
||||
class RideFile;
|
||||
|
||||
class AddIntervalDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
G_OBJECT
|
||||
|
||||
|
||||
public:
|
||||
|
||||
struct AddedInterval {
|
||||
QString name;
|
||||
double start, stop, avg;
|
||||
AddedInterval(double start, double stop, double avg) :
|
||||
start(start), stop(stop), avg(avg) {}
|
||||
};
|
||||
|
||||
AddIntervalDialog(MainWindow *mainWindow);
|
||||
|
||||
static void findPeakPowerStandard(const RideFile *ride, QList<AddedInterval> &results);
|
||||
|
||||
static void findBests(bool typeTime, const RideFile *ride, double windowSizeSecs,
|
||||
int maxIntervals, QList<AddedInterval> &results, QString name);
|
||||
|
||||
static void findFirsts(bool typeTime, const RideFile *ride, double windowSizeSecs,
|
||||
int maxIntervals, QList<AddedInterval> &results);
|
||||
|
||||
private slots:
|
||||
void createClicked();
|
||||
void doneClicked();
|
||||
void addClicked(); // add to inverval selections
|
||||
|
||||
void methodFirstClicked();
|
||||
void methodBestPowerClicked();
|
||||
void peakPowerStandardClicked();
|
||||
void peakPowerCustomClicked();
|
||||
void typeTimeClicked();
|
||||
void typeDistanceClicked();
|
||||
|
||||
private:
|
||||
|
||||
MainWindow *mainWindow;
|
||||
QWidget *intervalMethodWidget, *intervalPeakPowerWidget, *intervalTypeWidget, *intervalTimeWidget, *intervalDistanceWidget, *intervalCountWidget;
|
||||
|
||||
QHBoxLayout *intervalPeakPowerTypeLayout;
|
||||
QPushButton *createButton, *doneButton, *addButton;
|
||||
QDoubleSpinBox *hrsSpinBox, *minsSpinBox, *secsSpinBox, *countSpinBox,*kmsSpinBox, *msSpinBox;
|
||||
QRadioButton *methodFirst, *methodBestPower, *typeDistance, *typeTime, *peakPowerStandard, *peakPowerCustom;
|
||||
QTableWidget *resultsTable;
|
||||
};
|
||||
|
||||
#endif // _GC_AddIntervalDialog_h
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "AddIntervalDialog.h"
|
||||
#include "AthleteTool.h"
|
||||
#include "BestIntervalDialog.h"
|
||||
#include "ChooseCyclistDialog.h"
|
||||
@@ -715,8 +716,7 @@ MainWindow::MainWindow(const QDir &home) :
|
||||
optionsMenu->addAction(tr("Refresh Calendar"), this, SLOT(refreshCalendar()), tr (""));
|
||||
#endif
|
||||
optionsMenu->addSeparator();
|
||||
optionsMenu->addAction(tr("Find &best intervals..."), this, SLOT(findBestIntervals()), tr ("Ctrl+B"));
|
||||
optionsMenu->addAction(tr("Find power &peaks..."), this, SLOT(findPowerPeaks()), tr ("Ctrl+P"));
|
||||
optionsMenu->addAction(tr("Find intervals..."), this, SLOT(addIntervals()), tr (""));
|
||||
|
||||
// Add all the data processors to the tools menu
|
||||
const DataProcessorFactory &factory = DataProcessorFactory::instance();
|
||||
@@ -1801,6 +1801,14 @@ MainWindow::findBestIntervals()
|
||||
p->exec();
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::addIntervals()
|
||||
{
|
||||
AddIntervalDialog *p = new AddIntervalDialog(this);
|
||||
p->setWindowModality(Qt::ApplicationModal); // don't allow select other ride or it all goes wrong!
|
||||
p->exec();
|
||||
}
|
||||
|
||||
void
|
||||
MainWindow::addIntervalForPowerPeaksForSecs(RideFile *ride, int windowSizeSecs, QString name)
|
||||
{
|
||||
|
||||
@@ -246,6 +246,7 @@ class MainWindow : public QMainWindow
|
||||
#endif
|
||||
void importFile();
|
||||
void findBestIntervals();
|
||||
void addIntervals();
|
||||
void addIntervalForPowerPeaksForSecs(RideFile *ride, int windowSizeSecs, QString name);
|
||||
void findPowerPeaks();
|
||||
void splitRide();
|
||||
|
||||
@@ -186,6 +186,7 @@ HEADERS += ../qxt/src/qxtspanslider.h \
|
||||
|
||||
HEADERS += \
|
||||
AddDeviceWizard.h \
|
||||
AddIntervalDialog.h \
|
||||
Aerolab.h \
|
||||
AerolabWindow.h \
|
||||
AthleteTool.h \
|
||||
@@ -361,6 +362,7 @@ LEXSOURCES = JsonRideFile.l WithingsParser.l
|
||||
|
||||
SOURCES += \
|
||||
AddDeviceWizard.cpp \
|
||||
AddIntervalDialog.cpp \
|
||||
AerobicDecoupling.cpp \
|
||||
Aerolab.cpp \
|
||||
AerolabWindow.cpp \
|
||||
|
||||
Reference in New Issue
Block a user